Bridge any MCP server (stdio / SSE / StreamableHTTP) to a Xiaozhi server over WebSocket.
Project description
mcp2xiaozhi
Bridge any MCP server (stdio / SSE / StreamableHTTP) to a Xiaozhi server over WebSocket.
mcp2xiaozhi is a general-purpose bridge that connects any Model Context Protocol (MCP) server to a Xiaozhi server. The Xiaozhi server acts as an MCP client over a WebSocket: it sends JSON-RPC tool calls as text frames and expects JSON-RPC replies. This package receives those frames and relays them — at the protocol level — to your MCP server, wherever it runs and whatever transport it speaks.
JSON-RPC over WebSocket (text frames)
┌──────────────────────┐ wss://… ┌──────────────────────┐
│ Xiaozhi server │ ◄──────────► │ mcp2xiaozhi bridge │
│ (acts as MCP client)│ └──────────┬───────────┘
└──────────────────────┘ │ JSON-RPC
│ over MCP transport
┌──────────────────────┴───────────────┐
│ stdio │ SSE │ HTTP │
▼ ▼ ▼ ▼
local process remote server remote server
Features
- 🔄 Three transports, one bridge —
stdio,sse, andstreamablehttp(http), all native, nomcp-proxysubprocess required. - 🧱 Protocol-level relay — frames are parsed as
JSONRPCMessage(wrapped in the SDK'sSessionMessage), validated, then re-serialized. Malformed frames are logged and dropped instead of crashing the bridge. - 🔁 Automatic reconnection — exponential backoff with jitter on either side dropping; clean closes distinguished from abnormal ones.
- 🗂️ Multi-server — run one bridge or every server in your config; each server gets its own endpoint.
- ⚙️ Config-driven — drop-in compatible with the
mcp_config.jsonshape from Xiaozhi's officialmcp-calculatordemo. - 🖥️ Cross-platform — UTF-8 console handling on Windows, graceful
SIGINT/SIGTERMshutdown. - 📦 Real package —
pyproject.toml, src layout, type hints, CLI entry point, tests, CI, managed with uv.
Install
With uv (recommended):
uv tool install mcp2xiaozhi # install the CLI as a tool
# or, in a project:
uv add mcp2xiaozhi
With pip:
pip install mcp2xiaozhi
From source (for development):
git clone https://github.com/StanleyChanH/MCP2Xiaozhi.git
cd mcp2xiaozhi
uv sync --extra dev
Quick start
-
Create an MCP server (or use an existing one). A minimal stdio calculator:
# calculator.py from mcp.server.fastmcp import FastMCP import math mcp = FastMCP("Calculator") @mcp.tool() def calculator(python_expression: str) -> dict: """Evaluate a Python math expression.""" return {"success": True, "result": eval(python_expression, {"math": math})} if __name__ == "__main__": mcp.run(transport="stdio")
-
Describe your servers in
mcp_config.json:{ "mcpServers": { "calculator": { "type": "stdio", "command": "python", "args": ["calculator.py"] } } }
-
Set the Xiaozhi WebSocket endpoint and run:
export MCP_ENDPOINT="wss://api.your-xiaozhi-server.example/mcp/<token>" mcp2xiaozhi run calculator
Or run every enabled server at once:
mcp2xiaozhi run # all enabled servers mcp2xiaozhi list # show configured servers mcp2xiaozhi version
Configuration
Config discovery order: --config PATH → $MCP_CONFIG → ./mcp_config.json.
Schema
{
"mcpServers": {
"my-server": {
"type": "stdio", // stdio | sse | streamablehttp | http
"disabled": false, // optional; skip when running all
// stdio-only
"command": "python",
"args": ["-m", "my_server"],
"env": { "FOO": "bar" }, // merged onto the current environment
// sse / streamablehttp-only
"url": "https://example.com/mcp",
"headers": { "Authorization": "Bearer xxx" },
"timeout": 5.0, // connect timeout (s)
"sse_read_timeout": 300.0, // long-lived read timeout (s)
// optional per-server Xiaozhi endpoint
"endpoint": "wss://api.example.com/mcp/<token>"
}
}
}
Endpoint resolution
Each server needs the Xiaozhi WebSocket endpoint it should connect to. Resolved in priority:
endpointfield in the server config$MCP_ENDPOINT_<NAME>environment variable (name uppercased, non-alphanumeric →_)- global
$MCP_ENDPOINT
When running multiple servers, give each its own endpoint — otherwise the Xiaozhi server cannot route tool calls to the right server. The bridge will warn if a server falls back to the global endpoint while others are running.
Transports
| 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. |
All three are implemented with the official mcp Python SDK transport primitives, which yield (read, write) memory streams carrying SessionMessage objects. The bridge pumps messages between the WebSocket and those streams — it never spawns mcp-proxy.
CLI
mcp2xiaozhi [--config PATH] [--log-level LEVEL] <command>
commands:
run [SERVER] Run one server (or all enabled if omitted / --all)
list List configured servers
version Print version
options:
--endpoint URL Override the Xiaozhi endpoint (single-server runs only)
--log-level DEBUG | INFO | WARNING | ERROR | CRITICAL
python -m mcp2xiaozhi … works too.
Programmatic usage
import asyncio
from mcp2xiaozhi import McpBridge, load_config, resolve_endpoint, get_global_endpoint
async def main():
config = load_config()
server = config.require("calculator")
endpoint = resolve_endpoint(server, global_endpoint=get_global_endpoint())
bridge = McpBridge(server, endpoint)
await bridge.run() # reconnects forever until cancelled
asyncio.run(main())
Run several at once with ServerManager:
from mcp2xiaozhi import ServerManager, load_config
manager = ServerManager.from_config(load_config())
asyncio.run(manager.run())
Deployment
The bridge is a long-lived relay — the host running it must stay online, because the Xiaozhi server never connects to your MCP server directly. For remote SSE/HTTP MCP servers you can deploy it to any always-on machine (VPS, NAS, Raspberry Pi, container) and turn your laptop off.
Quick options:
# Docker (any platform) — the repo ships Dockerfile + docker-compose.yml
docker compose up -d --build
# Linux systemd — auto-start on boot, restart on crash
sudo systemctl enable --now mcp2xiaozhi
# Windows — NSSM wraps it as a native service
nssm install mcp2xiaozhi mcp2xiaozhi.exe
➡️ Full guide (config & secrets, multi-server, logs, upgrade): the Deployment docs.
Development
uv sync --extra dev # install dev dependencies
uv run ruff check . # lint
uv run mypy src # type check
uv run pytest # tests
uv build # build sdist + wheel
See CONTRIBUTING.md.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mcp2xiaozhi-0.2.0.tar.gz.
File metadata
- Download URL: mcp2xiaozhi-0.2.0.tar.gz
- Upload date:
- Size: 41.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f46d0342e8ef83da6b5f45ae32db109e773849808f929cd0333bd4366b22ff62
|
|
| MD5 |
52688068317338d62b4ce2c01fcf6e4c
|
|
| BLAKE2b-256 |
d1a6231e0108ee19ec78ee8a087e9b2128d23c273a419fc007014bd3b71ccb4a
|
Provenance
The following attestation bundles were made for mcp2xiaozhi-0.2.0.tar.gz:
Publisher:
ci.yml on StanleyChanH/MCP2Xiaozhi
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mcp2xiaozhi-0.2.0.tar.gz -
Subject digest:
f46d0342e8ef83da6b5f45ae32db109e773849808f929cd0333bd4366b22ff62 - Sigstore transparency entry: 2134842278
- Sigstore integration time:
-
Permalink:
StanleyChanH/MCP2Xiaozhi@efce624d4172468348785f8bcd2e4df8cacb596f -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/StanleyChanH
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@efce624d4172468348785f8bcd2e4df8cacb596f -
Trigger Event:
push
-
Statement type:
File details
Details for the file mcp2xiaozhi-0.2.0-py3-none-any.whl.
File metadata
- Download URL: mcp2xiaozhi-0.2.0-py3-none-any.whl
- Upload date:
- Size: 33.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c1c1d4e3c7c55c1a802768350f277222dca409aabab2c8baa78eaa7865400250
|
|
| MD5 |
d15c1fcc702e9d83075591dd6b031c16
|
|
| BLAKE2b-256 |
935aae371316ed29e12d811bb3a14c72d60f7ea8d7ddb2e4ae87faba925cf027
|
Provenance
The following attestation bundles were made for mcp2xiaozhi-0.2.0-py3-none-any.whl:
Publisher:
ci.yml on StanleyChanH/MCP2Xiaozhi
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mcp2xiaozhi-0.2.0-py3-none-any.whl -
Subject digest:
c1c1d4e3c7c55c1a802768350f277222dca409aabab2c8baa78eaa7865400250 - Sigstore transparency entry: 2134842879
- Sigstore integration time:
-
Permalink:
StanleyChanH/MCP2Xiaozhi@efce624d4172468348785f8bcd2e4df8cacb596f -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/StanleyChanH
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@efce624d4172468348785f8bcd2e4df8cacb596f -
Trigger Event:
push
-
Statement type: