Reject non-Hash JSON-RPC bodies in StreamableHTTPTransport#354
Open
koic wants to merge 1 commit into
Open
Conversation
## Motivation and Context MCP 2025-11-25 does not support JSON-RPC batch (batch support was removed in 2025-06-18), so request bodies are expected to be a single JSON-RPC message object. The previous code path: ```ruby body = parse_request_body(body_string) return body unless body.is_a?(Hash) ``` returned the parsed JSON value as if it were a Rack response when the body was a JSON array (former batch shape), a string, a number, or any other non-Hash JSON value. This produced a malformed Rack response instead of a proper HTTP 400. This was a pre-existing bug exposed during follow-up review of modelcontextprotocol#347 (`MCP-Protocol-Version` header validation), which surfaced it but did not fix the broken response shape for non-Hash bodies sent with a valid header. ### Behavior - `parse_request_body` now raises a private `InvalidJsonError` on parse failure instead of returning a Rack response tuple. The overloaded return type and the `parse_error_tuple?` discriminator are gone. - `handle_post` rescues `InvalidJsonError` and returns the same plain JSON 400 response as before (`{"error":"Invalid JSON"}`), so parse error handling is observably unchanged. - Non-Hash parsed bodies (arrays, strings, numbers, booleans, null) now return HTTP 400 with a plain JSON body explaining that the body must be a single JSON-RPC message object, instead of producing a malformed Rack response. The error response shape stays in the existing plain JSON style for consistency with `validate_content_type`, `not_acceptable_response`, and the other transport-level error helpers. Unifying all of these to a JSON-RPC error envelope (matching the Python and TypeScript SDKs) is deferred to a separate follow-up. ### SDK Comparison - Python SDK (`src/mcp/server/streamable_http.py`): Already rejects non-Hash bodies (Array, primitive) with HTTP 400. Uses a JSON-RPC error envelope with `INVALID_PARAMS`. - TypeScript SDK (`packages/server/src/server/streamableHttp.ts`): As of `main`, still processes JSON arrays as JSON-RPC batches and has not yet caught up with the 2025-06-18 batch removal. Primitives fail schema validation and return 400 with a JSON-RPC error envelope. This change brings the Ruby SDK in line with the current MCP spec (no batch). The Ruby SDK is now closer to the spec than the TypeScript SDK's `main` branch on this specific point. ## How Has This Been Tested? Existing regression test `test "handles POST request with invalid JSON"` continues to pass unchanged (plain JSON response preserved). Added new regression tests: - POST with a JSON array body returns 400 with a clear error message - POST with a non-object JSON body (e.g. `"foo"`) returns 400 `bundle exec rake test` and `bundle exec rake rubocop` both pass. ## Breaking Changes None for compliant clients sending single JSON-RPC message objects. Clients that were sending JSON arrays (batch requests, no longer supported as of MCP 2025-11-25) or other non-object bodies will now receive a proper HTTP 400 with a JSON error body instead of the previous malformed Rack response; no client could have relied on the broken pre-existing behavior.
7c60956 to
f62d813
Compare
atesgoral
approved these changes
May 17, 2026
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.
Motivation and Context
MCP 2025-11-25 does not support JSON-RPC batch (batch support was removed in 2025-06-18), so request bodies are expected to be a single JSON-RPC message object. The previous code path:
returned the parsed JSON value as if it were a Rack response when the body was a JSON array (former batch shape), a string, a number, or any other non-Hash JSON value. This produced a malformed Rack response instead of a proper HTTP 400.
This was a pre-existing bug exposed during follow-up review of #347 (
MCP-Protocol-Versionheader validation), which surfaced it but did not fix the broken response shape for non-Hash bodies sent with a valid header.Behavior
parse_request_bodynow raises a privateInvalidJsonErroron parse failure instead of returning a Rack response tuple. The overloaded return type and theparse_error_tuple?discriminator are gone.handle_postrescuesInvalidJsonErrorand returns the same plain JSON 400 response as before ({"error":"Invalid JSON"}), so parse error handling is observably unchanged.The error response shape stays in the existing plain JSON style for consistency with
validate_content_type,not_acceptable_response, and the other transport-level error helpers. Unifying all of these to a JSON-RPC error envelope (matching the Python and TypeScript SDKs) is deferred to a separate follow-up.SDK Comparison
src/mcp/server/streamable_http.py): Already rejects non-Hash bodies (Array, primitive) with HTTP 400. Uses a JSON-RPC error envelope withINVALID_PARAMS.packages/server/src/server/streamableHttp.ts): As ofmain, still processes JSON arrays as JSON-RPC batches and has not yet caught up with the 2025-06-18 batch removal. Primitives fail schema validation and return 400 with a JSON-RPC error envelope.This change brings the Ruby SDK in line with the current MCP spec (no batch). The Ruby SDK is now closer to the spec than the TypeScript SDK's
mainbranch on this specific point.How Has This Been Tested?
Existing regression test
test "handles POST request with invalid JSON"continues to pass unchanged (plain JSON response preserved). Added new regression tests:"foo") returns 400bundle exec rake testandbundle exec rake rubocopboth pass.Breaking Changes
None for compliant clients sending single JSON-RPC message objects. Clients that were sending JSON arrays (batch requests, no longer supported as of MCP 2025-11-25) or other non-object bodies will now receive a proper HTTP 400 with a JSON error body instead of the previous malformed Rack response; no client could have relied on the broken pre-existing behavior.
Types of changes
Checklist