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
- 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.
- Verify the server actually responds: run it manually and time a tools/list or call_tool round-trip.
- For stdio servers, check the child process is alive and not blocked on stdin/stdout buffering; for HTTP servers check the endpoint with curl.
- 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
- Set per-server read timeouts sized to the server's slowest realistic tool, not the default.
- Mark long-running tools as such so callers raise the per-call timeout.
- Remember the connection is marked Disconnected after this error — always go back through get_or_connect.
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
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- GET timeout
- SSE endpoint not received within {}ms
- invalid JSON payload at key {MCP_SERVER_DEFINITIONS_KEY}; co
- invalid MCP server definition list in key {MCP_SERVER_DEFINI
- SSE stream idle timeout after {}s — no data received (bytes_
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/03c3662e7ebf5227.
Report an issue: GitHub.