mcpstead
MCP Gateway that aggregates multiple upstream MCP servers into a single endpoint with persistent connections, tool registry, and authentication.
README
mcpstead
MCP Gateway
One downstream /mcp endpoint that fronts many upstream MCP servers. Persistent upstream connections with reconnect, tool registry with qualified names, JSON or SSE responses based on client Accept, per-upstream auth, Prometheus metrics.
Install
# npm (macOS, Linux, WSL)
npm i -g @ahkohd/mcpstead
# homebrew (macOS, Linux)
brew install ahkohd/tap/mcpstead
# cargo
cargo install mcpstead --locked --force
# verify
mcpstead --version
Quick start
# 1. write a config
mkdir -p ~/.config/mcpstead
cat > ~/.config/mcpstead/config.yaml <<'EOF'
host: 0.0.0.0
port: 8766
mcp:
auth:
mode: none
servers:
- name: example
url: http://127.0.0.1:3000/mcp
protocol: streamable
auth: none
EOF
# 2. run
mcpstead --config ~/.config/mcpstead/config.yaml
Then point any MCP client at http://127.0.0.1:8766/mcp.
Docker
docker build -t mcpstead .
docker run --rm \
-p 8766:8766 \
-v "$PWD/config:/etc/mcpstead:ro" \
mcpstead
The Dockerfile installs from crates.io.
HTTP API
| Method | Path | Purpose |
|---|---|---|
POST |
/mcp |
MCP over HTTP JSON-RPC |
GET |
/mcp |
returns 405 |
DELETE |
/mcp |
terminate downstream session |
GET |
/health |
upstream status, tool counts, last-seen, reconnects |
GET |
/metrics |
Prometheus text format |
POST |
/-/reload |
reload config without restart |
MCP client setup
Local, no-auth:
mcpstead:
url: http://127.0.0.1:8766/mcp
tools:
resources: false
prompts: false
Bearer auth:
mcpstead:
url: http://127.0.0.1:8766/mcp
headers:
Authorization: Bearer ${MCPSTEAD_BEARER_TOKEN}
tools:
resources: false
prompts: false
Tools appear with qualified names - <server>__<tool> - so multiple upstreams can ship overlapping tool names without collision.
Auth
Downstream (clients to mcpstead)
Default is no auth:
mcp:
auth:
mode: none
Bearer auth:
mcp:
auth:
mode: bearer
Token comes from env:
export MCPSTEAD_BEARER_TOKEN='replace-with-strong-secret'
mcpstead --config ~/.config/mcpstead/config.yaml
Clients send Authorization: Bearer <token>. Missing or wrong token returns 401. mcp.auth.bearer_token in config is rejected at startup.
/health and /metrics stay open regardless of auth mode - for monitoring without exposing the token.
Upstream (mcpstead to MCP servers)
Per-server in the servers list. Three modes:
servers:
- name: local
url: http://127.0.0.1:3000/mcp
auth: none
- name: workflow
url: https://workflow.example/mcp-server/http
auth:
type: bearer
token_env: WORKFLOW_TOKEN
- name: custom
url: https://api.example/mcp
headers:
X-API-Key: '${EXAMPLE_KEY}'
token_env resolves the named env var at startup and on config reload.
Config
Set the config path with --config <path> or the MCPSTEAD_CONFIG env var.
host: 0.0.0.0
port: 8766
mcp:
auth:
mode: none # none | bearer
session:
idle_ttl_seconds: 3600
gc_interval_seconds: 60
shutdown_grace_seconds: 5
servers:
- name: local
url: http://127.0.0.1:3000/mcp
protocol: streamable # streamable | sse | auto
required: false # if true, gateway won't start without this upstream
auth: none
reconnect:
max_attempts: 0 # 0 = infinite
backoff_base_ms: 1000
backoff_max_ms: 30000
tools:
ttl_seconds: 300
tls_skip_verify: false
quirks:
normalize_sse_events: true
inject_accept_header: 'application/json, text/event-stream'
metrics:
enabled: true
logging:
level: info
Hot reload
mcpstead reloads its config without restart on:
- SIGHUP (
systemctl reload mcpsteadorkill -HUP <pid>) POST /-/reload(gated by bearer auth in bearer mode)
Hot-reloadable:
- upstream list
- per-upstream auth, headers, quirks, reconnect, tools, URL, protocol, and TLS settings
mcp.auth.modeandMCPSTEAD_BEARER_TOKENmetrics.enabled
Restart required:
hostportlogging.levelmcp.session.*
Reload is best-effort. Bad config is rejected and ignored; the running config stays in place. Check logs and mcpstead_config_reloads_total{result="error"} for failures.
MCP session config keys
mcp.session.idle_ttl_seconds- evict inactive sessions after this many seconds (default3600)mcp.session.gc_interval_seconds- idle-session GC wake interval (default60)mcp.session.shutdown_grace_seconds- max shutdown accounting sweep time (default5)
Per-upstream config keys
name- required, used as tool prefixurl- required, MCP endpointprotocol-streamable | sse | auto(defaultauto)required- block startup if upstream fails to initialize (defaultfalse)auth-none,bearer(withtoken_env), orheadersmapreconnect.max_attempts-0= infinite (default)reconnect.backoff_base_ms/backoff_max_ms- exponential backoff boundstools.ttl_seconds- refresh cachedtools/listafter this intervaltls_skip_verify- disable TLS cert checks for this upstream (defaultfalse, use only for trusted local nets)quirks.normalize_sse_events- stripevent:lines from upstream SSE responsesquirks.inject_accept_header- override the Accept header sent upstream
Observability
Metrics
/metrics exposes Prometheus-format counters, gauges, and histograms. Label cardinality assumes a small bounded set of upstreams and tools; tool-call series are keyed by (server, tool).
mcpstead_build_info{version="...",rust_version="...",git_sha="..."}
mcpstead_start_time_seconds
mcpstead_uptime_seconds
mcpstead_process_resident_memory_bytes
mcpstead_process_virtual_memory_bytes
mcpstead_process_cpu_seconds_total
mcpstead_process_open_fds
mcpstead_process_max_fds
mcpstead_process_threads
mcpstead_upstream_connected{server="..."}
mcpstead_upstream_tools_count{server="..."}
mcpstead_upstream_reconnects_total{server="..."}
mcpstead_upstream_last_seen_seconds{server="..."}
mcpstead_upstream_initialize_total{server="...",result="success|error"}
mcpstead_upstream_initialize_duration_seconds_bucket{server="...",le="..."}
mcpstead_upstream_health_checks_total{server="...",result="success|failure"}
mcpstead_upstream_reconnect_attempts_total{server="...",result="success|error"}
mcpstead_upstream_backoff_seconds_total{server="..."}
mcpstead_upstream_in_backoff{server="..."}
mcpstead_upstream_current_backoff_seconds{server="..."}
mcpstead_upstream_session_resets_total{server="...",reason="unknown_session|expired|terminated"}
mcpstead_upstream_tools_refresh_total{server="...",result="success|error"}
mcpstead_upstream_tools_refresh_duration_seconds_bucket{server="...",le="..."}
mcpstead_upstream_tools_last_refresh_timestamp_seconds{server="..."}
mcpstead_upstream_bytes_total{server="...",direction="sent|received"}
mcpstead_downstream_sessions_active
mcpstead_downstream_sessions_total
mcpstead_downstream_session_duration_seconds_bucket{le="..."}
mcpstead_downstream_session_terminations_total{reason="..."}
mcpstead_mcp_requests_total{method="...",result="success|error"}
mcpstead_mcp_request_duration_seconds_bucket{method="...",le="..."}
mcpstead_mcp_auth_attempts_total{result="success|failure"}
mcpstead_mcp_auth_failures_total{reason="..."}
mcpstead_config_reloads_total{result="success|error"}
mcpstead_config_last_reload_timestamp_seconds
mcpstead_tool_calls_total{server="...",tool="..."}
mcpstead_tool_call_errors_total{server="...",tool="...",reason="..."}
mcpstead_tool_call_duration_seconds_bucket{server="...",tool="...",le="..."}
Scrape config
- job_name: mcpstead
metrics_path: /metrics
static_configs:
- targets: ['mcpstead:8766']
Health check
curl http://127.0.0.1:8766/health
Returns JSON: per-upstream connection state, tool counts, last successful contact, reconnect counts, last error.
Troubleshooting
- All tools list empty - at least one upstream failed to
initialize. Check/healthfor per-server status; check upstream URL, auth, and reachability. - Sporadic
SSE parse failed- upstream sends an SSE dialect mcpstead doesn't recognize. Tryquirks.normalize_sse_events: truefor that server, or setquirks.inject_accept_header: 'application/json'to force JSON. tools/callreturns auth error - upstream rejected the bearer token. Confirmtoken_envresolves to the right value at startup; check upstream's expected header name.- TLS handshake errors against a self-signed upstream - set
tls_skip_verify: trueon that server. Only safe on trusted local networks. - 401 from
/mcpwith mode bearer - client missing or sending wrongAuthorization: Bearer <token>. VerifyMCPSTEAD_BEARER_TOKENmatches what the client sends. - Upstream goes red in
/healthrepeatedly - checkmcpstead_upstream_reconnects_totalandmcpstead_upstream_last_seen_seconds. Tunereconnect.backoff_max_msif the upstream needs longer recovery.
推荐服务器
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 模型以安全和受控的方式获取实时的网络信息。