One endpoint. Every AI tool. Single binary.
A Rust gateway that orchestrates all your MCP servers behind one HTTP endpoint. Works with any MCP client — Claude, GPT, Gemini, Cursor, Windsurf, or your own.
Quick Start · How It Works · Configuration · REST API · Docs · Contributing
You have 20 MCP servers. Your AI agent spawns all of them at startup — 20 child processes, 20 handshakes, half of them timeout. Your config is 500 lines of JSON. Adding a new MCP means editing config, restarting your agent, and praying nothing breaks.
MCP Conductor is a Rust gateway that spawns and manages all your MCP backends, exposing them through one Streamable HTTP endpoint. Any MCP-compatible client connects to a single URL and gets every tool from every backend.
Before: AI Agent → 20 stdio processes (fragile, slow startup, config hell)
After: AI Agent → MCP Conductor (one HTTP URL) → 20 backends (managed, recoverable)
- ~700 lines of Rust — no runtime, no Docker, no dependencies
- Model-agnostic — any MCP client works (Claude Code, OpenAI agents, Cursor, custom apps)
- Concurrent startup — all backends connect in parallel with 30s timeout each
- Hot reconnect — restart a crashed backend without restarting anything else
- REST API — call any MCP tool from scripts, webhooks, or other services
- TOML config — readable, diffable, no more JSON arrays
MCP Conductor implements the Streamable HTTP transport from the MCP specification. Any client that supports MCP over HTTP can connect:
| Client | Config |
|---|---|
| Claude Code | { "type": "http", "url": "http://localhost:9090/mcp" } |
| Claude Desktop | { "transport": "http", "url": "http://localhost:9090/mcp" } |
| Cursor | Add as HTTP MCP server: http://localhost:9090/mcp |
| Windsurf | Add as MCP endpoint in settings |
| OpenAI Agents SDK | Connect via MCP client library |
| Custom apps | Any MCP client library (Python, TypeScript, Rust, Go) |
| REST/curl | POST /api/tools/call — no MCP client needed |
# Build from source
git clone https://github.com/pdaxt/mcp-conductor.git
cd mcp-conductor
cargo install --path crates/mcp-gateway
# Create config
cat > conductor.toml << 'EOF'
[server]
host = "127.0.0.1"
port = 9090
[backends.search]
transport = "stdio"
command = "search-mcp"
[backends.secrets]
transport = "stdio"
command = "pqvault-unified"
[backends.remote-api]
transport = "http"
url = "http://remote-server:8080/mcp"
EOF
# Run
mcp-conductor conductor.tomlPoint any MCP client at http://localhost:9090/mcp. That's it.
{
"mcpServers": {
"conductor": {
"type": "http",
"url": "http://localhost:9090/mcp"
}
}
}One line replaces your entire MCP config.
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
async with streamablehttp_client("http://localhost:9090/mcp") as (r, w, _):
async with ClientSession(r, w) as session:
await session.initialize()
tools = await session.list_tools()
result = await session.call_tool("search", {"query": "rust mcp"})Any MCP Client ──HTTP──▸ MCP Conductor (:9090/mcp)
│
├── spawns ──▸ search-mcp (stdio) 12 tools
├── spawns ──▸ secrets-mcp (stdio) 14 tools
├── spawns ──▸ crm-mcp (stdio) 35 tools
├── spawns ──▸ email-mcp (stdio) 10 tools
├── connects ▸ remote-api (HTTP) 50 tools
└── ...
All tools appear as one flat list.
Agent calls any tool → Conductor routes to the right backend.
- Load
conductor.toml— parse all backend configs - Spawn all stdio backends concurrently (30s timeout per backend)
- Discover tools from each backend via MCP
list_tools - Serve Streamable HTTP on
/mcp+ REST API on/api/*
One slow backend doesn't block the others. If a backend fails to connect, the rest still work.
When an agent calls a tool:
- Conductor searches all connected backends for the tool name
- Routes the call to the correct backend
- Returns the result
No prefixing, no namespacing — tools appear exactly as they do on the backend.
[server]
host = "127.0.0.1" # Listen address
port = 9090 # Listen port
timeout_secs = 120 # Request timeout
api_key = "secret" # Optional: require X-API-Key header on /api/*
# Stdio backend — Conductor spawns and manages the process
[backends.search]
transport = "stdio"
command = "/usr/local/bin/search-mcp"
args = ["--cache-ttl", "3600"]
cwd = "/tmp"
[backends.search.env]
BRAVE_API_KEY = "your-key"
# HTTP backend — Conductor connects to an already-running MCP server
[backends.remote]
transport = "http"
url = "http://10.0.0.5:8080/mcp"
# Webhook → tool call mapping
[webhooks.stripe_payment]
backend = "billing"
tool = "handle_payment"Full reference: docs/CONFIGURATION.md
Beyond the MCP endpoint, Conductor exposes a REST API for non-MCP clients:
| Method | Path | Description |
|---|---|---|
GET |
/health |
Backend status — connected count, degraded/healthy |
GET |
/api/tools |
List all tools from all backends |
GET |
/api/backends/{name}/tools |
List tools from one backend |
POST |
/api/tools/call |
Call any tool (auto-routed) |
POST |
/api/backends/{name}/tools/{tool} |
Call tool on specific backend |
POST |
/api/backends/{name}/reconnect |
Hot-reconnect a backend |
POST |
/api/webhooks/{hook} |
Webhook → tool call |
# Health check
curl http://localhost:9090/health
# {"status":"healthy","backends_connected":20,"backends_configured":20}
# List all tools
curl http://localhost:9090/api/tools | jq '.count'
# 1458
# Call a tool
curl -X POST http://localhost:9090/api/tools/call \
-H 'Content-Type: application/json' \
-d '{"tool": "search", "arguments": {"query": "rust mcp server"}}'
# Reconnect a crashed backend
curl -X POST http://localhost:9090/api/backends/search/reconnect
# {"status":"reconnected","backend":"search"}| Alternative | Stars | Language | Conductor Difference |
|---|---|---|---|
| docker/mcp-gateway | 1.3K | Go | Docker plugin — requires Docker. Conductor is standalone. |
| microsoft/mcp-gateway | 514 | TypeScript | Enterprise auth/RBAC focus. Conductor is minimal and fast. |
| MCPJungle | 896 | TypeScript | Node.js runtime. Conductor is a single static binary. |
| Unla | 2K | Go | Proxy-only (HTTP→HTTP). Conductor spawns stdio backends. |
Conductor's unique value: It manages the full lifecycle of stdio MCP servers — spawn, monitor, reconnect — not just proxy HTTP. Most MCP servers use stdio. Conductor is the only Rust gateway that handles them natively.
crates/mcp-gateway/src/
├── main.rs 113 lines Axum server + MCP Streamable HTTP
├── pool.rs 221 lines Backend lifecycle (spawn, connect, reconnect)
├── config.rs 105 lines TOML config parsing
├── proxy.rs 69 lines MCP ServerHandler → routes to pool
├── routes.rs 159 lines REST API endpoints
└── middleware.rs 55 lines Auth + request logging
─────────
~700 lines total
Built on:
- rmcp — Official Rust MCP SDK (Streamable HTTP server + stdio client)
- axum — Rust web framework
- tokio — Async runtime
- dashmap — Concurrent HashMap for backend pool
- Stdio backend management (spawn, connect, reconnect)
- HTTP backend support (connect to remote MCP servers)
- Streamable HTTP MCP endpoint
- REST API for scripts and webhooks
- Concurrent startup with per-backend timeout
- Hot reconnect without restart
- Dashboard UI (web-based backend monitoring)
- Tool-level metrics (latency, error rates, call counts)
- Config hot-reload (watch config for changes)
- Backend health checks (periodic ping, auto-reconnect)
- Tool namespacing (optional
backend.toolprefix to avoid collisions) - SSE backend transport support
cargo test
cargo clippy -- -D warnings
cargo fmt --checkPRs welcome. The codebase is ~700 lines — you can read the whole thing in 10 minutes.
| Doc | Description |
|---|---|
| Getting Started | Install, configure, and run in 2 minutes |
| Configuration | Full config reference with examples |
| Architecture | How it works internally, design decisions |
MIT