modelcontextprotocol / modelcontextprotocol/go-sdk

mcp: the negotiated protocol version is recorded on one path of four, so stdio accepts batches a SEP-2575 session forbids

Open
#1,272 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Go
Stars
5.1k
Forks
543
Avg merge
1d 17h
Merged PRs (30d)
37

Description

Part of the audit indexed at #1257.

Four places record a protocol version on a ServerSession and only one of them records it in NegotiatedProtocolVersion:

  • ServerSession.initialize records both, the negotiated one through negotiatedVersion.
  • Server.handle records InitializeParams from the version a new-protocol client declares in _meta, once its support check has accepted it.
  • server/discover records InitializeParams with the version it was asked about.
  • The streamable handler synthesizes InitializeParams from the MCP-Protocol-Version header for an old-protocol request with no handshake.

Readers that ask what a session speaks therefore disagree depending on how the session began. The concrete failure is on stdio: ioConn.sessionUpdated reads only NegotiatedProtocolVersion, so a SEP-2575 session over stdio is treated as 2025-03-26 and accepts JSON-RPC batches. Batching was removed in 2025-06-18, and the streamable handler already refuses them by reading the header, so the two transports disagree about the same session shape.

The support check each of the other three paths applies is all the negotiation SEP-2575 has: there is no handshake response in which a downgrade could be communicated, so a version those paths accept is one the server supports, and recording it as negotiated states what happened. initialize stays the one path that downgrades, because the lifecycle page requires it to answer with a version it supports and InitializeResult.ProtocolVersion is where the client learns the result.

The fallback in the unexported protocolVersion() accessor stays. ServerSessionOptions.State is exported, so a caller may supply InitializeParams with no negotiated version, and state persisted before #1199 carries none either.

Raised by the maintainer during review of #1266: "what do you think about setting the ss.state.NegotiatedProtocolVersion also in case of new protocol version?" The pull request is linked below.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by tracing protocol-version assignment in ServerSession.initialize, Server.handle, server/discover, and the streamable handler, then inspect ioConn.sessionUpdated and the unexported protocolVersion() accessor. Verify that every accepted non-downgrade path records the negotiated version while preserving the fallback for state without one, and test that stdio rejects batches for SEP-2575 sessions as the streamable handler does.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
backend-api-design
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
64/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.