mcp-proxy
A Tower-native MCP traffic plane for teams that need resilient, policy-aware MCP routing without adopting a full platform.
mcp-proxy is a config-driven Model Context Protocol reverse proxy. It aggregates stdio, HTTP, and WebSocket MCP backends behind one endpoint and applies authentication, per-backend resilience, traffic management, and observability as composable middleware from the tower ecosystem, via tower-mcp. Run it as a single binary or embed it in a Rust application.
Project status: maintained with an intentionally stable scope. mcp-proxy is a tower-native gateway and reference deployment for aggregating MCP backends. Maintenance focuses on security, dependency and protocol updates, bug fixes, and documentation rather than speculative new features.
Where it fits
- Self-hosted internal MCP fleets -- one authenticated, observable endpoint in front of the MCP servers a team already runs.
- Non-Kubernetes and mixed deployments -- a single binary and a TOML file; no service mesh, operator, or container platform required.
- Rust applications that need a gateway -- the same proxy is a library; mount it in an existing axum app or drive it from a builder.
For how mcp-proxy relates to other MCP gateways (Docker MCP Gateway, IBM ContextForge, Microsoft MCP Gateway, Kong), see docs/comparison.md.
What it does
Aggregates many MCP servers behind one endpoint. Backends speaking stdio, HTTP, or WebSocket are exposed under per-backend namespaces at a single HTTP endpoint. Tools, resources, and prompts can be allow/deny filtered and aliased per backend; default or per-tool arguments can be injected into calls; composite tools fan one call out across multiple backend tools; hot reload adds new backends from config changes without a restart; and optional BM25 discovery can expose search instead of full tool lists.
Contains backend failures. Each backend gets its own resilience chain: timeouts, rate limits, concurrency caps, retries with exponential backoff and budgets, circuit breakers, request hedging, and outlier detection that temporarily ejects unhealthy backends.
Manages traffic for rollouts and load. Traffic mirroring shadows a percentage of requests to a canary backend; canary routing and ordered failover control weighted rollouts; response caching (in-memory, Redis, or SQLite) and request coalescing cut duplicate work.
Applies policy at the front door. Bearer token, JWT/JWKS, and OAuth 2.1 authentication; role-based tool visibility (RBAC); token passthrough to backends; and request argument validation.
Reports what is happening. Prometheus metrics, OpenTelemetry trace export, structured audit logging, an admin HTTP API for health, backend status, and cache stats, and admin MCP tools under the proxy/ namespace.
Every option is documented in config.example.toml, deployment shapes in docs/architectures.md, and runnable configurations in examples/.
Installation
Homebrew
Cargo
Docker
Pre-built binaries
Download from GitHub Releases.
Quick Start
Create a proxy.toml:
[]
= "my-proxy"
= "/"
[]
= "127.0.0.1"
= 8080
[[]]
= "files"
= "stdio"
= "npx"
= ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
Run:
All tools from the filesystem server are now available under the files/ namespace at http://127.0.0.1:8080/mcp.
Configuration
See config.example.toml for the full configuration reference with all options documented.
For a complete production example (ten backends behind one endpoint, with JWT/RBAC, per-backend resilience, a mirrored canary, selective caching, and metrics), see docs/production-gateway.md and examples/production-gateway.toml.
Per-backend middleware
[[]]
= "github"
= "stdio"
= "npx"
= ["-y", "@modelcontextprotocol/server-github"]
[]
= "${GITHUB_TOKEN}"
[]
= 60
[]
= 30
= 1
[]
= 0.5
= 5
= 30
[]
= 3
= 100
= 5000
= 20.0
[]
= 200
= 1
[]
= 5
= 30
= 50
[]
= 60
= 300
Argument injection
[[]]
= "db"
= "http"
= "http://db.internal:8080"
# Inject into all tool calls for this backend
[]
= 30
# Inject into a specific tool (overrides default_args for matching keys)
[[]]
= "query"
= { = true, = 1000 }
# Force overwrite existing arguments
[[]]
= "dangerous_op"
= { = true }
= true
Traffic mirroring
[[]]
= "api"
= "http"
= "http://api-v1:8080"
[[]]
= "api-v2"
= "http"
= "http://api-v2:8080"
= "api"
= 10
Authentication
# Bearer token
[]
= "bearer"
= ["my-secret-token"]
# Or JWT with RBAC
[]
= "jwt"
= "https://auth.example.com"
= "mcp-proxy"
= "https://auth.example.com/.well-known/jwks.json"
[[]]
= "reader"
= ["files/read_file", "files/list_directory"]
[[]]
= "admin"
[]
= "scope"
= { = "reader", = "admin" }
Capability filtering
[[]]
= "files"
= "stdio"
= "npx"
= ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
# Only expose these tools
= ["read_file", "list_directory"]
# Or hide specific tools
# hide_tools = ["write_file", "delete_file"]
Library Usage
Add to your Cargo.toml:
[]
= "0.4"
use ;
let config = load?;
let proxy = from_config.await?;
// Embed in an existing axum app
let = proxy.into_router;
// Or serve standalone
proxy.serve.await?;
Admin API
HTTP endpoints:
GET /admin/backends-- list backends with health status and proxy infoGET /admin/health-- health check summary (healthy/degraded)GET /admin/metrics-- Prometheus metricsGET /admin/cache/stats-- per-backend cache hit/miss ratesPOST /admin/cache/clear-- clear all caches
MCP tools (under proxy/ namespace):
proxy/list_backends-- list backends with health statusproxy/health_check-- cached health check resultsproxy/session_count-- active session countproxy/add_backend-- dynamically add an HTTP backendproxy/config-- dump current config
Architecture
Client
|
v
[Auth] -> [Audit] -> [Metrics] -> [Token Passthrough] -> [RBAC]
-> [Alias] -> [Filter] -> [Validation] -> [Coalesce] -> [Cache]
-> [Mirror] -> [Inject Args]
-> McpProxy
|
v (per-backend)
[Retry] -> [Hedge] -> [Concurrency] -> [Rate Limit]
-> [Timeout] -> [Circuit Breaker] -> [Outlier Detection]
-> Backend
Global middleware wraps the entire proxy. Per-backend middleware is applied individually to each backend connection. All middleware is built with tower Service layers.
Feature Flags
Pre-built binaries and cargo install include the default features. If you're building from source and don't need everything, you can disable optional features for a smaller binary:
| Feature | Default | What it includes |
|---|---|---|
otel |
yes | OpenTelemetry distributed tracing (OTLP export) |
metrics |
yes | Prometheus metrics and /admin/metrics endpoint |
oauth |
yes | JWT/JWKS auth, RBAC, and token passthrough |
openapi |
yes | OpenAPI schema and endpoint support |
websocket |
yes | WebSocket backend transport |
discovery |
yes | BM25 tool discovery and search exposure mode |
yaml |
yes | YAML configuration files |
skills |
yes | agentskills.io prompts for proxy administration |
redis-cache |
no | Shared Redis response cache |
sqlite-cache |
no | Persistent SQLite response cache |
protocol-2026-07-28 |
no | Released MCP 2026-07-28 protocol support through tower-mcp |
# Minimal build (bearer auth only, no metrics/tracing/JWT)
# Just metrics, no otel or JWT
Config parsing always works regardless of features -- if you reference a disabled feature in your config (e.g., type = "jwt" without the oauth feature), you'll get a clear error at startup.
The default protocol baseline remains MCP 2025-11-25. Build with
--features protocol-2026-07-28 to compile support for the released,
sessionless 2026-07-28 protocol. Continuation fields such as inputResponses
and requestState are preserved through proxy routing in either build.
License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.