mcp-proxy
Health Warn
- License — License: Apache-2.0
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 7 GitHub stars
Code Pass
- Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
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.
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.
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.
Features
Proxy
- Multi-backend proxy -- connect stdio, HTTP, and WebSocket 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
- Tool discovery -- BM25 search and an optional search-only exposure mode
- Composite tools -- fan out one tool call across multiple backend tools
- 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)
- Canary routing and failover -- weighted rollouts and ordered fallback backends
- Response caching -- in-memory caching with optional Redis or SQLite storage
- Request coalescing -- deduplicate identical concurrent requests
Security
- Bearer token auth -- static token validation
- JWT/JWKS auth -- token verification with RBAC (role-based access control)
- OAuth 2.1 auth -- metadata discovery and token introspection
- 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
brew install joshrotenberg/brew/mcp-proxy
Cargo
cargo install mcp-proxy
Docker
docker pull ghcr.io/joshrotenberg/mcp-proxy:latest
docker run -v ./proxy.toml:/etc/mcp-proxy/proxy.toml:ro -p 8080:8080 ghcr.io/joshrotenberg/mcp-proxy:latest
Pre-built binaries
Download from GitHub Releases.
Quick Start
Create a proxy.toml:
[proxy]
name = "my-proxy"
separator = "/"
[proxy.listen]
host = "127.0.0.1"
port = 8080
[[backends]]
name = "files"
transport = "stdio"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
Run:
mcp-proxy --config proxy.toml
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
[[backends]]
name = "github"
transport = "stdio"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-github"]
[backends.env]
GITHUB_PERSONAL_ACCESS_TOKEN = "${GITHUB_TOKEN}"
[backends.timeout]
seconds = 60
[backends.rate_limit]
requests = 30
period_seconds = 1
[backends.circuit_breaker]
failure_rate_threshold = 0.5
minimum_calls = 5
wait_duration_seconds = 30
[backends.retry]
max_retries = 3
initial_backoff_ms = 100
max_backoff_ms = 5000
budget_percent = 20.0
[backends.hedging]
delay_ms = 200
max_hedges = 1
[backends.outlier_detection]
consecutive_errors = 5
base_ejection_seconds = 30
max_ejection_percent = 50
[backends.cache]
tool_ttl_seconds = 60
resource_ttl_seconds = 300
Argument injection
[[backends]]
name = "db"
transport = "http"
url = "http://db.internal:8080"
# Inject into all tool calls for this backend
[backends.default_args]
timeout = 30
# Inject into a specific tool (overrides default_args for matching keys)
[[backends.inject_args]]
tool = "query"
args = { read_only = true, max_rows = 1000 }
# Force overwrite existing arguments
[[backends.inject_args]]
tool = "dangerous_op"
args = { dry_run = true }
overwrite = true
Traffic mirroring
[[backends]]
name = "api"
transport = "http"
url = "http://api-v1:8080"
[[backends]]
name = "api-v2"
transport = "http"
url = "http://api-v2:8080"
mirror_of = "api"
mirror_percent = 10
Authentication
# Bearer token
[auth]
type = "bearer"
tokens = ["my-secret-token"]
# Or JWT with RBAC
[auth]
type = "jwt"
issuer = "https://auth.example.com"
audience = "mcp-proxy"
jwks_uri = "https://auth.example.com/.well-known/jwks.json"
[[auth.roles]]
name = "reader"
allow_tools = ["files/read_file", "files/list_directory"]
[[auth.roles]]
name = "admin"
[auth.role_mapping]
claim = "scope"
mapping = { "mcp:read" = "reader", "mcp:admin" = "admin" }
Capability filtering
[[backends]]
name = "files"
transport = "stdio"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
# Only expose these tools
expose_tools = ["read_file", "list_directory"]
# Or hide specific tools
# hide_tools = ["write_file", "delete_file"]
Library Usage
Add to your Cargo.toml:
[dependencies]
mcp-proxy = "0.5"
use mcp_proxy::{Proxy, ProxyConfig};
let config = ProxyConfig::load("proxy.toml".as_ref())?;
let proxy = Proxy::from_config(config).await?;
// Embed in an existing axum app
let (router, session_handle) = 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)
cargo install mcp-proxy --no-default-features
# Just metrics, no otel or JWT
cargo install mcp-proxy --no-default-features --features metrics
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.
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found