Skip to content

fix: use negotiated protocol version in server-sse-polling raw requests - #416

Merged
pcarleton merged 2 commits into
mainfrom
fix/sse-polling-negotiated-version
Jul 27, 2026
Merged

fix: use negotiated protocol version in server-sse-polling raw requests#416
pcarleton merged 2 commits into
mainfrom
fix/sse-polling-negotiated-version

Conversation

@claude

@claude claude Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Requested by Paul Carleton · Slack thread

Before

The server-sse-polling scenario initialized through the SDK's StreamableHTTPClientTransport but hard-coded mcp-protocol-version: 2025-11-25 on its two raw follow-up requests — the tools/call test_reconnection POST and the Last-Event-ID reconnection GET. Against a stateful server that enforces the version negotiated during initialize, those requests are rejected the moment the negotiated version is anything other than 2025-11-25: the 400 on the POST is misreported as "server does not implement test_reconnection tool" (WARNING) and the scenario ends before testing any polling behavior. This is the same bug class as #412, fixed for server-sse-multiple-streams in #415.

After

Both raw requests carry the version actually negotiated during initialize, so version-enforcing servers get their SSE polling behavior genuinely exercised. Servers that don't enforce the version are unaffected.

How

In src/scenarios/server/sse-polling.ts, after client.connect(transport) the scenario reads the SDK transport's public protocolVersion getter into negotiatedProtocolVersion (alongside the session-id extraction, now via the public sessionId getter) and uses it for the mcp-protocol-version header on the raw POST and reconnection GET, falling back to the run's specVersion if the transport exposes none — mirroring the merged #415 pattern exactly.

src/scenarios/server/sse-polling.test.ts adds regression coverage with a hardened fixture: the fixture server down-negotiates every initialize to 2025-06-18 regardless of the proposed version, records each follow-up request's MCP-Protocol-Version header (and, for GETs, Last-Event-ID presence, so the SDK's automatic standalone GET cannot satisfy the reconnect assertion), and 400s any version or session-id mismatch — so a wrong fix that hard-codes the latest version cannot pass. The fixture serves a spec-shaped priming event (id + empty data) then closes the stream without the tool result, forcing the scenario down the Last-Event-ID reconnection GET path so both fixed sites are exercised. Verified the test fails against the previous scenario code with either site regressed individually. A second test pins that the fixture rejects the supported-but-non-negotiated latest version.

Also verified end-to-end against the in-repo everything-server (examples/servers/typescript) on this branch: server-sse-polling passes 3/3 (priming event, retry field, disconnect-resume), so lenient servers see no behavior change.

Follow-up to #415; same bug class as #412.

The server-sse-polling scenario hard-coded mcp-protocol-version:
2025-11-25 on its raw tools/call POST and Last-Event-ID reconnection
GET instead of the version negotiated during initialize — the same
bug class as #412. A server that enforces the negotiated session
version rejects those requests once it negotiates any other version.

Read transport.protocolVersion after client.connect() and use it for
both raw request headers, falling back to the run's spec version,
mirroring the sse-multiple-streams fix (#415). Add a regression test
whose fixture server down-negotiates to 2025-06-18 and 400s any
request whose MCP-Protocol-Version header does not match, so a fix
that hard-codes the latest version cannot pass.

Follow-up to #415.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0178cz8nAyFRHQyq9cg2XgZz
@pkg-pr-new

pkg-pr-new Bot commented Jul 27, 2026

Copy link
Copy Markdown

Open in StackBlitz

npx https://pkg.pr.new/@modelcontextprotocol/conformance@416

commit: 75702c7

Enforce session id alongside protocol version in the fixture, record
Last-Event-ID presence per GET so the SDK's automatic standalone GET
cannot satisfy the reconnect assertion, normalize the header type
instead of casting, return a descriptive 400 body on the GET path,
add Allow to 405s, use the spec-shaped empty-data priming event, and
read sessionId via the SDK's public getter in the scenario.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0178cz8nAyFRHQyq9cg2XgZz
@claude
claude Bot marked this pull request as ready for review July 27, 2026 11:24
@pcarleton
pcarleton merged commit 0c85302 into main Jul 27, 2026
8 checks passed
@pcarleton
pcarleton deleted the fix/sse-polling-negotiated-version branch July 27, 2026 12:21
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.

2 participants