Repository navigation
mcp: fail Ping fast on 2026-07-28 sessions - #1378
guglielmo-san merged 7 commits into
Conversation
Follow-up to modelcontextprotocol#1249, modelcontextprotocol#1174, and modelcontextprotocol#1130: ping was removed from the 2026-07-28 protocol schema, so a ping sent on a session negotiated at 2026-07-28 can never succeed. Today the client sends the removed method anyway and surfaces the server's rejection - either MethodNotFound at dispatch, or -32020 CodeHeaderMismatch at transports that validate header/meta consistency for a meta-less request (observed against mcp.svelte.dev). Ping now returns a descriptive local error on 2026-07-28 sessions without a round trip, pointing at the capability list requests that do verify liveness. No working behavior is lost: every such ping fails today. The cmd test's readiness check now relies on the connect itself, since ping is unavailable on those sessions. Verified by TestClientPingRemovedOnNewProtocol, which simulates a 2026-07-28 session and asserts no ping request is sent. Signed-off-by: Alexey Zilber <AlexeyZilber@gmail.com>
|
@azilber thank you for reporting this. Contrary to what I said in previous issues that were opened relatively to this case, I might see now the advantage of injecting the _meta also for Ping method. This would allow to have the same failure for both stdio and streamable. |
chrikrah
left a comment
There was a problem hiding this comment.
@azilber, two lines in the body do not match what I see in memory: "every 2026-07-28 ping fails today" and "cannot be reproduced against the SDK's own server". A default Client.Connect over NewInMemoryTransports negotiates 2026-07-28, and Ping succeeds on main. Ping sends params=null, so handle sees a legacy request and skips its removed-method branch. tools/list on the same session carries the envelope. From a scratch test outside the diff, with a receiving middleware on the server:
$ go test ./mcp/ -run TestZZProbePing -count=1 -v # go1.27.1
== main @ 8c761a2
usesNew=true
server got ping params=null
ping err=<nil>
server got tools/list params={"_meta":{...,"io.modelcontextprotocol/protocolVersion":"2026-07-28"}}
== PR @ 0b57f53
usesNew=true
ping err=mcp: ping is not supported on 2026-07-28 sessions (the method was removed); verify liveness with tools/list, resources/list, or prompts/list
server got tools/list params={"_meta":{...,"io.modelcontextprotocol/protocolVersion":"2026-07-28"}}
$ go test ./... -count=1 # probe file removed
main @ 8c761a2: ok github.com/modelcontextprotocol/go-sdk/mcp 32.965s
PR @ 0b57f53: ok github.com/modelcontextprotocol/go-sdk/mcp 33.261s
blocking, if you keep this direction: return a *jsonrpc.Error with CodeMethodNotFound, not a fmt.Errorf. docs/protocol.md promises that code. The server also sends it for a ping that carries the new-protocol meta, so callers match one error with errors.As.
non-blocking: plain Connect already lands on 2026-07-28, and the cmd_test.go change relies on that too. TestClientPingRemovedOnNewProtocol can drop the cs.state write and its comment about the 2025-11-25 cap.
non-blocking: the Ping note in internal/docs/protocol.src.md still says the server answers MethodNotFound. With this change the client answers first, and docs/protocol.md regenerates from that source.
@guglielmo-san, the ping that works in memory today is the missing-envelope behavior from #1249, which you closed as by design. This PR turns it into a local error. Is that the direction you want for #1377?
|
Thanks both — and thanks for the correction, @chrikrah: you're right that today's in-memory ping succeeds (params=null takes the legacy path and skips the removed-method branch), so "no working behavior is lost" in my description was wrong for lenient servers. Both directions under discussion end that success: injecting _meta converts the ping into a new-protocol request, which then hits the removed-method branch too (I prototyped that first — it's what flipped cmd_test from pong to MethodNotFound). On @guglielmo-san's question — I'd still lean fail-fast, for one main reason: injecting the SEP-2575 _meta stamps the request as following 2026-07-28, for a method 2026-07-28 deleted. Both approaches reach the same terminal state on spec-correct servers (live check against mcp.svelte.dev: with _meta injected, the -32020 goes away and the MethodNotFound remains — the server has no ping), but fail-fast gets there without the round trip and lets the error name the liveness alternatives. Per the blocking note I'll return *jsonrpc.Error with CodeMethodNotFound rather than fmt.Errorf, matching docs/protocol.md. That said, the consistency argument for _meta (no client-side special casing; servers decide) is a real one, and the direction is squarely a #1377 call — happy to revise to whichever the WG prefers; the diff is ~10 lines either way. If fail-fast stands I'll also apply the non-blocking nits (drop the state write, fix the comment wording). |
Ping's removed-method guard now returns *jsonrpc.Error with CodeMethodNotFound, matching the documented error contract and the server's own phrasing for removed methods. The test drops its manual state write: a plain Connect over in-memory transports negotiates 2026-07-28 via server/discover, and asserts the typed error and that no ping request is sent. The doc comment now describes the lenient-server behavior (params-less pings currently succeed via the legacy path). Signed-off-by: Alexey Zilber <AlexeyZilber@gmail.com>
| if cs.usesNewProtocol() { | ||
| return &jsonrpc.Error{ | ||
| Code: jsonrpc.CodeMethodNotFound, | ||
| Message: fmt.Sprintf("%q is not supported in the new protocol; verify liveness with tools/list, resources/list, or prompts/list", |
There was a problem hiding this comment.
i would remove the "verify liveness" part, both in the error and in the comment above
|
@azilber ok lets proceed with the fail fast |
Per review: the error and doc comment now match the server's phrasing exactly; naming replacement liveness checks belongs to callers, not the SDK. Signed-off-by: Alexey Zilber <AlexeyZilber@gmail.com>
|
Done — proceeding with the fail-fast per your ruling, and the "verify liveness" hint is removed from both the error message and the doc comment (67f44db); the message now matches the server's phrasing exactly. Naming replacement liveness checks is left to callers — in our CLI that mapping (ping → first declared capability list) lives client-side anyway. Happy to make any further adjustments; otherwise this is ready for another look whenever convenient. |
| // | ||
| // On sessions negotiated at protocol version 2026-07-28 or later, Ping | ||
| // returns a [jsonrpc.Error] with code [jsonrpc.CodeMethodNotFound] without a | ||
| // round trip: the 2026-07-28 protocol schema removes the ping method. (A | ||
| // ping sent without per-request _meta is additionally rejected by transports | ||
| // that validate header/meta consistency, with -32020.) |
There was a problem hiding this comment.
| // | |
| // On sessions negotiated at protocol version 2026-07-28 or later, Ping | |
| // returns a [jsonrpc.Error] with code [jsonrpc.CodeMethodNotFound] without a | |
| // round trip: the 2026-07-28 protocol schema removes the ping method. (A | |
| // ping sent without per-request _meta is additionally rejected by transports | |
| // that validate header/meta consistency, with -32020.) | |
| // | |
| // On sessions negotiated at protocol version 2026-07-28 or later, Ping | |
| // returns a [jsonrpc.Error] with code [jsonrpc.CodeMethodNotFound] without a | |
| // round trip: the 2026-07-28 protocol schema removes the ping method. |
|
Let's also update the internal/docs/protocol.src.md as still says "the server returns MethodNotFound (-32601) for ping" |
Per review: the doc comment drops the _meta/-32020 parenthetical, and the protocol documentation now describes the client-side behavior - Ping returns MethodNotFound locally, without a round trip - instead of attributing the error to the server. docs/protocol.md regenerated via go generate ./internal/docs. Signed-off-by: Alexey Zilber <AlexeyZilber@gmail.com>
|
Both applied in 852eafd:
No behavior change; tests unchanged. |
Updates #1377. Follow-up to #1249 / #1174 / #1130, which closed the missing-
_metareports as by design because the 2026-07-28 protocol schema removes ping.This change completes that ruling on the client side: on sessions negotiated at 2026-07-28,
Pingnow returns a*jsonrpc.ErrorwithCodeMethodNotFoundwithout a round trip, instead of sending the removed method and surfacing whatever the server does with it._metawould equally convert the ping into a new-protocol request that hits the removed-method branch. On strict transports both forms fail today: meta-less with-32020header-mismatch, meta-injected withMethodNotFound(live evidence: mcp.svelte.dev).tools/list,resources/list,prompts/list).TestClientPingRemovedOnNewProtocol, which uses a plainConnect(in-memory transports negotiate 2026-07-28 viaserver/discover) and asserts the typed error, its code, and that no ping request is sent.