Open-source gateway for Model Context Protocol (MCP) servers.
MCPGate is a reverse proxy that sits between AI clients (Claude, Cursor, OpenAI Agents SDK) and your MCP servers. It adds authentication, rate limiting, access control, and observability — without modifying your MCP servers.
AI Client → MCPGate → MCP Server (GitHub)
→ MCP Server (Postgres)
→ MCP Server (Slack)
MCP servers are powerful, but deploying them in production raises questions:
- Who is calling my MCP server? MCPGate authenticates every request with API keys, JWT, or OIDC.
- How do I prevent abuse? Token-bucket rate limiting with per-client and per-server controls.
- Can I restrict which tools a client can use? Fine-grained access control policies with wildcard matching.
- What's happening across my MCP servers? Prometheus metrics, structured audit logs, and request tracing.
- Node.js 22+
- One or more MCP servers (any server that speaks MCP Streamable HTTP)
git clone https://github.com/BuildWithAbid/mcpgate.git
cd mcpgate
npm installCreate config/mcpgate.yaml:
server:
port: 3000
upstreams:
github:
url: "https://your-github-mcp-server.example.com/mcp"
timeout: 30000# Development
npm run dev
# Production
npm run build
npm startMCPGate is now proxying requests at http://localhost:3000/servers/github/mcp.
MCPGate uses a single YAML config file. Every section except upstreams is optional — start simple and add features as you need them.
Each upstream gets its own route on the gateway:
upstreams:
github:
url: "https://github-mcp.example.com/mcp"
timeout: 30000
auth:
type: bearer
token_env: GITHUB_MCP_TOKEN # Reads from environment variable
postgres:
url: "https://db-mcp.internal:8080/mcp"
timeout: 15000
auth:
type: header
header: X-DB-Key
value_env: DB_MCP_KEYEnvironment variables are interpolated with ${VAR_NAME} syntax.
Authenticate clients connecting to the gateway. Choose one:
API Keys (simplest):
auth:
type: api-key
header: X-API-Key
keys:
- id: engineering
key: "${ENG_API_KEY}"
- id: contractors
key: "${CONTRACTOR_API_KEY}"JWT (OAuth 2.1):
auth:
type: jwt
jwks_uri: "https://auth.example.com/.well-known/jwks.json"
issuer: "https://auth.example.com"
audience: "mcpgate"
algorithms: ["RS256"]
client_id_claim: subOIDC (auto-discovery):
auth:
type: oidc
issuer: "https://accounts.google.com"
audience: "your-client-id"
client_id_claim: subToken-bucket rate limiting with configurable capacity and refill rate:
rate_limit:
backend: memory # memory (single instance) or redis (multi-instance)
defaults:
capacity: 100 # Max burst size
refill_rate: 10 # Tokens per secondRate-limited responses include X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, and Retry-After headers.
First-match-wins policy rules with wildcard support:
policy:
default: allow
rules:
- clients: ["engineering"]
servers: ["github", "postgres"]
tools: ["*"]
decision: allow
- clients: ["contractors"]
servers: ["github"]
tools: ["search_*", "get_*"] # Prefix wildcards
decision: allow
- clients: ["contractors"]
servers: ["postgres"]
decision: denyobservability:
metrics:
enabled: true
path: /metrics # Prometheus endpoint
audit:
enabled: true
log_bodies: false # Set true to log request/response bodiesPrometheus metrics exposed at /metrics:
| Metric | Type | Labels |
|---|---|---|
mcpgate_requests_total |
Counter | server, method, status |
mcpgate_request_duration_seconds |
Histogram | server, method |
mcpgate_active_sessions |
Gauge | server |
mcpgate_tool_calls_total |
Counter | server, tool |
mcpgate_rate_limit_rejections_total |
Counter | client, server |
mcpgate_policy_denials_total |
Counter | client, server, tool |
See config/mcpgate.example.yaml for a complete annotated config.
mcpgate start [--config path] # Start the gateway
mcpgate validate [--config path] # Validate config without starting
mcpgate list-servers [--config path] # List configured upstream serversdocker build -t mcpgate .
docker run -p 3000:3000 -v ./config:/app/config mcpgateOr with Docker Compose:
docker compose upClient POST /servers/github/mcp
│
├─ Auth middleware → Validate API key / JWT / OIDC
├─ Rate limit middleware → Check token bucket
├─ Policy middleware → Evaluate access control rules
│
└─ Proxy handler
├─ Map client session → upstream session
├─ Inject upstream credentials
├─ Forward request via fetch()
└─ Stream SSE response back (zero buffering)
MCPGate manages client-to-upstream session mapping automatically:
- Client sends
initialize→ gateway creates a session, forwards to upstream - Gateway captures upstream's
Mcp-Session-Id, maps it to a client-facing ID - All subsequent requests have session IDs rewritten transparently
- Client sends
DELETE→ gateway cleans up and forwards to upstream
SSE responses from upstream servers are streamed chunk-by-chunk with zero buffering using ReadableStream passthrough. Long-running tool calls stream results in real time.
src/
├── auth/ # API key, JWT, OIDC providers + middleware
├── cli/ # CLI commands (start, validate, list-servers)
├── config/ # Zod schema + YAML loader with env var interpolation
├── lib/ # Shared utilities (Result type, logger, JSON-RPC helpers)
├── observability/ # Prometheus metrics, audit logging, middleware
├── policy/ # Access control engine with wildcard matching
├── proxy/ # Core: session store, request parser, upstream client, SSE passthrough
├── ratelimit/ # Token bucket rate limiter + middleware
├── server/ # Hono app, routes, health endpoint, graceful shutdown
└── index.ts # Entry point
- Zero protocol participation — MCPGate proxies raw HTTP. It does not participate in MCP handshakes or modify JSON-RPC payloads.
- Opt-in everything — Auth, rate limiting, policies, and observability are all optional. Start with just
upstreamsand add features as needed. - Functional core — Business logic uses
Result<T, E>types instead of exceptions. Side effects are pushed to the edges. - Stream-first — SSE responses are never buffered. Backpressure propagates naturally through
ReadableStream.
npm test # Run all tests (113 tests)
npm run test:watch # Watch mode
npm run typecheck # Type checking- Redis-backed rate limiting for multi-instance deployments
- OpenTelemetry distributed tracing
- Hot config reload without dropping connections
- Admin API for runtime session/config inspection
- WebSocket transport support
- Plugin system for custom middleware
Contributions are welcome. Please open an issue first to discuss what you'd like to change.