Skip to content

Echo parsed request id in Streamable HTTP header/session error responses#1682

Open
anneheartrecord wants to merge 1 commit into
modelcontextprotocol:mainfrom
anneheartrecord:fix/header-validation-echo-request-id
Open

Echo parsed request id in Streamable HTTP header/session error responses#1682
anneheartrecord wants to merge 1 commit into
modelcontextprotocol:mainfrom
anneheartrecord:fix/header-validation-echo-request-id

Conversation

@anneheartrecord

Copy link
Copy Markdown

Summary

Header-validation and session-gate errors in the stateless Streamable HTTP transport were returning id: null even though the JSON-RPC request body — and its id — had already been parsed.

In StreamableHttpHandler.HandlePostRequestAsync, the body is parsed (ReadJsonRpcMessageAsync) before ValidateMcpHeaders runs, but WriteJsonRpcErrorAsync constructed a JsonRpcError without an Id, so it serialized as null. The same applied to the post-parse session checks in GetOrCreateSessionAsync.

Per JSON-RPC 2.0 §5, a null id is reserved for responses where the request id couldn't be read (parse error / invalid request). The MCP base protocol likewise requires error responses to carry the request's id except when it couldn't be read from a malformed request. Here the id was available, so it should be echoed.

Change

  • WriteJsonRpcErrorAsync takes an optional RequestId and sets it on the error.
  • A small GetRequestId(JsonRpcMessage?) helper returns the parsed id for messages that carry one, or default (→ null) otherwise.
  • Threaded the parsed id into the header-mismatch path and the three post-parse session-gate error paths.
  • Pre-parse paths (Accept negotiation, malformed/null body) and the body-less GET/DELETE paths keep id: null, which stays correct.

Tests

Added to HttpHeaderConformanceTests:

  • missing Mcp-Name header echoes the numeric request id (the case in the issue)
  • header/body name mismatch echoes a string request id
  • Mcp-Session-Id rejected under the 2026-07-28 revision echoes the id
  • a malformed body still returns id: null (guards against over-correcting)

Verified the three echo tests fail before the change and pass after; the malformed-body test passes in both. Full HttpHeaderConformanceTests and the Streamable HTTP / stateless / July-2026 suites pass (net10.0; net8/net9 runtimes weren't available locally but the logic is framework-independent).

Fixes #1677

Header validation and session-gate errors in the stateless Streamable HTTP
transport ran after the JSON-RPC body (and its id) had been parsed, but the
error responses were written with no id, so they serialized as id:null.

JSON-RPC 2.0 §5 reserves a null id for responses where the request id could
not be read (parse/invalid-request errors). Once the body is parsed the id is
known, so thread it through WriteJsonRpcErrorAsync for the header-mismatch and
session-validation paths. Pre-parse and unparseable-body paths keep id:null.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Draft protocol: HTTP header-validation errors return id: null after parsing the request id

2 participants