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
{{ message }}
Repository navigation
mcp: Client.Connect never falls back to initialize when a stdio server ignores server/discover #1332
For protocol ≥ 2026-07-28, Client.Connect sends server/discover first and falls back to initialize on an error, or on a non-modern error from the server. Some handshake-era stdio servers silently ignore requests they don't recognize before initialize, and the stdio transport spec permits that. With such a server, server/discover never gets a reply, so Connect blocks until the caller's context ends and then fails. It never reaches the initialize fallback.
Setting ClientSessionOptions{ProtocolVersion: "2025-11-25"} connects immediately, but then the client can't use 2026-07-28 with any server.
To Reproduce
A stdio server that only answers initialize and tools/list and drops everything else:
ctx, cancel:=context.WithTimeout(context.Background(), 5*time.Second)
defercancel()
_, err:=client.Connect(ctx, &mcp.CommandTransport{Command: exec.Command("python3", "silent.py")}, nil)
// v1.8.0 and main at 53effc0: returns after 5s with context deadline exceeded
Expected behavior
This needs an API decision, so raising it before any PR. One option: bound the discover probe and treat "no reply in time" as a handshake-era server on transports where silence is possible, e.g. ClientSessionOptions.DiscoverTimeout, or a short default for CommandTransport.
Additional context
Found while moving PromptKit's MCP client onto go-sdk v1.8.0 (AltairaLabs/PromptKit#2141). We work around it by restarting the stdio server and connecting with the handshake-era protocol version when discover times out: client.go. Our interop matrix runs the silent server above as a fixture: silent_stdio.py.
I reproduced it on main (53effc0) with an IOTransport test double that drops everything except initialize and tools/list. Connect blocks until the caller's deadline, and the initialize fallback never runs because it reuses the same expired context.
Bounding only the discover probe (context.WithTimeout around the c.discover call, outer ctx kept for initialize) fixes it locally, and the rest of go test ./mcp still passes. The remaining question is where the bound comes from:
No new API: apply a short internal default only to stdio-style transports (CommandTransport, IOTransport), where the spec allows a server to stay silent before initialize. HTTP keeps the current behavior, since an HTTP server always responds.
ClientSessionOptions.DiscoverTimeout: as you suggested, with zero meaning the per-transport default from (1). This would need a proposal.
I'd lean toward (1) as the bug fix, with (2) as a follow-up proposal if people want to tune it. A stdio server that answers discover late is harmless: the client has already sent initialize, which modern servers still accept, and the stray response is dropped.
@guglielmo-san thanks! I opened #1334 with option 1. It bounds the discover probe at 3s (or half of the caller's remaining deadline, if that's shorter) on stdio transports only, and adds tests for a silent server, a short deadline, and a slow server.
Since it's my first PR here, the CI workflows are waiting for a maintainer to approve the run.
Activity
crossxr commentedon Oct 2, 2026
I'd like to work on this.
I reproduced it on main (53effc0) with an
IOTransporttest double that drops everything exceptinitializeandtools/list.Connectblocks until the caller's deadline, and theinitializefallback never runs because it reuses the same expired context.Bounding only the discover probe (
context.WithTimeoutaround thec.discovercall, outer ctx kept forinitialize) fixes it locally, and the rest ofgo test ./mcpstill passes. The remaining question is where the bound comes from:CommandTransport,IOTransport), where the spec allows a server to stay silent beforeinitialize. HTTP keeps the current behavior, since an HTTP server always responds.ClientSessionOptions.DiscoverTimeout: as you suggested, with zero meaning the per-transport default from (1). This would need a proposal.I'd lean toward (1) as the bug fix, with (2) as a follow-up proposal if people want to tune it. A stdio server that answers discover late is harmless: the client has already sent
initialize, which modern servers still accept, and the stray response is dropped.Happy to send a PR for whichever you prefer.
guglielmo-san commentedon Oct 2, 2026
Hi @crossxr I think we can proceed with option 1
crossxr commentedon Oct 2, 2026
@guglielmo-san thanks! I opened #1334 with option 1. It bounds the discover probe at 3s (or half of the caller's remaining deadline, if that's shorter) on stdio transports only, and adds tests for a silent server, a short deadline, and a slow server.
Since it's my first PR here, the CI workflows are waiting for a maintainer to approve the run.
mcp: bound server/discover probe on stdio transports