Transports¶
mcp2xiaozhi supports all three standard MCP transports via the official
mcp Python SDK. Each
transport is an async context manager that yields (read, write) memory
streams carrying SessionMessage objects; the bridge pumps messages between
the WebSocket and those streams.
| Type | Use when | Notes |
|---|---|---|
stdio |
Your MCP server is a local script/binary | Spawns it as a child process; env merged with the current process. |
sse |
Legacy remote MCP server using Server-Sent Events | GET /sse for the stream, POST for requests — handled by the SDK. |
streamablehttp / http |
Modern remote MCP server (recommended) | The production HTTP transport. http is an alias. |
stdio¶
The MCP server runs as a child process. Its stdin/stdout carry JSON-RPC.
envis merged onto the current environment (PATH and other essentials are preserved).- The child inherits
stdout/stderrencoding handling; on Windows the bridge reconfigures the console to UTF-8 so non-ASCII tool descriptions never crash.
SSE¶
For remote servers speaking the older SSE transport.
{
"type": "sse",
"url": "https://mcp.example.com/sse",
"headers": { "Authorization": "Bearer xxx" },
"timeout": 5.0,
"sse_read_timeout": 300.0
}
The SDK opens a GET to url for the event stream and POSTs requests to the
endpoint advertised by the server. headers apply to both.
StreamableHTTP¶
The modern, production-grade HTTP transport (recommended for new servers).
{
"type": "streamablehttp",
"url": "https://mcp.example.com/mcp",
"headers": { "Authorization": "Bearer xxx" }
}
http is accepted as an alias and normalized to streamablehttp. Headers,
timeouts, and auth live on the underlying httpx.AsyncClient (the
recommended v2 pattern).
How the relay works¶
All three transports yield the same (read, write) stream shape, so the
bridge is transport-agnostic:
WebSocket (text) ──► JSONRPCMessage ──► SessionMessage ──► transport.write
WebSocket (text) ◄── JSONRPCMessage ◄── SessionMessage ◄── transport.read
- ws → mcp: each WebSocket frame is parsed as
JSONRPCMessage; malformed frames are logged and dropped. Valid messages are wrapped inSessionMessageand sent to the transport's write stream. - mcp → ws: each
SessionMessagefrom the transport's read stream is unwrapped to itsJSONRPCMessageand re-serialized onto the WebSocket.
Adding a custom transport¶
Subclass McpTransport, implement _session() as an async context manager
that wraps the SDK transport and yields (read, write), then register it:
from mcp2xiaozhi import McpTransport, register_transport
from contextlib import asynccontextmanager
class MyTransport(McpTransport):
kind = "my"
@asynccontextmanager
async def _session(self):
async with my_sdk_client(...) as (read, write):
yield read, write
register_transport("my", MyTransport)
See Development for the full architecture.