Skip to content

Make stdio MCP handshake timeout configurable (currently hardcoded 30s in stdio.rs:237) #20

Description

@hekr4jivs

Summary

The initialize handshake timeout for stdio upstream servers is hardcoded to
30 seconds (src/protocol/stdio.rs:237, unchanged in v0.6.0) and cannot be
overridden per server or globally. Any stdio server whose first response takes
longer than 30s at gateway startup fails the handshake and enters the respawn
loop, never connecting — and the gateway does not bind its listen port until the
initial connection phase settles, so a couple of slow servers delay startup by
minutes.

Where

src/protocol/stdio.rs:237
    let result = tokio::time::timeout(Duration::from_secs(30), rx)

There is a configurable circuit breaker (circuit_breaker_max_crashes,
circuit_breaker_window_secs) and an HTTP request_timeout_secs, but no knob for
the stdio handshake/initialize timeout.

Repro

Configure a stdio server launched via npx with a cold npm/npx cache:

[[servers]]
name = "desktop-commander"
command = "npx"
args = ["@wonderwhy-er/[email protected]"]

On first boot (cold cache) npx resolves + downloads the package over the network
before the MCP server even starts. Under contention from several servers spawning
in parallel this regularly exceeds 30s. Log:

WARN Respawn attempt N/5 for 'desktop-commander' failed: MCP initialize handshake
     failed: Request to 'desktop-commander' timed out after 30s (method=initialize, id=10)
INFO [desktop-commander] stdout closed (server process ended)
WARN Stdio server 'desktop-commander' exited: exit status: 0

The same server, once its cache is warm, handshakes in <1s — so the process is
healthy; the 30s ceiling is simply too tight for a cold-start.

Impact

  • Slow-but-healthy stdio servers (npx cold start, heavy model/embedding init,
    first-run compilation) can never connect, with no config recourse.
  • Gateway startup is delayed while these servers burn through respawn attempts.

Proposed fix

Add a configurable handshake timeout, ideally per-server with a global default:

[[servers]]
name = "desktop-commander"
command = "npx"
args = ["@wonderwhy-er/[email protected]"]
handshake_timeout_secs = 90   # default 30, 0 = no timeout

Fall back to a global default_handshake_timeout_secs when unset, keeping the
current 30s as the default so behavior is unchanged unless opted in.

Workaround (for reference)

Install the package globally and point the server at the resolved binary so there
is no per-boot download:

command = "/opt/homebrew/bin/desktop-commander"
args = []

or front a warm HTTP server instead of stdio. Both avoid the cold-start entirely,
but a configurable timeout is the general fix for genuinely slow-initializing
servers.

Environment

  • MCPlex v0.6.0, macOS (arm64), 5 stdio/http servers, semantic router.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions