mcpstead

mcpstead

MCP Gateway that aggregates multiple upstream MCP servers into a single endpoint with persistent connections, tool registry, and authentication.

Category
Visit Server

README

mcpstead

CI npm version Crates.io License

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 mcpstead or kill -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.mode and MCPSTEAD_BEARER_TOKEN
  • metrics.enabled

Restart required:

  • host
  • port
  • logging.level
  • mcp.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 (default 3600)
  • mcp.session.gc_interval_seconds - idle-session GC wake interval (default 60)
  • mcp.session.shutdown_grace_seconds - max shutdown accounting sweep time (default 5)

Per-upstream config keys

  • name - required, used as tool prefix
  • url - required, MCP endpoint
  • protocol - streamable | sse | auto (default auto)
  • required - block startup if upstream fails to initialize (default false)
  • auth - none, bearer (with token_env), or headers map
  • reconnect.max_attempts - 0 = infinite (default)
  • reconnect.backoff_base_ms / backoff_max_ms - exponential backoff bounds
  • tools.ttl_seconds - refresh cached tools/list after this interval
  • tls_skip_verify - disable TLS cert checks for this upstream (default false, use only for trusted local nets)
  • quirks.normalize_sse_events - strip event: lines from upstream SSE responses
  • quirks.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 /health for per-server status; check upstream URL, auth, and reachability.
  • Sporadic SSE parse failed - upstream sends an SSE dialect mcpstead doesn't recognize. Try quirks.normalize_sse_events: true for that server, or set quirks.inject_accept_header: 'application/json' to force JSON.
  • tools/call returns auth error - upstream rejected the bearer token. Confirm token_env resolves to the right value at startup; check upstream's expected header name.
  • TLS handshake errors against a self-signed upstream - set tls_skip_verify: true on that server. Only safe on trusted local networks.
  • 401 from /mcp with mode bearer - client missing or sending wrong Authorization: Bearer <token>. Verify MCPSTEAD_BEARER_TOKEN matches what the client sends.
  • Upstream goes red in /health repeatedly - check mcpstead_upstream_reconnects_total and mcpstead_upstream_last_seen_seconds. Tune reconnect.backoff_max_ms if the upstream needs longer recovery.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured