mcp-mux
A dynamic MCP server orchestrator and multiplexer that routes requests to multiple remote and local sub-MCP servers, with hot-reload config, SSE/Streamable HTTP support, and a summary endpoint to reduce context token usage.
README
🔀 MCP Mux
Dynamic Multi-Endpoint Python Model Context Protocol (MCP) Router & Orchestrator
mcp-mux is a dynamic MCP server orchestrator and multiplexer. It acts as a central proxy to multiple remote and local sub-MCP servers, monitoring a config.yaml file to hot-reload endpoints live without server restarts. It supports Server-Sent Events (SSE) and Streamable HTTP backend transports, including a local SSE bridge for clients that need an SSE endpoint while the upstream server speaks Streamable HTTP. It also provides a lightweight /summary endpoint to minimize context token flooding for AI agents.
🌟 Key Features
- 🔄 Dynamic Hot-Reloading: Reloads endpoint configuration in place and uses one shared filesystem observer to invalidate only managed backends whose watched source trees changed.
- 🚀 Flexible Sub-Server Modes:
- Remote: Seamless proxying to external HTTP MCP endpoints.
- Managed CLI: Native spawning of command-line tools (e.g., via
npxoruvx).
- 🧩 Configurable Request Headers: Adds endpoint-specific upstream headers, including tokens loaded from environment variables.
- 🌉 Optional Local SSE-to-Streamable-HTTP Bridge: Streamable HTTP endpoints are proxied as Streamable HTTP by default. Endpoints can opt into a legacy SSE compatibility bridge when traditional SSE clients need a local
event: endpointflow. - ⚡ Automatic Transport Auto-Detection: Dynamically detects the backend transport mode (
streamable-httpvssse) based on URL paths. Sub-servers with/mcpor/mcp/in their URL automatically default tostreamable-http. - 🛡️ Session Propagation & Isolation: For opt-in legacy bridge sessions, tracks local sessions per endpoint, maps upstream
Mcp-Session-Idvalues to the correct local session, and rejects cross-endpoint session reuse. - 🤝 Streamable HTTP Client Compatibility: Normalizes upstream
Acceptheaders for Streamable HTTP POST/DELETE requests and fills in missing JSON-RPC"jsonrpc": "2.0"fields for request bodies that otherwise look like JSON-RPC messages. - 🧼 Decoded Response Header Safety: Strips stale
Content-Encodingand upstreamContent-Lengthheaders when the router reads and rebuilds JSON responses. - 📊 Token-Saving Metadata Endpoint: Registers a custom
/summaryroute returning only namespaces and descriptions, shielding AI clients from schema bloat. - 🧹 Clean Subprocess Lifecycle: The manager isolates background subprocesses inside unique Unix process groups (
os.setsid) to guarantee no zombie processes are left behind on teardown.
📐 Architecture
graph TD
A[main.py - Uvicorn/Starlette Server] --> B[config_loader.py - ConfigWatcher]
B -->|Watches & Parses| C[config.yaml]
A --> J[source_watcher.py - Shared Observer]
J -->|Debounce and invalidate| E
A --> D[process_manager.py - ProcessManager]
D -->|Spawns / Cleans up| E[Managed Subprocesses: uvx / npx]
A --> F[server.py - MCPRouter]
F -->|Proxy SSE| G[Remote SSE Servers]
F -->|Proxy Streamable HTTP & map sessions| H[Streamable HTTP Servers]
A --> I[summary route]
⚙️ Configuration (config.yaml)
Define your endpoints in mcp_router/config.yaml. Here is an example layout:
endpoints:
- path: "web-search"
mode: "remote"
url: "https://mcp.garion.us/mcp"
summary: "Google Search and content extraction tool"
# transport: "streamable-http" (Automatically detected due to /mcp path suffix)
allowed_tools:
- "google_search"
- "batch_extract_urls"
- path: "firecrawl"
mode: "managed_cli"
command: "export NVM_DIR=$HOME/.config/nvm && [ -s $NVM_DIR/nvm.sh ] && . $NVM_DIR/nvm.sh && HTTP_STREAMABLE_SERVER=true PORT=3033 HOST=localhost FIRECRAWL_API_URL=http://garion.us:3002 npx --yes firecrawl-mcp"
url: "http://localhost:3033/mcp"
summary: "Firecrawl Web Content Extraction Tool"
timeout: 300 # Automatically shuts down after 300 seconds of inactivity
watch:
paths:
- "/absolute/path/to/firecrawl-mcp/src"
debounce_ms: 400
allowed_tools:
- "firecrawl_search"
- "firecrawl_scrape"
- path: "huggingface"
mode: "remote"
url: "https://huggingface.co/mcp"
summary: "HuggingFace MCP Server — model search, hub browsing, model download"
headers:
Authorization: "Bearer ${HF_TOKEN:-}"
Environment Variable Expansion
Configuration values support shell-style environment references before validation:
${NAME}expands to the required environment variableNAMEand fails config loading if it is missing.${NAME:-fallback}expands toNAMEwhen set, otherwise tofallback.- Empty
Authorization: "Bearer ${HF_TOKEN:-}"values are omitted, allowing endpoints such as Hugging Face to fall back to anonymous access. - If
HF_TOKENis accidentally set toBearer hf_..., the loader normalizesBearer Bearer hf_...toBearer hf_....
Configuration Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
String | Yes | Unique namespace/route for the sub-server. |
mode |
String | Yes | Spawning mode. remote and managed_cli are currently handled by the router. |
url |
String | Yes (for remote/managed) | Target endpoint URL. |
command |
String | Yes (for managed) | Command string to spawn the local server process. |
summary |
String | Yes | Brief description of the sub-server, returned by /summary. |
timeout |
Integer | No | Inactivity timeout in seconds for CLI mode (defaults to 300). |
transport |
String | No | Transport mode (sse or streamable-http). Automatically detected if omitted. |
legacy_sse_bridge |
Boolean | No | For streamable-http endpoints only. Defaults to false; set to true to expose the local legacy SSE bridge instead of preserving upstream GET+SSE behavior. |
headers |
Mapping | No | Extra request headers forwarded upstream after environment expansion. |
allowed_tools |
List of Strings | No | Allowlist of tool names. Only these tools are exposed. |
denied_tools |
List of Strings | No | Denylist of tool names. These tools are excluded. (Ignored if allowed_tools is set). |
watch.paths |
List of Strings | No | Absolute, existing source directories that invalidate this managed endpoint when files change. Managed CLI endpoints only. |
watch.debounce_ms |
Integer | No | Per-endpoint change coalescing window from 50 through 60,000 milliseconds. Defaults to 400. |
watch.ignore_patterns |
List of Strings | No | Filename or relative-path glob patterns ignored by the watcher. Defaults to Python bytecode, editor swap/temporary files, and backup files. |
Managed Backend Source Invalidation
watch provides selective development-time reload behavior without restarting the
mux or unrelated backends:
endpoints:
- path: "gh"
mode: "managed_cli"
command: >-
cd /workspace/mcp_servers/gh_mcp &&
MCP_GH_TRANSPORT=streamable-http
MCP_GH_HTTP_PORT=8768
uv run mcp-gh
url: "http://localhost:8768/mcp"
summary: "GitHub CLI MCP server"
timeout: 300
transport: "streamable-http"
watch:
paths:
- "/workspace/mcp_servers/gh_mcp/src"
debounce_ms: 400
The mux creates one watchdog Observer for all configured endpoints and schedules
each unique source directory only once. A file create, delete, modification, or
atomic move is mapped back to every endpoint that watches that path. Changes under
version-control metadata, virtual environments, dependency directories, and common
tool caches are always ignored. ignore_patterns provides additional
endpoint-specific exclusions.
Events are transferred from watchdog's thread to the mux event loop and coalesced per endpoint. When the debounce window expires, the mux:
- acquires the endpoint's existing startup lock;
- verifies that the current configuration still watches the endpoint;
- discards its local bridge sessions and inactivity timestamp;
- terminates its complete managed process group; and
- leaves the endpoint stopped.
The next request follows the normal on-demand activation path and starts the backend from its revised source. An endpoint that was already stopped is not started merely because a file changed. Shared source directories may invalidate multiple explicitly configured endpoints, while unrelated managed and remote endpoints remain untouched.
Watch paths must be absolute, existing directories. Filesystem roots are rejected to
prevent accidentally monitoring an entire host. To include dependency manifests or
other files outside src, watch a suitably narrow common directory and add
ignore_patterns for generated content. Do not watch .venv, node_modules, build
output, logs, or other high-churn trees.
Invalidation intentionally interrupts active calls to the affected backend. Clients must retry or reinitialize according to the backend transport; stateless Streamable HTTP backends provide the cleanest behavior. This mechanism reloads backend code but does not force external clients such as ChatGPT to rediscover a cached MCP tool schema.
Streamable HTTP Bridge Behavior
For streamable-http endpoints, the mux preserves the original transport by default:
POST /<path>forwards JSON-RPC messages to the upstream MCP endpoint.GET /<path>withAccept: text/event-streamforwards to the upstream MCP endpoint and preserves the upstream response status and stream.
To expose the legacy local SSE bridge, set legacy_sse_bridge: true on that endpoint. SSE clients can then connect with:
curl -N -H 'Accept: text/event-stream' http://127.0.0.1:8012/huggingface
The first SSE event contains a local POST endpoint such as:
event: endpoint
data: /huggingface?session_id=<local-session-id>
Client POSTs to that local URL are forwarded upstream. If the upstream returns Mcp-Session-Id, the router stores it on the local bridge session and forwards it on later POSTs for the same endpoint. Sessions are removed when the local SSE stream closes or when their endpoint is removed or changed during config reload.
🚀 Getting Started
Prerequisites
Make sure you have uv installed.
1. Installation & Setup
Clone the repository and install all dependencies:
# Activate virtual environment
source .venv/bin/activate
# Install & sync dependencies
uv sync
2. Running the Orchestrator
Start the main router server (default port is 8012):
export HF_TOKEN=hf_xxx # optional; omit for anonymous Hugging Face access
uv run python main.py --port 8012
3. Querying Endpoint Summary
You can check active routes and summaries by visiting:
curl http://127.0.0.1:8012/summary
🧪 Testing
The project is fully tested using pytest and pytest-asyncio. To execute unit tests:
uv run pytest
Current verification state: 35 passed.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。