You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
On a session negotiated at 2026-07-28, ClientSession.Ping cannot succeed by any route, against the SDK's own server.
Ping is the one remaining Modern-eligible client method that does not call injectRequestMeta, so it sends ping with the MCP-Protocol-Version: 2026-07-28 header and no SEP-2575 _meta, and a conformant server rejects it. Supplying _meta by hand does not rescue it: the request then fails inside the transport with failed to connect (session ID: ): session not found, because a v2 session is necessarily stateless and stateless mode neither reads nor sets Mcp-Session-Id.
Two further consequences make it worse than a failed health check:
Every failed ping takes the whole connection with it. After the failed ping, unrelated calls on the same session fail with connection closed: ... client is closing: sending "ping": .... A caller doing periodic liveness pings therefore kills the sessions it is checking, and in-flight application calls die reporting a ping error they never issued. (Likely the same underlying behaviour as StreamableClientTransport: Connection poisoned by transient errors #683.)
Supplying the envelope unconditionally breaks pre-v2 sessions. A 2025-11-25 session accepts the bare ping and rejects one carrying the reserved _meta keys, so a client cannot simply always send it.
This is adjacent to #1116, but not the same. That issue was closed because SetLoggingLevel was removed from the 2026-07-28 protocol, so its missing injection no longer matters. ping is still in the protocol, so the same omission is a live defect. On current main, injectRequestMeta has 10 call sites and Ping (mcp/client.go:1222) is absent from them:
Self-contained; client and server are both this SDK. A fresh session per case, because a failed ping poisons the connection and contaminates any later probe on it.
Output on v1.7.0 (identical code on current main):
stateful + bare ping negotiated=2025-11-25 ping=OK listTools_after=OK
stateful + envelope negotiated=2025-11-25 ping=ERR listTools_after=ERR
ping err: calling "ping": sending "ping": Bad Request
poisoned: connection closed: calling "tools/list": client is closing: sending "ping": Bad Request
stateless + bare ping negotiated=2026-07-28 ping=ERR listTools_after=ERR
ping err: calling "ping": sending "ping": missing or invalid _meta field "io.modelcontextprotocol/protocolVersion": Bad Request
poisoned: connection closed: calling "tools/list": client is closing: sending "ping": missing or invalid _meta field "io.modelcontextprotocol/protocolVersion": Bad Request
stateless + envelope negotiated=2026-07-28 ping=ERR listTools_after=ERR
ping err: calling "ping": sending "ping": failed to connect (session ID: ): session not found
poisoned: connection closed: calling "tools/list": client is closing: sending "ping": failed to connect (session ID: ): session not found
Expected behavior
Ping succeeds on a 2026-07-28 session against the SDK's own server, and a failed ping does not tear down the connection.
Observed in the wild
A gateway using Ping as its periodic liveness probe against ~30 upstreams: after those upstreams moved to 2026-07-28, every probe failed, dropped the session, and the next sweep redialed — 1,059 reconnects in 37 minutes, roughly one per upstream per sweep. Because the failure also poisons the connection, unrelated in-flight calls intermittently failed with calling "tools/call": sending "ping": .... Both symptoms stopped once the probe skipped v2 sessions entirely.
Additional context
go-sdk v1.7.0; reproduced against current main (same code at mcp/client.go:1222).
Fix presumably mirrors the other ten call sites: guard on cs.usesNewProtocol() and injectRequestMeta. That alone would resolve the bare-ping case, but the stateless session not found path looks separate and would still need addressing for Ping to work on a v2 session.
Describe the bug
On a session negotiated at
2026-07-28,ClientSession.Pingcannot succeed by any route, against the SDK's own server.Pingis the one remaining Modern-eligible client method that does not callinjectRequestMeta, so it sendspingwith theMCP-Protocol-Version: 2026-07-28header and no SEP-2575_meta, and a conformant server rejects it. Supplying_metaby hand does not rescue it: the request then fails inside the transport withfailed to connect (session ID: ): session not found, because a v2 session is necessarily stateless and stateless mode neither reads nor setsMcp-Session-Id.Two further consequences make it worse than a failed health check:
connection closed: ... client is closing: sending "ping": .... A caller doing periodic liveness pings therefore kills the sessions it is checking, and in-flight application calls die reporting a ping error they never issued. (Likely the same underlying behaviour as StreamableClientTransport: Connection poisoned by transient errors #683.)2025-11-25session accepts the bare ping and rejects one carrying the reserved_metakeys, so a client cannot simply always send it.This is adjacent to #1116, but not the same. That issue was closed because
SetLoggingLevelwas removed from the2026-07-28protocol, so its missing injection no longer matters.pingis still in the protocol, so the same omission is a live defect. On currentmain,injectRequestMetahas 10 call sites andPing(mcp/client.go:1222) is absent from them:To Reproduce
Self-contained; client and server are both this SDK. A fresh session per case, because a failed ping poisons the connection and contaminates any later probe on it.
Output on v1.7.0 (identical code on current
main):Expected behavior
Pingsucceeds on a2026-07-28session against the SDK's own server, and a failed ping does not tear down the connection.Observed in the wild
A gateway using
Pingas its periodic liveness probe against ~30 upstreams: after those upstreams moved to2026-07-28, every probe failed, dropped the session, and the next sweep redialed — 1,059 reconnects in 37 minutes, roughly one per upstream per sweep. Because the failure also poisons the connection, unrelated in-flight calls intermittently failed withcalling "tools/call": sending "ping": .... Both symptoms stopped once the probe skipped v2 sessions entirely.Additional context
main(same code atmcp/client.go:1222).cs.usesNewProtocol()andinjectRequestMeta. That alone would resolve the bare-ping case, but the statelesssession not foundpath looks separate and would still need addressing forPingto work on a v2 session.