[server] return JSON-RPC error envelopes on auth failure#243
Open
txcfi-scott wants to merge 1 commit intoNateBJones-Projects:mainfrom
Open
[server] return JSON-RPC error envelopes on auth failure#243txcfi-scott wants to merge 1 commit intoNateBJones-Projects:mainfrom
txcfi-scott wants to merge 1 commit intoNateBJones-Projects:mainfrom
Conversation
…e HTTP 401) Strict MCP hosts (Codex CLI, Claude Code) treat bare HTTP 4xx responses as transport-level failures and tear down the connection rather than recovering from an application-level auth error. Wrap the auth-failure path in a JSON-RPC 2.0 error envelope (HTTP 200, code -32001) so the protocol layer carries the failure and the connection stays alive. The success path is unchanged. CORS preflight and StreamableHTTPTransport behavior are preserved. Best-effort extraction of the inbound request id keeps the response correlated with the call; malformed or missing bodies fall back to id: null.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The reference MCP server returns a bare HTTP 401 on auth failure:
Strict MCP clients that follow the JSON-RPC 2.0 wire spec — Codex CLI, Claude Code, and anything else that distinguishes transport faults from application errors — interpret a bare HTTP 4xx as a transport-level failure and tear the connection down rather than surfacing the auth error to the application layer. The practical consequence is that scenarios like "wrong key — prompt the user to update it" or "stale cached key — refetch and retry" can't be handled gracefully: the client sees a dead connection, not a recoverable error.
This bug bites anyone wiring a fresh OB1 deployment up to a strict client and rotating keys (or fat-fingering the first one).
Solution
Return HTTP 200 with a JSON-RPC 2.0 error envelope. The transport stays healthy, and the JSON-RPC layer carries the failure as an application-level error the client can handle:
{ "jsonrpc": "2.0", "error": { "code": -32001, "message": "Unauthorized: missing or invalid authentication." }, "id": <inbound id or null> }-32001lives in JSON-RPC's-32099..-32000"implementation-defined server error" range and is the conventional code MCP servers use for "Unauthorized" in the wild. The inbound requestidis best-effort extracted and echoed back so the response correlates with the call; malformed or missing bodies fall back toid: null.Backward compatibility
error. It can read the message or ignore it.Edge cases handled
iddefaults tonull.iddefaults tonull.idfalls back tonull(onlystring | number | nullare accepted, per the JSON-RPC 2.0 spec).OPTIONS): unchanged — still returnsok200 with CORS headers before auth runs.Implementation
Three small helpers added next to the existing
corsHeadersblock:readBodyText(req)— best-effort body read, returnsnullon bodyless methods or read failure.extractJsonRpcId(bodyText)— best-effort JSON-RPC id extraction, returnsnullon anything malformed.unauthorizedResponse(id)— builds the HTTP 200 + JSON-RPC error envelope with CORS headers attached.The auth check in
app.all("*", ...)calls those helpers in place of the barec.json(...)401.Tested
deno check server/index.ts— no type errors.A wire-level diagnosis of why bare 4xx breaks strict MCP hosts (and why a JSON-RPC envelope is the right fix) was written up separately during the downstream investigation. Happy to share or adapt the relevant parts as a
docs/note if useful — let me know.Files touched
server/index.ts— only file changed. +85 / -1.