mcp-proxy
A config-driven Model Context Protocol (MCP) reverse proxy built in Rust. Aggregates multiple MCP backends behind a single endpoint with per-backend middleware, authentication, and observability.
Built on tower-mcp and the tower middleware ecosystem.
Features
Proxy
- Multi-backend proxy -- connect stdio and HTTP MCP servers behind one endpoint
- Capability filtering -- allow/deny lists for tools, resources, and prompts per backend
- Tool aliasing -- rename tools exposed by backends
- Argument injection -- merge default or per-tool arguments into tool calls
- Hot reload -- watch config file and add new backends without restart
- Library mode -- embed the proxy in your own Rust application
Resilience
- Timeout -- per-backend request timeouts
- Rate limiting -- per-backend request rate limits
- Concurrency limiting -- per-backend max concurrent requests
- Circuit breaker -- trip open on failure rate threshold
- Retry -- automatic retries with exponential backoff and optional budget
- Request hedging -- parallel redundant requests to reduce tail latency
- Outlier detection -- passive health checks that eject unhealthy backends
Traffic Management
- Traffic mirroring -- shadow traffic to a canary backend (fire-and-forget)
- Response caching -- per-backend TTL-based caching for tool calls and resource reads
- Request coalescing -- deduplicate identical concurrent requests
Security
- Bearer token auth -- static token validation
- JWT/JWKS auth -- token verification with RBAC (role-based access control)
- Token passthrough -- forward client auth tokens to backends
- Request validation -- argument size limits
Observability
- Prometheus metrics -- request counts and duration histograms
- OpenTelemetry tracing -- distributed trace export via OTLP
- Audit logging -- structured logging of all MCP requests
- Admin API -- health checks, backend status, cache stats
- Admin MCP tools -- introspection tools under
proxy/namespace
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.
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.1"
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 all features by default. 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 |
# 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.
License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.