Skip to content

fix(transport): honour json_response in session mode - #1407

Open
wweir wants to merge 1 commit into
modelcontextprotocol:mainfrom
wweir:fix/json-response-session-mode
Open

wweir wants to merge 1 commit into
modelcontextprotocol:mainfrom
wweir:fix/json-response-session-mode

Conversation

@wweir

@wweir wweir commented Oct 29, 2026

Copy link
Copy Markdown

Problem

StreamableHttpServerConfig::json_response only applies to the stateless path
(serve_negotiated_request_directly). With a session (the default
legacy_session_mode, and any legacy-protocol client), every POST answer is framed
as text/event-stream, no matter what the client asked for.

That matters because the transport also requires the client to declare both
media types:

Accept: application/json, text/event-stream   → ok
Accept: application/json                      → 406 "Client must accept both ..."

So a client that declares text/event-stream only to satisfy the 406 check, and
then parses nothing but JSON, gets a correct initialize and then stops — it cannot
read any subsequent call. Concretely: Gemini's connector verification does exactly
that, opens a session (initialize + notifications/initialized succeed), and then
never reads tools/list, reporting "cannot connect to this server".

Change

Honour json_response in session mode too: peek the first message of the
request-wise stream.

  • terminal response/error → answer application/json (jsonrpc_message_response),
    same as the stateless path already does;
  • anything else (priming event, notification, server request) → replay that first
    message in front of the rest and keep the SSE framing, so no message is lost and
    streaming clients are unaffected (json_response defaults to false).

Also updates the json_response doc comment, which claimed stateless-only.

Tests

  • cargo check -p rmcp --lib --features server — clean
  • cargo test -p rmcp --lib --features server — 229 passed

The behaviour was validated against a real deployment: with this change, a client
that sends Accept: application/json, text/event-stream but only parses JSON
receives initialize, tools/list (~90 KiB catalogue) and tools/call as
application/json with a working Mcp-Session-Id, and proceeds to use the tools.

Note: this is deliberately not relaxing the Accept requirement — that check is
spec-faithful; only the response framing was unreachable for these clients.

A client that negotiated a session can need JSON bodies just as much as a
stateless one - it cannot parse an event stream at all - but json_response only
applied to the stateless path, so every session-mode call came back as SSE.

Peek the first message of the request-wise stream: a terminal response is
answered as application/json, and any intermediate message (priming event,
notification, server request) is replayed in front of the rest so the stream
still carries it.

Refs: MCP Streamable HTTP, Accept: application/json, text/event-stream.
@wweir
wweir requested a review from a team as a code owner October 29, 2026 00:47
@github-actions github-actions Bot added T-documentation Documentation improvements T-core Core library changes T-transport Transport layer changes labels Oct 29, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-29T00:48:23.730124Z
We couldn’t complete this review. Comment @codex review to try again.
2c60815 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions
github-actions Bot requested a review from gocamille October 29, 2026 00:47
@github-actions

Copy link
Copy Markdown
Contributor

@gocamille is on triage rotation this week and is the reviewer for this one.

Labels come from the PR labeler. Please review the change.

Automated triage. Labels are a first pass and the assigned maintainer has the final say.

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

T-core Core library changes T-documentation Documentation improvements T-transport Transport layer changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant