Skip to main content

Bridge any MCP server (stdio / SSE / StreamableHTTP) to a Xiaozhi server over WebSocket.

Project description

mcp2xiaozhi

English | 中文

Bridge any MCP server (stdio / SSE / StreamableHTTP) to a Xiaozhi server over WebSocket.

PyPI Python License: MIT CI

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 bridgestdio, sse, and streamablehttp (http), all native, no mcp-proxy subprocess required.
  • 🧱 Protocol-level relay — frames are parsed as JSONRPCMessage (wrapped in the SDK's SessionMessage), 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.json shape from Xiaozhi's official mcp-calculator demo.
  • 🖥️ Cross-platform — UTF-8 console handling on Windows, graceful SIGINT/SIGTERM shutdown.
  • 📦 Real packagepyproject.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

  1. 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")
    
  2. Describe your servers in mcp_config.json:

    {
      "mcpServers": {
        "calculator": {
          "type": "stdio",
          "command": "python",
          "args": ["calculator.py"]
        }
      }
    }
    
  3. 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:

  1. endpoint field in the server config
  2. $MCP_ENDPOINT_<NAME> environment variable (name uppercased, non-alphanumeric → _)
  3. 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())

How it differs from the official mcp-calculator demo

mcp-calculator demo mcp2xiaozhi
Transports stdio directly; sse/http via mcp-proxy subprocess All three native via the mcp SDK
Framing Byte-pipe passthrough Protocol-level: parse → validate → re-serialize (SessionMessage aware)
Packaging Single script PyPI package, CLI, src layout
Reconnect Per-server backoff Per-bridge backoff with jitter + clean/abnormal close distinction
Multi-server All share one endpoint Per-server endpoints with conflict warnings
Error handling Raises + reconnects Malformed frames dropped; structured exception-group unwrapping

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mcp2xiaozhi-0.1.0.tar.gz (31.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mcp2xiaozhi-0.1.0-py3-none-any.whl (26.9 kB view details)

Uploaded Python 3

File details

Details for the file mcp2xiaozhi-0.1.0.tar.gz.

File metadata

  • Download URL: mcp2xiaozhi-0.1.0.tar.gz
  • Upload date:
  • Size: 31.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for mcp2xiaozhi-0.1.0.tar.gz
Algorithm Hash digest
SHA256 66e10f112a42ed09c4bb4fce1dc5d08d0a7f2a584d3020c723376f8ef466ff7f
MD5 e72c5cb7646b878acd3269e07fd632ad
BLAKE2b-256 bbe94019fd65b8a10fb262aa12790067b52e840eae73001e3ab7c9478f67f999

See more details on using hashes here.

Provenance

The following attestation bundles were made for mcp2xiaozhi-0.1.0.tar.gz:

Publisher: ci.yml on StanleyChanH/MCP2Xiaozhi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mcp2xiaozhi-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: mcp2xiaozhi-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 26.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for mcp2xiaozhi-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8ac0b0dfa1349cae2ce9d6d93e5bdc0965d165cc631313399147df135df9bf7c
MD5 a2eac91a5d0739dee98a2a2488ff2d20
BLAKE2b-256 1952fd471e3f99f138e27289c0021f2dfcff7d62648f4dffb1f0db9ac053a0c9

See more details on using hashes here.

Provenance

The following attestation bundles were made for mcp2xiaozhi-0.1.0-py3-none-any.whl:

Publisher: ci.yml on StanleyChanH/MCP2Xiaozhi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page