Skip to content

mcp: fail Ping fast on 2026-07-28 sessions - #1378

Merged
guglielmo-san merged 7 commits into
modelcontextprotocol:mainfrom
azilber:fix/ping-fail-fast-new-protocol
Oct 15, 2026
Merged

guglielmo-san merged 7 commits into
modelcontextprotocol:mainfrom
azilber:fix/ping-fail-fast-new-protocol

Conversation

@azilber

@azilber azilber commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Updates #1377. Follow-up to #1249 / #1174 / #1130, which closed the missing-_meta reports 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, Ping now returns a *jsonrpc.Error with CodeMethodNotFound without a round trip, instead of sending the removed method and surfacing whatever the server does with it.

  • Corrected (per review): on lenient servers a params-less ping currently succeeds (it takes the legacy path and skips the removed-method branch), so this is a behavior change for that case — as any fix in this area must be, since injecting _meta would 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 -32020 header-mismatch, meta-injected with MethodNotFound (live evidence: mcp.svelte.dev).
  • The error message mirrors the server's own phrasing for removed methods and names the capability list requests that do verify liveness (tools/list, resources/list, prompts/list).
  • The cmd test's readiness check now relies on the connect itself (handshake or discovery round trip), since ping is unavailable on those sessions.
  • Verified by TestClientPingRemovedOnNewProtocol, which uses a plain Connect (in-memory transports negotiate 2026-07-28 via server/discover) and asserts the typed error, its code, and that no ping request is sent.

azilber and others added 2 commits October 12, 2026 12:35
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>
@guglielmo-san

Copy link
Copy Markdown
Contributor

@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.
What do you think?

@chrikrah chrikrah left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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?

@azilber

azilber commented Oct 13, 2026

Copy link
Copy Markdown
Contributor Author

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).

azilber and others added 2 commits October 13, 2026 11:51
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>
Comment thread mcp/client.go Outdated
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",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would remove the "verify liveness" part, both in the error and in the comment above

@guglielmo-san

Copy link
Copy Markdown
Contributor

@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>
@azilber

azilber commented Oct 13, 2026

Copy link
Copy Markdown
Contributor Author

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.

Comment thread mcp/client.go Outdated
Comment on lines +1265 to +1270
//
// 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.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//
// 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.

@guglielmo-san

Copy link
Copy Markdown
Contributor

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>
@azilber

azilber commented Oct 15, 2026

Copy link
Copy Markdown
Contributor Author

Both applied in 852eafd:

  • The Ping doc comment is now exactly the suggested two sentences (the _meta/-32020 parenthetical is gone).
  • internal/docs/protocol.src.md no longer attributes the error to the server: the note now reads "when a session negotiates 2026-07-28 or later, Ping returns MethodNotFound (-32601) locally, without a round trip, and KeepAlive should not be enabled." docs/protocol.md regenerated with go generate ./internal/docs.

No behavior change; tests unchanged.

@guglielmo-san
guglielmo-san merged commit 37ebae3 into modelcontextprotocol:main Oct 15, 2026
10 checks passed
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.

3 participants