Hmbown/CodeWhale · error · anyhow::Error
MCP server '{server}': {method} failed: {error}
Error message
MCP server '{server}': {method} failed: {error} What it means
The MCP server answered the request with a JSON-RPC error object, and the client surfaces it verbatim: 'MCP server '<name>': <method> failed: <error>'. This is a server-side rejection, not a transport failure — the protocol worked. The embedded error is the server's own message/code (e.g. 'Tool not found', 'Invalid params', 'Internal error').
Source
Thrown at crates/mcp/src/stdio_client.rs:378
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 {
continue;
};
if message.get("id").and_then(Value::as_u64) != Some(id) {
continue;
}
if let Some(error) = message.get("error") {
bail!("MCP server '{server}': {method} failed: {error}");
}
return Ok(message.get("result").cloned().unwrap_or(Value::Null));
}
}
/// The child's exit status, when it has one, for appending to a failure
/// message. Polls briefly because both callers run at the moment the child
/// is dying: the write can return EPIPE, or stdout can hit EOF, before the
/// kernel has finished reaping the process. Bounded and error-path-only,
/// so the cost buys a real diagnostic ("exited with status 127" is the
/// difference between a crashed server and a missing one).
fn exit_note(&mut self) -> String {
let deadline = Instant::now() + EXIT_STATUS_GRACE;
loop {
match self.child.try_wait() {
Ok(Some(status)) => return format!(" (process exited with {status})"),
Ok(None) if Instant::now() < deadline => {
thread::sleep(Duration::from_millis(5));View on GitHub (pinned to 0c42157ee5)
Solutions
- Read the embedded error object — its code and message come from the server and name the actual problem
- For unknown-tool errors, list current tools (list_tools) and use the exact advertised name
- For invalid-params errors, match your arguments to the tool's inputSchema from the descriptor
- For credential/quota errors, fix them on the server's configuration side, then retry
Example fix
// before
let v = registry.call_tool("github", "search_code", json!({"q": "repo:x y"}))?;
// after: inspect the server's error and correct the call
match registry.call_tool("github", "search_code", json!({"q": "repo:x y"})) {
Err(e) if e.to_string().contains("failed") => {
let tools = registry.list_tools()?; // verify the tool name and its inputSchema
tracing::warn!("server rejected call: {e:#}");
}
other => other?,
} Defensive patterns
Strategy: try-catch
Validate before calling
// Verify the tool exists with the exact advertised name before calling:
let names: Vec<String> = registry.list_tools()?
.into_iter().map(|t| t.tool_name).collect();
if !names.contains(&tool.to_string()) { bail!("unknown tool {tool}"); } Try / catch
match registry.call_tool(server, tool, args) {
Ok(v) => Ok(v),
Err(err) if err.to_string().contains("failed:") => {
// server-side JSON-RPC error: surface its message; fix args/name based on it
Err(err)
}
Err(err) => Err(err),
} Prevention
- Validate arguments against the tool's inputSchema before invoking
- Pin server versions so tool names/schemas do not drift under you
- Refresh credentials the server depends on before they expire
When it happens
Trigger: Calling a tool name the server does not implement; sending arguments that fail the server's schema validation; the tool's internal operation failing (auth expired on the server's side, upstream API error); the server rejecting the initialize params.
Common situations: Tool renamed or removed in a new server version while callers still use the old name; argument schemas drifted between client and server; expired credentials inside the server (GitHub token, DB password); rate limits or quota errors surfaced by the underlying API the tool wraps.
Related errors
- MCP server '{server}': {method} timed out after {timeout:?}
- MCP server '{server}': process closed stdout before answerin
- MCP error in '{}': {}
- app-server auth token cannot be empty
- MCP server '{}' collides with already-registered server '{ex
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/cd496bfb9aaba2b3.
Report an issue: GitHub.