-
Notifications
You must be signed in to change notification settings - Fork 1
Architecture
Chenglei Yuan edited this page Jun 4, 2026
·
1 revision
A source-grounded tour of how the proxy is put together. For deep dives see Bridge Internals and OAuth2 Internals.
┌────────────────────────── mcp-oauth2-proxy ──────────────────────────┐
│ │
MCP client │ stdio.ts bridge.ts oauth2/ │ upstream
(parent) │ ┌─────────┐ ┌───────────┐ getToken() ┌────────────┐ │ MCP server
──stdin──▶│ │StdioCodec│──msg──▶│ Bridge │──────────────▶│TokenManager│ │
◀─stdout──│ │ (JSON-RPC)│◀─resp─│ POST/SSE │◀──Bearer──────│ + Grant │──token─┼──▶ IdP token
│ └─────────┘ └─────┬─────┘ └────────────┘ │ endpoint
│ ▲ │ HTTP + SSE (undici) │
│ │ └────────────────────────────────────────────┼──▶ upstream /mcp
│ log.ts (pino → stderr) │
└────────────────────────────────────────────────────────────────────────┘
| Module | Responsibility |
|---|---|
index.ts |
Process entry point and startup orchestration. |
config.ts |
zod schema, env-var overrides, load-time security validation. |
security.ts |
isLoopbackHost, assertSecureUrl (cleartext-HTTP policy). |
log.ts |
pino logger to stderr with secret redaction. |
stdio.ts |
Newline-delimited JSON-RPC codec; request/notification/response helpers. |
bridge.ts |
The message loop: forward each JSON-RPC message to the upstream over HTTP/SSE. |
oauth2/tokenManager.ts |
Access-token cache, proactive refresh, in-flight dedup, invalidate. |
oauth2/factory.ts |
Build the right Grant; wire the refresh-token cache for authorization_code. |
oauth2/grants/* |
Grant interface + client_credentials / authorization_code / refresh_token. |
oauth2/discovery.ts |
RFC 9728 → RFC 8414 / OIDC endpoint discovery. |
oauth2/interactive.ts |
PKCE, browser open, local callback server, DNS-rebinding/CSRF defense. |
oauth2/tokenCache.ts |
AES-256-GCM refresh-token cache on disk. |
oauth2/http.ts |
undici-backed token-endpoint client; client-auth + token-response parsing. |
-
loadConfig()— readMCP_PROXY_CONFIG(if set), apply env overrides, validate with zod, runassertSecureUrlon upstream/token/authorization URLs. -
createLogger()— pino to stderr at the configured level. - If
discovery.enabled,discoverFromUpstream()fills in missing endpoints. -
buildGrant()— construct the configured grant (requires a token URL). -
new TokenManager(grant, …)and prefetch a token viagetToken(). On failure the process exits with code 2 (fail fast, before touching stdio). -
new StdioCodec(process.stdin, process.stdout)andnew Bridge(...). - Install
SIGINT/SIGTERMhandlers for graceful shutdown. -
bridge.run()— enter the message loop. A warning is logged ifstdinis a TTY (the proxy expects to be driven by an MCP client, not a human).
client → stdin → StdioCodec.messages() → Bridge
Bridge: token = TokenManager.getToken()
POST upstream (Authorization: Bearer, Accept: json + event-stream)
├─ 200 application/json → write JSON-RPC response to stdout
├─ 200 text/event-stream → parse SSE frames, emit data: payloads
├─ 202 Accepted → ack; maybe open server stream
├─ 401 → invalidate token, refresh, retry once
└─ other → JSON-RPC error to stdout
-
Serialized message loop. The bridge chains processing with
pending = pending.then(...), so messages from the client are forwarded in order, one at a time. -
Async server stream. Independently, a long-lived
GET text/event-streamchannel (whenopenServerStreamis on) delivers server-initiated notifications, with bounded exponential backoff on reconnect. See Bridge Internals. -
Single token authority. All token access funnels through one
TokenManager, which de-duplicates concurrent refreshes.
- Upstream transport errors become JSON-RPC errors (
-32000), with timeouts detected from undici'sUND_ERR_BODY_TIMEOUT/UND_ERR_HEADERS_TIMEOUT. - Non-2xx upstream responses become JSON-RPC error
-32001. -
SIGINT/SIGTERMstop the loop and let in-flight work unwind.
GitHub repo · npm package · Licensed under MIT
Overview
Guides
- Getting Started
- Configuration
- OAuth2 Grants and Tokens
- Discovery
- Security
- Remote Hosts (SSH Port Forwarding)
- Troubleshooting
Internals