-
Notifications
You must be signed in to change notification settings - Fork 1
Bridge Internals
bridge.ts is the heart of the proxy: it forwards each JSON-RPC message from the
client to the upstream MCP server over HTTP, parses the response (JSON or SSE),
and manages a long-lived server-notification stream. This page documents its
behavior in detail. For the surrounding flow, see Architecture.
Messages from StdioCodec.messages() are processed through a serialized
promise chain:
pending = pending.then(() => handle(message))
This guarantees in-order, one-at-a-time forwarding even though each handle
call is async.
For each message the bridge issues a POST to upstream.url with:
-
Authorization: Bearer <token>(from theTokenManager). -
Accept: application/json, text/event-stream— the upstream may answer with either. -
Content-Type: application/json. -
MCP-Protocol-Version(ifprotocolVersionis configured). -
Mcp-Session-Idonce captured (see below).
The request is bounded by an AbortController plus undici body/headers
timeouts derived from upstream.timeoutMs.
| Status | Behavior |
|---|---|
200 (JSON) |
Parse the body as a JSON-RPC response and write it to stdout. |
200 (SSE) |
Parse text/event-stream frames and emit each data: payload to stdout. |
202 Accepted |
Acknowledge; the body is drained. For requests that imply a session, may open the server stream. |
401 |
TokenManager.invalidate(), fetch a fresh token, and retry the request once. |
| other non-2xx | Emit a JSON-RPC error (-32001) carrying the status. |
A second consecutive 401 is surfaced to the client rather than retried again.
The first response carrying an Mcp-Session-Id header has that value captured
and replayed as a request header on subsequent calls, so the upstream can
correlate the session.
The response Content-Type decides parsing:
-
application/json→ single JSON-RPC object. -
text/event-stream→ SSE framing (below).
The proxy parses Server-Sent Events incrementally:
- Bytes are accumulated into a buffer and split on event boundaries
(
\n\nor\r\n\r\n). - Within an event, only
data:lines are collected; the joined data is the JSON-RPC payload emitted to stdout. - A buffer guard caps the in-progress buffer at 8 MB (
MAX_BUF); this prevents a malicious or broken stream from growing memory unbounded.
When upstream.openServerStream is enabled, the bridge maintains a separate,
long-lived GET request with Accept: text/event-stream to receive
server-initiated notifications:
-
Backoff on reconnect: exponential, base 500 ms, capped at 30 s,
with jitter; up to 10 retries (
maxRetries). -
Auto-disable: if the upstream responds
404or405to the stream request, the proxy concludes the upstream doesn't support it and stops trying. - The stream reuses the captured session id and Bearer token, and emits notifications to stdout as they arrive.
- Requests use
AbortControllertogether with undicibodyTimeout/headersTimeout. - Transport errors become JSON-RPC error
-32000. Timeouts are detected from undici error codesUND_ERR_BODY_TIMEOUTandUND_ERR_HEADERS_TIMEOUTand reported as timeouts. - When stdout is closed (client gone), writes are swallowed so shutdown stays clean.
See OAuth2 Internals for how the Bearer token is produced and refreshed.
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