Hmbown/CodeWhale · error · anyhow::Error

MCP server '{server}': {method} timed out after {timeout:?}

Error message

MCP server '{server}': {method} timed out after {timeout:?}

What it means

A JSON-RPC request to a stdio MCP server exceeded its deadline: the loop recomputed the remaining time before waiting on the response channel and found it already exhausted. The timeout value in the message is the per-call bound — 30s (HANDSHAKE_TIMEOUT) for the initialize handshake and 120s (REQUEST_TIMEOUT) for regular requests like tools/call. Lines without the matching JSON-RPC id (banners, log lines, notifications) are skipped and do not reset the clock, so a server that never answers with the right id will always time out.

Source

Thrown at crates/mcp/src/stdio_client.rs:353

            // write loses the race and returns EPIPE. Which one wins is
            // platform- and timing-dependent (macOS reliably reports the write
            // error where Linux reports the EOF), so both report the death the
            // same way rather than leaking a bare "Broken pipe".
            if is_broken_pipe(&err) {
                bail!(
                    "MCP server '{server}': process closed stdin before answering {method}{}",
                    self.exit_note()
                );
            }
            return Err(err)
                .with_context(|| format!("MCP server '{server}': failed to send {method}"));
        }

        let deadline = Instant::now() + timeout;
        loop {
            let remaining = deadline.saturating_duration_since(Instant::now());
            if remaining.is_zero() {
                bail!("MCP server '{server}': {method} timed out after {timeout:?}");
            }
            let line = match self.responses.recv_timeout(remaining) {
                Ok(line) => line,
                Err(RecvTimeoutError::Timeout) => {
                    bail!("MCP server '{server}': {method} timed out after {timeout:?}");
                }
                Err(RecvTimeoutError::Disconnected) => {
                    bail!(
                        "MCP server '{server}': process closed stdout before answering {method}{}",
                        self.exit_note()
                    );
                }
            };

            // Servers occasionally emit banners or log lines on stdout, and
            // notifications carry no id. Both are skipped; only the matching
            // response ends the wait.
            let Ok(message) = serde_json::from_str::<Value>(&line) else {

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. If the tool is legitimately slow, restructure the work (batch smaller, run async server-side) or use spawn_with_timeouts via the crate's testing seam to raise the budget
  2. Run the server manually and time the same request to distinguish 'slow' from 'hung'
  3. Verify the server copies the request id into its response — responses with wrong/missing ids are silently skipped and guarantee a timeout
  4. For npx-style servers, pre-install the package so the handshake does not race package download
Defensive patterns

Strategy: retry

Validate before calling

null

Try / catch

let mut attempt = 0;
loop {
    attempt += 1;
    match client_call(&client, tool, args) {
        Ok(v) => break Ok(v),
        Err(err) if err.to_string().contains("timed out") && attempt < 2 => {
            tokio::time::sleep(Duration::from_secs(2)).await; // server may be slow, not dead
        }
        Err(err) => break Err(err),
    }
}

Prevention

When it happens

Trigger: A tools/call that legitimately runs longer than 120s; a server that hangs (deadlock, waiting on a network resource); a server that replies with a different or missing id so its responses are skipped as noise; initialize taking over 30s on a cold npx install that downloads dependencies first.

Common situations: Long-running tools (bulk scans, codebase indexing, remote builds) exceeding the fixed 120s budget; first-run npx-based servers compiling/installing during the 30s handshake; servers behind a slow network dependency; buggy servers that echo the wrong id.

Understand the failure class

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/da0ee7d9fe4d2715. Report an issue: GitHub.