Skip to main content
Glama
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.

Maintenance

ActivitySlowing
ResponsivenessNo issues