mcp-auth-proxy
README.md
# mcp-auth-proxy
Authenticating reverse proxy for [MCP](https://modelcontextprotocol.io)
servers — credential isolation, OAuth2 token management, and composite
tool aggregation.
## What it does
**mcp-auth-proxy** sits between an LLM orchestrator and one or more
upstream services (MCP servers, REST APIs, etc.). It provides:
- **Credential injection** — per-route header injection so the
orchestrator process never holds service tokens.
- **OAuth2 consent flow** — browser-based OAuth2 authorization code
flow for remote MCP providers. Tokens are stored encrypted at rest
(AES-256-GCM) and refreshed automatically.
- **Composite MCP server** — aggregates tools from all linked OAuth2
providers into a single `tools/list` response, namespaced by
provider (e.g. `google_calendar-create_event`). Includes built-in
`proxy-link_provider` for runtime provider linking.
- **Tiered approval system** — configurable per-route and per-tool
autonomy tiers. Tier 2 actions require single-use or standing
approval tokens, preventing the double-send class of bugs.
- **Rate limiting** — sliding-window per-route rate limiter.
- **Prometheus metrics** — request counts, latency histograms,
upstream errors, and approval token lifecycle metrics.
- **Structured logging** — JSON-formatted logs for
Loki/Promtail/Alloy ingestion.
## Architecture
```
┌─────────────┐ ┌─────────────────┐ ┌──────────────────┐
│ Orchestrator │────▶│ mcp-auth-proxy │────▶│ Upstream Service │
│ (LLM) │ │ │ │ (MCP / REST) │
└─────────────┘ │ • auth inject │ └──────────────────┘
│ • rate limit │
│ • tier enforce │ ┌──────────────────┐
│ • MCP aggregate │────▶│ OAuth2 Provider │
│ • metrics │ │ (Google, etc.) │
└─────────────────┘ └──────────────────┘
```
The orchestrator authenticates to the proxy with a shared virtual key
(`X-Gateway-Key` header). The proxy matches the request path to a
configured route, injects the upstream's credentials, and forwards the
request.
For MCP, the proxy presents itself as an MCP server on `POST /mcp`
(Streamable HTTP transport) and `GET /mcp/events` (SSE notifications).
It aggregates tools from all OAuth2-linked providers and dispatches
`tools/call` to the correct upstream.
## Quick start
```bash
# Install
pip install .
# Run with a config file
mcp-auth-proxy --config config.yaml
# Or with Docker
docker build -t mcp-auth-proxy .
docker run -v ./config.yaml:/etc/mcp-auth-proxy/config.yaml mcp-auth-proxy
```
## Configuration
See [`config.example.yaml`](config.example.yaml) for a fully
documented example configuration.
### Routes
Each route maps a path prefix to an upstream URL with optional
credential injection:
```yaml
routes:
- prefix: /api
upstream: http://internal-service:8080
inject_headers:
Authorization: "Bearer ${SERVICE_TOKEN}"
rate_limit: 60 # requests per minute
tier: 1 # 1 = autonomous, 2 = requires approval token
```
### OAuth2 providers
Remote MCP servers accessed via OAuth2:
```yaml
oauth:
encryption_key: "${OAUTH_ENCRYPTION_KEY}" # 64 hex chars (32 bytes AES-256)
external_url: "https://proxy.example.com"
providers:
google_calendar:
display_name: "Google Calendar"
auth_url: "https://accounts.google.com/o/oauth2/v2/auth"
token_url: "https://oauth2.googleapis.com/token"
client_id: "${GOOGLE_CLIENT_ID}"
client_secret: "${GOOGLE_CLIENT_SECRET}"
scopes: ["https://www.googleapis.com/auth/calendar"]
mcp_server_url: "https://calendar-mcp.example.com/mcp"
```
### Approval tokens
Tier 2 actions require an approval token obtained from `POST /approve`:
```bash
# Issue a single-use token
curl -X POST http://localhost:8090/approve \
-H "X-Admin-Key: $ADMIN_KEY" \
-d '{"action": "email.send", "scope": {"to": "user@example.com"}}'
# Use it in a proxied request
curl -X POST http://localhost:8090/email/send \
-H "X-Gateway-Key: $VIRTUAL_KEY" \
-H "X-Approval-Token: apt-abc123..." \
-d '{"to": "user@example.com", "body": "Hello"}'
```
## Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/health` | GET | Health check with route count and MCP status |
| `/_routes` | GET | List configured routes (no credentials exposed) |
| `/mcp` | POST | MCP JSON-RPC endpoint (tools/list, tools/call) |
| `/mcp/events` | GET | SSE stream for MCP notifications |
| `/approve` | POST | Issue an approval token (admin) |
| `/approve` | GET | List active tokens (admin) |
| `/approve/standing` | POST | Issue a standing (multi-use) token |
| `/approve/audit` | GET | Audit trail of token lifecycle |
| `/approve/{id}` | DELETE | Revoke a token |
| `/auth/{provider}/start` | GET | Start OAuth2 consent flow |
| `/auth/{provider}/callback` | GET | OAuth2 callback |
| `/auth/status` | GET | Show linked providers for user |
| `/admin/refresh-tools` | POST | Force tool cache refresh |
| `/admin/tool-cache` | GET | Tool cache status |
| `/{path}` | * | Catch-all reverse proxy |
## Security headers
| Header | Direction | Purpose |
|--------|-----------|---------|
| `X-Gateway-Key` | Client → Proxy | Orchestrator authentication |
| `X-Admin-Key` | Client → Proxy | Approval endpoint authentication |
| `X-MCP-User` | Client → Proxy | User identification for MCP |
| `X-Approval-Token` | Client → Proxy | Tier 2 action authorisation |
| `Remote-User` | Auth proxy → Proxy | Authenticated user (from SSO) |
## Metrics
Prometheus metrics are served on port 9091 (configurable):
- `mcp_proxy_request_total` — requests by route, method, status
- `mcp_proxy_request_duration_seconds` — latency histogram
- `mcp_proxy_rate_limited_total` — rate-limited requests
- `mcp_proxy_upstream_error_total` — upstream errors
- `mcp_proxy_approval_*` — approval token lifecycle
## License
[0BSD](LICENSES/0BSD.txt) — do whatever you want with it.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues