Describe the bug
The C# SDK's initialize handler accepts protocolVersion: "2026-07-28" and echoes it back in the InitializeResult, even though the 2026-07-28 revision removed the initialize/notifications/initialized handshake and moved protocol version, client identity, and client capabilities to per-request _meta.
The draft specification's versioning page defines the two eras:
- Modern (2026-07-28 and later): protocol versions that convey version, identity, and capabilities as per-request metadata.
- Legacy (2025-11-25 and earlier): protocol versions that establish a session with an
initialize handshake.
For dual-era servers, the spec says: "An initialize request selects legacy semantics, scoped to the stdio process (stdio) or the session (HTTP), as specified by the negotiated legacy protocol version." That seems to imply that initialize should negotiate one of the legacy revisions, not a modern revision such as 2026-07-28.
When initialize negotiates "2026-07-28", the session appears to claim a protocol revision that no longer defines the handshake that created it. The resulting state looks mixed: it declares a modern version, but it was established through the legacy initialization path without per-request _meta.
This appears to be a version-negotiation consistency issue.
Environment tested:
main snapshot from 2026-06-28: d6b1615988d3a73ffb653d08778b14fa695e1318
- Server used:
tests/ModelContextProtocol.TestServer
- Transport: stdio
To Reproduce
Steps to reproduce the behavior:
- Start the C# SDK stdio server.
- Send an
initialize request with protocolVersion: "2026-07-28":
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2026-07-28","capabilities":{"roots":{"listChanged":true},"sampling":{},"elicitation":{}},"clientInfo":{"name":"repro","version":"0.1.0"}}}
- Observe the response.
Expected behavior
When a dual-era server receives initialize, I would expect it to negotiate a legacy protocol revision. In this case, I would expect the server to either:
- Negotiate down to
"2025-11-25" or the latest legacy revision it supports, since initialize selects legacy semantics; or
- Reject the request with an error indicating that
"2026-07-28" is not available through the legacy initialize handshake, optionally pointing the client to server/discover with per-request _meta.
I would not expect the legacy initialize path to echo "2026-07-28", because that makes the resulting session claim a protocol revision that does not define the handshake that created it.
Observed behavior
The server responds:
{"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2026-07-28","capabilities":{"logging":{},"prompts":{},"resources":{"subscribe":true},"tools":{},"completions":{}},"serverInfo":{"name":"TestServer","version":"1.0.0.0"},"instructions":"This is a test server with only stub functionality"}}
The response echoes "protocolVersion": "2026-07-28" — a revision that removed initialize.
This was observed over stdio. I have not separately confirmed the HTTP behavior; I mention ConfigureInitialize because the handler appears not to be stdio-specific.
Additional context
Looking at McpServerImpl.cs, the ConfigureInitialize handler appears to negotiate the version as follows:
string? protocolVersion = options.ProtocolVersion;
protocolVersion ??= request?.ProtocolVersion is string clientProtocolVersion &&
McpSessionHandler.SupportedProtocolVersions.Contains(clientProtocolVersion) ?
clientProtocolVersion :
McpHttpHeaders.November2025ProtocolVersion;
Because SupportedProtocolVersions includes McpHttpHeaders.July2026ProtocolVersion ("2026-07-28"), this check appears to pass and the client's requested version is echoed. The fallback to November2025ProtocolVersion appears to trigger only for versions not in the supported list.
The code comment on lines ~373-377 describes the legacy initialize handshake as authoritative:
"The legacy initialize handshake is authoritative: it may supersede a protocol version a prior server/discover probe established on the same connection..."
That makes sense for choosing the legacy session state after a client has entered through initialize. The part that seems inconsistent with the draft versioning model is allowing that legacy entry point to negotiate a modern protocol revision.
One possible fix would be to cap initialize negotiation at November2025ProtocolVersion or the latest supported legacy revision, and return either a downgraded version or an error when a modern version is requested through the legacy handshake.
Related:
Describe the bug
The C# SDK's
initializehandler acceptsprotocolVersion: "2026-07-28"and echoes it back in theInitializeResult, even though the 2026-07-28 revision removed theinitialize/notifications/initializedhandshake and moved protocol version, client identity, and client capabilities to per-request_meta.The draft specification's versioning page defines the two eras:
initializehandshake.For dual-era servers, the spec says: "An
initializerequest selects legacy semantics, scoped to the stdio process (stdio) or the session (HTTP), as specified by the negotiated legacy protocol version." That seems to imply thatinitializeshould negotiate one of the legacy revisions, not a modern revision such as2026-07-28.When
initializenegotiates"2026-07-28", the session appears to claim a protocol revision that no longer defines the handshake that created it. The resulting state looks mixed: it declares a modern version, but it was established through the legacy initialization path without per-request_meta.This appears to be a version-negotiation consistency issue.
Environment tested:
mainsnapshot from 2026-06-28:d6b1615988d3a73ffb653d08778b14fa695e1318tests/ModelContextProtocol.TestServerTo Reproduce
Steps to reproduce the behavior:
initializerequest withprotocolVersion: "2026-07-28":{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2026-07-28","capabilities":{"roots":{"listChanged":true},"sampling":{},"elicitation":{}},"clientInfo":{"name":"repro","version":"0.1.0"}}}Expected behavior
When a dual-era server receives
initialize, I would expect it to negotiate a legacy protocol revision. In this case, I would expect the server to either:"2025-11-25"or the latest legacy revision it supports, sinceinitializeselects legacy semantics; or"2026-07-28"is not available through the legacyinitializehandshake, optionally pointing the client toserver/discoverwith per-request_meta.I would not expect the legacy
initializepath to echo"2026-07-28", because that makes the resulting session claim a protocol revision that does not define the handshake that created it.Observed behavior
The server responds:
{"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2026-07-28","capabilities":{"logging":{},"prompts":{},"resources":{"subscribe":true},"tools":{},"completions":{}},"serverInfo":{"name":"TestServer","version":"1.0.0.0"},"instructions":"This is a test server with only stub functionality"}}The response echoes
"protocolVersion": "2026-07-28"— a revision that removedinitialize.This was observed over stdio. I have not separately confirmed the HTTP behavior; I mention
ConfigureInitializebecause the handler appears not to be stdio-specific.Additional context
Looking at
McpServerImpl.cs, theConfigureInitializehandler appears to negotiate the version as follows:Because
SupportedProtocolVersionsincludesMcpHttpHeaders.July2026ProtocolVersion("2026-07-28"), this check appears to pass and the client's requested version is echoed. The fallback toNovember2025ProtocolVersionappears to trigger only for versions not in the supported list.The code comment on lines ~373-377 describes the legacy initialize handshake as authoritative:
That makes sense for choosing the legacy session state after a client has entered through
initialize. The part that seems inconsistent with the draft versioning model is allowing that legacy entry point to negotiate a modern protocol revision.One possible fix would be to cap
initializenegotiation atNovember2025ProtocolVersionor the latest supported legacy revision, and return either a downgraded version or an error when a modern version is requested through the legacy handshake.Related:
initialize/notifications/initializedhandshake."initializerequest selects legacy semantics ... as specified by the negotiated legacy protocol version."