Skip to content

Return Parse error (-32700) for malformed JSON in Streamable HTTP POST - #1922

Draft
burrows99 wants to merge 1 commit into
modelcontextprotocol:mainfrom
burrows99:fix/1918-http-parse-error
Draft

burrows99 wants to merge 1 commit into
modelcontextprotocol:mainfrom
burrows99:fix/1918-http-parse-error

Conversation

@burrows99

@burrows99 burrows99 commented Oct 12, 2026 •

Copy link
Copy Markdown

Fixes #1918

The Streamable HTTP POST handler caught every JsonException from reading the body in one place and answered InvalidRequest (-32600). That catch covered two different failures: a body that is not valid JSON, and valid JSON that the JsonRpcMessage converter rejects (for example "id": null). JSON-RPC 2.0 §5.1 assigns -32700 (Parse error) to the first and -32600 to the second.

The handler now reads the body as a JsonElement first and answers ParseError (-32700) if that fails, then deserializes the JsonRpcMessage from the element and keeps InvalidRequest (-32600) for envelope failures. HTTP status stays 400 and the error id stays null. SseHandler, which shares ReadJsonRpcMessageAsync, is unchanged. This matches the stdio transport, which already answers ParseError since #1629.

Tests: PostMalformedJson_Returns400_InvalidRequest_WithNullId asserted -32600 for malformed JSON; it is now PostMalformedJson_Returns400_ParseError_WithNullId, a theory over { this is not valid json and the issue's truncated {"jsonrpc":. Both cases fail on main (expected -32700, actual -32600) and pass with this change. PostRequestWithExplicitNullId_Returns400_InvalidRequest_WithNullId still passes, pinning -32600 for valid JSON that is not a valid request. Builds on net8.0/net9.0/net10.0 with 0 warnings; tests run on net10.0.

Reproduced against samples/AspNetCoreMcpServer by POSTing {"jsonrpc":: main answers -32600, this branch answers -32700 (recording below).

Notes: overlaps the catch block edited in #1844, so whichever lands second needs a small rebase. Valid requests now parse once into a JsonElement before deserializing; happy to measure if that's a concern. Opened as a draft so the approach can be confirmed on the issue first.

AI disclosure: this change was written with Claude Code (investigation, code and tests). I reviewed the diff and ran the build and tests locally.

Evidence: real MCP clients against the SDK's own samples/AspNetCoreMcpServer. Left pane: the sample server, built from main (before) or this branch (after), with its request log. Right: the official MCP Inspector connects over Streamable HTTP and calls echo normally, then a small "buggy client" page POSTs (1) a normal initialize, (2) a body cut off mid-write ({"jsonrpc":), and (3) valid JSON with "id": null, showing the error code it gets back and what that code tells a client developer.

Before (main): the cut-off body gets -32600 Invalid Request.

video-narrow.mp4

After (this branch): the cut-off body gets -32700 Parse error; the null-id request still gets -32600.

video-narrow.mp4

The POST handler answered a body that is not valid JSON with Invalid
Request (-32600). JSON-RPC 2.0 section 5.1 reserves -32700 for invalid
JSON and -32600 for JSON that is not a valid request object.

Parse the body as JSON first and answer -32700 when that fails, then
deserialize the JSON-RPC message and keep -32600 for envelope failures
such as an explicit null request id.

Fixes modelcontextprotocol#1918

This branch has not been deployed

No deployments
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.

Streamable HTTP returns -32600 instead of -32700 for malformed JSON

1 participant