Describe the bug
The Go 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: 65ece5964304528e7568507e2b17351824ef73fc
- Server used:
conformance/everything-server
- Transport: stdio
To Reproduce
Steps to reproduce the behavior:
- Start the Go 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":{"capabilities":{"completions":{},"logging":{},"prompts":{"listChanged":true},"resources":{"listChanged":true,"subscribe":true},"tools":{"listChanged":true}},"protocolVersion":"2026-07-28","serverInfo":{"name":"mcp-conformance-test-server","version":"1.0.0"}}}
The response echoes "protocolVersion": "2026-07-28" — a revision that no longer defines the initialize handshake.
Additional context
Looking at mcp/shared.go (~line 53), the negotiatedVersion function has a comment that says: "Cap the supported versions at the legacy protocolVersion20251125, as this method is used by the initialize method which is deprecated in version protocolVersion20260728." However, the function body does not appear to implement this cap: it returns clientVersion as-is when the version is found in supportedProtocolVersions, which includes "2026-07-28".
func negotiatedVersion(clientVersion string) string {
// Cap the supported versions at the legacy protocolVersion20251125 ...
if !slices.Contains(supportedProtocolVersions, clientVersion) {
return protocolVersion20251125
}
return clientVersion // ← "2026-07-28" passes through uncapped
}
This looks like the comment describes the intended behavior, but the code does not enforce it. If the cap were applied, initialize would negotiate "2025-11-25" for a "2026-07-28" request, which would appear consistent with the spec's "negotiated legacy protocol version" model.
As a supporting observation: the InitializeResult echoed from this path also omits resultType, a field that the 2026-07-28 schema requires on all result objects. This is consistent with the session being in a mixed state — the negotiated version says 2026-07-28, but the response structure follows legacy semantics. I am tracking the resultType omission separately.
Related spec references:
Describe the bug
The Go 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:65ece5964304528e7568507e2b17351824ef73fcconformance/everything-serverTo 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":{"capabilities":{"completions":{},"logging":{},"prompts":{"listChanged":true},"resources":{"listChanged":true,"subscribe":true},"tools":{"listChanged":true}},"protocolVersion":"2026-07-28","serverInfo":{"name":"mcp-conformance-test-server","version":"1.0.0"}}}The response echoes
"protocolVersion": "2026-07-28"— a revision that no longer defines theinitializehandshake.Additional context
Looking at
mcp/shared.go(~line 53), thenegotiatedVersionfunction has a comment that says: "Cap the supported versions at the legacy protocolVersion20251125, as this method is used by the initialize method which is deprecated in version protocolVersion20260728." However, the function body does not appear to implement this cap: it returnsclientVersionas-is when the version is found insupportedProtocolVersions, which includes"2026-07-28".This looks like the comment describes the intended behavior, but the code does not enforce it. If the cap were applied,
initializewould negotiate"2025-11-25"for a"2026-07-28"request, which would appear consistent with the spec's "negotiated legacy protocol version" model.As a supporting observation: the
InitializeResultechoed from this path also omitsresultType, a field that the 2026-07-28 schema requires on all result objects. This is consistent with the session being in a mixed state — the negotiated version says 2026-07-28, but the response structure follows legacy semantics. I am tracking theresultTypeomission separately.Related spec references:
initialize/notifications/initializedhandshake."initializerequest selects legacy semantics ... as specified by the negotiated legacy protocol version."