Skip to content

mcp: bound server/discover probe on stdio transports - #1334

Open
crossxr wants to merge 2 commits into
modelcontextprotocol:mainfrom
crossxr:mcp-discover-fallback
Open

crossxr wants to merge 2 commits into
modelcontextprotocol:mainfrom
crossxr:mcp-discover-fallback

Conversation

@crossxr

@crossxr crossxr commented Oct 2, 2026 •

Copy link
Copy Markdown

On stdio transports, a handshake-era server may silently ignore server/discover, which the stdio spec permits. Connect then blocked until the caller's context expired and never reached the initialize fallback, which reused the same expired context.

Connect now bounds the discover probe at 3s, or half of the caller's remaining deadline if that is shorter, when the transport's connection is a newline-delimited stream connection. That covers CommandTransport, IOTransport, StdioTransport and InMemoryTransport, including through LoggingTransport or a custom transport that returns the inner connection. On timeout it falls back to initialize as for any other discover error. HTTP transports are unchanged. ClientSessionOptions.DiscoverTimeout changes the bound: a positive value applies it on all transports, and a negative value disables it.

A modern stdio server that answers discover only after the probe times out still connects through initialize, so it gets the 2025-11-25 protocol. Its late discover reply is dropped.

Fixes #1332

Copy link
Copy Markdown

One edge case appears to leave the original silent-stdio hang intact for custom/wrapped transports.

discoverProbeContext enables the timeout only when isStdioTransport(t) recognizes *CommandTransport, *IOTransport, *StdioTransport, or recursively *LoggingTransport. But Transport is a public interface and the SDK docs explicitly support custom transports. A user-defined decorator around an IOTransport/stdio transport therefore fails the type switch and receives the unbounded discovery context; against a handshake-era server that silently ignores server/discover, Connect can still wait indefinitely.

This is especially easy to hit for instrumentation/filtering wrappers (the codebase already has precedent for wrapped transport/connection types). Could stdio/discover-timeout capability be preserved through a small optional interface/marker, or otherwise made wrapper-safe, instead of relying only on the concrete outer transport type?

A regression can wrap the same silent IOTransport fixture in a custom Transport delegator and assert that fallback still occurs within the configured probe timeout.

AI assistance was used to inspect the patch; no acceptance/merge claim is implied.

@crossxr
crossxr force-pushed the mcp-discover-fallback branch from d8b43e6 to c481cad Compare October 2, 2026 21:05
@crossxr

crossxr commented Oct 2, 2026

Copy link
Copy Markdown
Author

Thanks, good catch. I switched the check from the transport type to the connection it returns. The probe is now bounded whenever the connection is a newline-delimited stream connection (*ioConn, including through LoggingTransport). That covers stdio, IO and in-memory transports, plus any custom transport that delegates and returns the inner connection. I added your suggested regression test, which wraps the silent IOTransport in a custom delegating Transport.

A wrapper that also wraps the Connection still won't be detected. Covering that would need an exported interface, which seems better as a separate proposal if there's interest.

Comment thread mcp/client.go Outdated
return cs, nil
}

var streamDiscoverTimeout = 3 * time.Second // mutable for testing

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 think this should be made configurable

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done. I added ClientSessionOptions.DiscoverTimeout, next to ProtocolVersion:

  • zero keeps the default (3s on stdio, IO and in-memory transports, unbounded elsewhere)
  • a positive value applies on all transports, which also covers custom transports that wrap the Connection
  • a negative value disables the bound

It's still capped at half of the remaining Connect deadline. Since this adds an exported field, let me know if you'd like a separate proposal issue for it.

This branch has not been deployed

No deployments
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.

mcp: Client.Connect never falls back to initialize when a stdio server ignores server/discover

3 participants