Skip to content

fix: do not treat malformed JSON 200 as Accepted for requests - #1208

Open
SebTardif wants to merge 1 commit into
modelcontextprotocol:mainfrom
SebTardif:fix/request-json-parse-not-accepted
Open

fix: do not treat malformed JSON 200 as Accepted for requests#1208
SebTardif wants to merge 1 commit into
modelcontextprotocol:mainfrom
SebTardif:fix/request-json-parse-not-accepted

Conversation

@SebTardif

Copy link
Copy Markdown

Summary

Fail a streamable HTTP request POST when the server returns HTTP 200 with application/json that is not a JSON-RPC message, instead of treating that body as Accepted and hanging call_tool.

Problem

post_message maps any application/json body that fails to parse as ServerJsonRpcMessage to StreamableHttpPostResponse::Accepted. That fallback is correct for notification POSTs: nothing is waiting for a reply. The same branch also runs for requests.

The worker then records the request id as pending and waits on the SSE/GET stream. call_tool never returns when the server answers 200 + Content-Type: application/json with {}, an empty body, or other non-JSON-RPC JSON.

Public repro:

  1. StreamableHttpClientTransport::from_uri
  2. ().serve(transport)
  3. peer.call_tool(...)
  4. Mock: initialize succeeds; tools/call returns 200 + application/json + {}

Change

Keep the Accepted fallback only when the outbound message is a notification, response, or error. For a request, return UnexpectedServerResponse.

The same check is applied in the reqwest client and the Unix socket client. Notification empty-200 handling from Ref #849 is unchanged. This does not implement the unexpected-content-type fallback in open Ref #1063.

The parse-as-Accepted path was added in #687 (2026-02-26) for notification POSTs that lack an id.

Validation

Red (production restored from origin/main, new tests only):

cargo test -p rmcp --test test_streamable_http_malformed_json_request \
  --features 'client,transport-streamable-http-client,transport-streamable-http-client-reqwest'

Failed with call_tool must return instead of hanging on malformed JSON 200: Elapsed(()) and expected UnexpectedServerResponse, got: Ok(Accepted).

Green (after the fix): same command, 5 passed in 0.01s.

Also passed:

cargo test -p rmcp --test test_streamable_http_4xx_error_body \
  --test test_streamable_http_empty_2xx_notification \
  --features 'client,transport-streamable-http-client,transport-streamable-http-client-reqwest'
cargo test -p rmcp --test test_unix_socket_transport \
  --features 'client,server,transport-streamable-http-client-unix-socket'
cargo +nightly fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings

Related

Ref #849 (accept empty 200 for notification POSTs)

Ref #1063 (notification POST response shape; still open). That PR tests that a request still rejects text/plain. It does not close JSON parse failure on a request POST.

Streamable HTTP mapped any JSON body that failed to parse as
ServerJsonRpcMessage to Accepted, which is correct for notification
POSTs. For a request the worker then waits on SSE forever, so
call_tool hangs when the server returns 200 application/json with
an empty or non-JSON-RPC body.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
@SebTardif
SebTardif requested a review from a team as a code owner August 23, 2026 18:25
@github-actions github-actions Bot added T-dependencies Dependencies related changes T-test Testing related changes T-config Configuration file changes T-core Core library changes T-transport Transport layer changes labels Aug 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-config Configuration file changes T-core Core library changes T-dependencies Dependencies related changes T-test Testing related changes T-transport Transport layer changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant