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 CommandTransport (stdio), the SEP-2575 server/discover probe is unrecoverable against a pre-2026-07-28 server that treats an unexpected first message as fatal: the subprocess exits, and the fallback documented in Client.Connect — "fall back to the legacy initialize handshake on any non-modern error from server/discover" (mcp/client.go) — never gets a chance to run, because there is no process left to send initialize to.
This is not hypothetical. @webclaw/mcp v0.6.19 (rmcp-based) does exactly this, and every dial from a v1.7.0 client fails:
The same binary answers a plain initialize correctly (it negotiates 2025-06-18), and — notably — answers server/discover with a proper -32601after the handshake. Only the pre-initialize path is fatal.
I've filed that as a server-side bug (0xMassi/webclaw#107), and I agree it is primarily their bug: the spec's fallback is built on the server replying. This issue is about the SDK-side blast radius, which I think is worth considering separately.
Why it's worth handling on this side too
For HTTP transports the fallback works as designed — a server that dislikes the probe answers with an HTTP or JSON-RPC error and the client moves on. On stdio the failure mode is categorically different: the peer is a process this SDK spawned, and its death takes the whole connection with it. So a spec-conformant client cannot connect to a non-conformant-but-otherwise-working server, and the user-visible error names server/discover — a method they never asked for — rather than anything actionable.
Repro
Any stdio server that exits on a non-initialize first message. Minimal shape, if you'd like one in-tree: a CommandTransport pointed at a binary that reads one line and calls os.Exit(1) unless the method is initialize; Client.Connect fails with the process exit rather than falling back.
Possible directions
Roughly in order of how much I'd expect maintainers to like them, not of preference:
Treat "connection died during the probe, before any reply" as a discover failure on stdio, and retry the handshake on a fresh process. More invasive — it means the transport, not the session, owns a respawn — so I mention it mostly for completeness.
Document the hazard on CommandTransport / Client.Connect, so embedders know that a probe failure on stdio is not equivalent to one on HTTP.
Happy to send a PR for whichever shape you settle on, once maintainers have weighed in (per CONTRIBUTING, I'm not sending one ahead of that).
Workaround, for anyone searching
Wrap the mcp.Connection returned by the transport and answer server/discover locally with -32601, so the probe never reaches the subprocess and the client takes its own documented fallback path. That's what we ship today, gated behind a flag we intend to delete once (1) lands or the upstream server is fixed.
Environment: go-sdk v1.7.0, Go 1.25, Linux x86_64.
Summary
On a
CommandTransport(stdio), the SEP-2575server/discoverprobe is unrecoverable against a pre-2026-07-28 server that treats an unexpected first message as fatal: the subprocess exits, and the fallback documented inClient.Connect— "fall back to the legacy initialize handshake on any non-modern error fromserver/discover" (mcp/client.go) — never gets a chance to run, because there is no process left to sendinitializeto.This is not hypothetical.
@webclaw/mcpv0.6.19 (rmcp-based) does exactly this, and every dial from a v1.7.0 client fails:The same binary answers a plain
initializecorrectly (it negotiates2025-06-18), and — notably — answersserver/discoverwith a proper-32601after the handshake. Only the pre-initialize path is fatal.I've filed that as a server-side bug (0xMassi/webclaw#107), and I agree it is primarily their bug: the spec's fallback is built on the server replying. This issue is about the SDK-side blast radius, which I think is worth considering separately.
Why it's worth handling on this side too
For HTTP transports the fallback works as designed — a server that dislikes the probe answers with an HTTP or JSON-RPC error and the client moves on. On stdio the failure mode is categorically different: the peer is a process this SDK spawned, and its death takes the whole connection with it. So a spec-conformant client cannot connect to a non-conformant-but-otherwise-working server, and the user-visible error names
server/discover— a method they never asked for — rather than anything actionable.Repro
Any stdio server that exits on a non-
initializefirst message. Minimal shape, if you'd like one in-tree: aCommandTransportpointed at a binary that reads one line and callsos.Exit(1)unless the method isinitialize;Client.Connectfails with the process exit rather than falling back.Possible directions
Roughly in order of how much I'd expect maintainers to like them, not of preference:
ClientSessionOptions.ProtocolVersionexport (No public way for a client to request a specific protocol version #1113, merged in mcp: export ClientSessionOptions.ProtocolVersion #1127 on 2026-08-03, not in v1.7.0). With it, an embedder facing a known-bad upstream can pin2025-11-25and skip the probe entirely. That alone unblocks this case, and I'd be happy for that to be the whole answer.CommandTransport/Client.Connect, so embedders know that a probe failure on stdio is not equivalent to one on HTTP.Happy to send a PR for whichever shape you settle on, once maintainers have weighed in (per CONTRIBUTING, I'm not sending one ahead of that).
Workaround, for anyone searching
Wrap the
mcp.Connectionreturned by the transport and answerserver/discoverlocally with-32601, so the probe never reaches the subprocess and the client takes its own documented fallback path. That's what we ship today, gated behind a flag we intend to delete once (1) lands or the upstream server is fixed.Environment: go-sdk v1.7.0, Go 1.25, Linux x86_64.