Abstract
The Model Context Protocol (MCP) is designed to be transport-agnostic. While most implementations support stdio and SSE by default, real-world enterprise systems often require custom transports such as:
HTTP (JSON-RPC over POST)
WebSocket (bidirectional streaming)
Message queues (Kafka, RabbitMQ)
This article explains how MCP works internally and how to implement a custom transport layer (HTTP and WebSocket) in Python.
Understanding MCP Architecture
MCP separates two concerns:
| Layer | Responsibility |
|---|
| Protocol Layer | JSON-RPC message format |
| Transport Layer | How messages are delivered |
MCP defines:
It does not mandate how messages are transported. That’s why you can plug in custom transports.
MCP Internal Flow
Client - Transport Layer - MCP Message Handler - Tool Execution - Transport Response
The key idea:
Your custom transport only needs to forward JSON messages to the MCP handler.
Implementing HTTP Transport (JSON-RPC over POST)
This is ideal when:
Integrating with API gateways
Deploying in microservices
Running behind load balancers
Install Dependencies
pip install fastapi uvicorn mcp
Example HTTP MCP Server
# http_mcp_server.py
from fastapi import FastAPI, Request
from mcp.server.fastmcp import FastMCP
app = FastAPI()
mcp = FastMCP("HTTP MCP Server")
# Define tool
@mcp.tool()
def leave_balance(employee_name: str) -> int:
return {"Jayant": 15}.get(employee_name, 0)
@app.post("/mcp")
async def handle_mcp(request: Request):
body = await request.json()
# Pass raw JSON-RPC message to MCP
response = await mcp.handle_message(body)
return response
Run Server
uvicorn http_mcp_server:app --reload
Endpoint:
POST http://localhost:8000/mcp
Characteristics of HTTP Transport
Implementing WebSocket Transport
WebSocket is ideal for:
Streaming LLM responses
Real-time agents
Long-lived sessions
WebSocket MCP Server
# websocket_mcp_server.py
from fastapi import FastAPI, WebSocket
from mcp.server.fastmcp import FastMCP
import json
app = FastAPI()
mcp = FastMCP("WebSocket MCP Server")
@mcp.tool()
def leave_balance(employee_name: str) -> int:
return {"Jayant": 15}.get(employee_name, 0)
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
while True:
data = await websocket.receive_text()
message = json.loads(data)
response = await mcp.handle_message(message)
await websocket.send_text(json.dumps(response))
Run Server
uvicorn websocket_mcp_server:app --reload
WebSocket endpoint:
ws://localhost:8000/ws
Supporting Streaming Responses
If your MCP server supports streaming tokens:
HTTP - requires chunked responses
WebSocket - naturally supports streaming
SSE - designed for streaming
Transport Comparison
| Transport | Streaming | Stateful | Best Use |
|---|
| stdio | Yes | Local only | Dev testing |
| SSE | Yes | Semi | Browser streaming |
| HTTP POST | No | Stateless | APIs |
| WebSocket | Yes | Yes | Agents, real-time |
Conclusion
MCP is protocol-focused, not transport-bound.
To implement a custom transport:
Accept JSON-RPC message
Forward to handle_message()
Return response
Keep transport layer separate
By implementing HTTP or WebSocket adapters, you gain:
Flexibility
Scalability
Enterprise readiness
Real-time capability
Custom transport is not about changing MCP — it’s about extending infrastructure around it.