{"record":{"id":"03c3662e7ebf5227","repo":"Hmbown/CodeWhale","slug":"timed-out-waiting-for-mcp-json-rpc-response-from-s","errorCode":null,"errorMessage":"Timed out waiting for MCP JSON-RPC response from server '{}' after {}s","messagePattern":"Timed out waiting for MCP JSON-RPC response from server '(.+?)' after (.+?)s","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/mcp.rs","lineNumber":2201,"sourceCode":"                Duration::from_secs(self.read_timeout_secs),\n                async {\n                    tokio::select! {\n                        biased;\n                        _ = self.cancel_token.cancelled() => {\n                            anyhow::bail!(\"MCP connection '{}' was cancelled\", self.name)\n                        }\n                        result = self.transport.recv() => result,\n                    }\n                },\n            )\n            .await\n            {\n                Ok(result) => result.inspect_err(|_e| {\n                    self.state = ConnectionState::Disconnected;\n                })?,\n                Err(_) => {\n                    self.state = ConnectionState::Disconnected;\n                    anyhow::bail!(\n                        \"Timed out waiting for MCP JSON-RPC response from server '{}' after {}s\",\n                        self.name,\n                        self.read_timeout_secs\n                    );\n                }\n            };\n            let value: serde_json::Value = match serde_json::from_slice(&bytes) {\n                Ok(value) => value,\n                Err(err) => {\n                    self.state = ConnectionState::Disconnected;\n                    let preview = if self.config.reviewed_plugin.is_some() {\n                        \"<server details suppressed for reviewed plugin>\".to_string()\n                    } else {\n                        invalid_json_preview(&bytes)\n                    };\n                    return Err(err).with_context(|| {\n                        format!(\n                            \"Invalid MCP JSON-RPC message from server '{}': {}\",","sourceCodeStart":2183,"sourceCodeEnd":2219,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/mcp.rs#L2183-L2219","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (.mcp.json)\n{\"servers\": {\"slow-search\": {\"command\": \"searchd\"}}}\n// tool calls time out after the default read timeout\n\n// after\n{\"servers\": {\"slow-search\": {\"command\": \"searchd\", \"read_timeout_secs\": 120}}}\n// or globally: {\"timeouts\": {\"read_secs\": 120}, \"servers\": {...}}","handlingStrategy":"retry","validationCode":"// Rust: probe responsiveness before dispatching slow work\nasync fn server_responds_within(pool: &McpPool, name: &str, secs: u64) -> bool {\n    tokio::time::timeout(Duration::from_secs(secs), async {\n        let _ = pool.get_or_connect(name).await?.tools().to_vec();\n        Ok::<_, anyhow::Error>(())\n    }).await.map(|r| r.is_ok()).unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":"// Rust: retry with backoff, give up after N attempts\nfor attempt in 0..3 {\n    match pool.get_or_connect(server).await?.call_tool(n, a, t).await {\n        Err(e) if e.to_string().contains(\"Timed out waiting for MCP JSON-RPC\") => {\n            tokio::time::sleep(Duration::from_secs(2u64.pow(attempt))).await;\n            continue;\n        }\n        other => return other,\n    }\n}\nbail!(\"server '{server}' unresponsive after retries\")","preventionTips":["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."],"tags":["mcp","timeout","network","config"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}