Hmbown/CodeWhale · error · anyhow::Error

Timed out waiting for MCP JSON-RPC response from server '{}'

Error message

Timed out waiting for MCP JSON-RPC response from server '{}' after {}s

What it means

recv wraps transport.recv() in tokio::time::timeout(read_timeout_secs); when the server sends no bytes within that window the connection is marked Disconnected and this error is returned. read_timeout_secs comes from the server config's effective read timeout (per-server override or the global timeouts block), so the '{}'s value in the message is the effective per-connection limit.

Source

Thrown at crates/tui/src/mcp.rs:2201

                Duration::from_secs(self.read_timeout_secs),
                async {
                    tokio::select! {
                        biased;
                        _ = self.cancel_token.cancelled() => {
                            anyhow::bail!("MCP connection '{}' was cancelled", self.name)
                        }
                        result = self.transport.recv() => result,
                    }
                },
            )
            .await
            {
                Ok(result) => result.inspect_err(|_e| {
                    self.state = ConnectionState::Disconnected;
                })?,
                Err(_) => {
                    self.state = ConnectionState::Disconnected;
                    anyhow::bail!(
                        "Timed out waiting for MCP JSON-RPC response from server '{}' after {}s",
                        self.name,
                        self.read_timeout_secs
                    );
                }
            };
            let value: serde_json::Value = match serde_json::from_slice(&bytes) {
                Ok(value) => value,
                Err(err) => {
                    self.state = ConnectionState::Disconnected;
                    let preview = if self.config.reviewed_plugin.is_some() {
                        "<server details suppressed for reviewed plugin>".to_string()
                    } else {
                        invalid_json_preview(&bytes)
                    };
                    return Err(err).with_context(|| {
                        format!(
                            "Invalid MCP JSON-RPC message from server '{}': {}",

View on GitHub (pinned to 8880682c63)

Solutions

  1. Raise the timeout: set a per-server read timeout (or the global timeouts read value) in the MCP config above the server's worst-case response time.
  2. Verify the server actually responds: run it manually and time a tools/list or call_tool round-trip.
  3. For stdio servers, check the child process is alive and not blocked on stdin/stdout buffering; for HTTP servers check the endpoint with curl.
  4. Note the connection is marked Disconnected — reconnect via get_or_connect after fixing the timeout rather than reusing the handle.

Example fix

// before (.mcp.json)
{"servers": {"slow-search": {"command": "searchd"}}}
// tool calls time out after the default read timeout

// after
{"servers": {"slow-search": {"command": "searchd", "read_timeout_secs": 120}}}
// or globally: {"timeouts": {"read_secs": 120}, "servers": {...}}
Defensive patterns

Strategy: retry

Validate before calling

// Rust: probe responsiveness before dispatching slow work
async fn server_responds_within(pool: &McpPool, name: &str, secs: u64) -> bool {
    tokio::time::timeout(Duration::from_secs(secs), async {
        let _ = pool.get_or_connect(name).await?.tools().to_vec();
        Ok::<_, anyhow::Error>(())
    }).await.map(|r| r.is_ok()).unwrap_or(false)
}

Try / catch

// Rust: retry with backoff, give up after N attempts
for attempt in 0..3 {
    match pool.get_or_connect(server).await?.call_tool(n, a, t).await {
        Err(e) if e.to_string().contains("Timed out waiting for MCP JSON-RPC") => {
            tokio::time::sleep(Duration::from_secs(2u64.pow(attempt))).await;
            continue;
        }
        other => return other,
    }
}
bail!("server '{server}' unresponsive after retries")

Prevention

When it happens

Trigger: Any request/response round-trip where the MCP server takes longer than read_timeout_secs to produce the matching response: slow tool execution, a hung stdio child process, an HTTP server that stalls the SSE/streamable stream, or a dead network path that never RSTs.

Common situations: LLM-facing tools that run multi-second queries (code search, DB queries) against the default read timeout; MCP server blocked on its own downstream dependency; containerized servers with cold starts; proxies/firewalls dropping idle streams silently so the client blocks until timeout.

Understand the failure class

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/03c3662e7ebf5227. Report an issue: GitHub.