zeroclaw-labs/zeroclaw · error · anyhow::Error
MCP `{op}` (server `{server_name}`) returned isError: {detai
Error message
MCP `{op}` (server `{server_name}`) returned isError: {detail} What it means
MCP servers report tool-execution failures as a successful JSON-RPC response whose result object carries isError: true; check_result_is_error converts that envelope into an anyhow error carrying the server-supplied detail. This is how 'the tool ran but failed' (bad arguments, missing resource, internal tool error) reaches the caller, as opposed to protocol errors.
Source
Thrown at crates/zeroclaw-tools/src/mcp_client.rs:153
.filter_map(|item| item.get("text").and_then(|t| t.as_str()))
.collect::<Vec<_>>()
.join("\n")
})
.filter(|s: &String| !s.is_empty())
.unwrap_or_else(|| "(no error detail returned by server)".to_string());
let detail = zeroclaw_providers::sanitize_api_error(&detail);
::zeroclaw_log::record!(
WARN,
::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)
.with_outcome(::zeroclaw_log::EventOutcome::Failure)
.with_attrs(::serde_json::json!({
"mcp_server": server_name,
"op": op,
"detail": &detail,
})),
"mcp_client: MCP result returned isError:true"
);
bail!("MCP `{op}` (server `{server_name}`) returned isError: {detail}");
}
// ── Internal server state ──────────────────────────────────────────────────
struct McpServerInner {
config: McpServerConfig,
#[cfg(target_has_atomic = "64")]
next_id: AtomicU64,
#[cfg(not(target_has_atomic = "64"))]
next_id: AtomicU32,
tools: Vec<McpToolDef>,
capabilities: McpServerCapabilities,
}
// ── Recovery barrier ────────────────────────────────────────────────────────
/// Synchronously-published gate that blocks new writes while a post-write
/// outcome-unknown request is being recovered.View on GitHub (pinned to 88bb9c8533)
Solutions
- Read the detail text — the server usually names the offending argument or resource
- Validate arguments against the tool's inputSchema from tools/list before calling
- If the server was upgraded, refresh the cached tool list; field names change between versions
- Retry only when the detail clearly indicates a transient downstream failure
Example fix
// before
let res = server.call_tool("read_file", json!({"path": p})).await?;
// after: match the tool's declared schema first
let schema = tool_input_schema(&server, "read_file").await?;
validate_args(&schema, &args)?;
let res = server.call_tool("read_file", args).await?; Defensive patterns
Strategy: try-catch
Validate before calling
// Validate args against the tool's declared input schema before calling
let tools = list_tools(&server).await?;
let tool = tools.iter().find(|t| t.name == name).context("unknown tool")?;
validate_against_schema(&args, &tool.input_schema)?;
let result = server.call_tool(name, args).await?; Type guard
fn is_tool_is_error(err: &anyhow::Error) -> bool {
err.to_string().contains("returned isError")
} Try / catch
Catch, read the server-supplied detail, and branch: argument problems -> fix args and retry; missing resource -> skip or create it; transient downstream failure -> retry with backoff.
Prevention
- Always validate arguments against the live inputSchema rather than a stale copy
- Re-fetch tools/list after upgrading a server
- Prefer idempotent tool usage so isError retries are safe
When it happens
Trigger: tools/call with arguments violating the tool's input schema; referencing a file/URI the server cannot resolve; the tool's own downstream dependency failing (API key, missing binary); server-side exceptions surfaced as error text.
Common situations: Schema drift after a server upgrade renames a required field; passing strings where the tool expects numbers; paths that exist on the client but not on the server host.
Related errors
- MCP tool `{tool_name}` error {}: {}
- MCP server `{server_name}` rejected initialize: {:?}
- MCP server `{server_name}` timed out after {timeout_secs}s d
- MCP `{rpc_method}` error {}: {}
- MCP server `{server_name}` recovery task failed before writi
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/d5647727decf4f7f.
Report an issue: GitHub.