D2.1 designed a tool’s happy path; this chapter designs its failure path. When a call goes wrong, the architect decides three things: which channel the failure travels down, what the failure text says, and whether the schema could have prevented it at all. But “which channel” depends on which regime you are in — and conflating the Claude Messages API with MCP is the most common mistake here, so we separate them first.
Two regimes, two spellings
“Structured error” means two related-but-distinct things depending on the surface you are on, and the exam (and real code) punish conflating them.
The casing is the tell: is_error is the Claude Messages API; isError is MCP. The two-channel (isError vs JSON-RPC) split below is an MCP model — the direct Messages API has only the single is_error signal for tool failures.
is_error: true is the canonical failure signal (Messages API)
On the Claude Messages API, a failed tool still returns a tool_result — but flagged. is_error: true is the canonical signal that a tool call failed: Claude folds the error into its next-turn reasoning and may retry.
[Official]
Handle tool calls · AnthropicT1-official original The flag is what turns a failure into a message to the model rather than a dead end — a result whose content reads ConnectionError: the weather service API is not available (HTTP 500) with is_error: true lets the next turn reason about what to do.
[Official]
Handle tool calls · AnthropicT1-official original The design principle is two lines: set is_error: true on the tool_result block, and make the content text actionable.
[Official]
Writing tools for agents · AnthropicT1-official original
Write instructive error messages
The flag says that it failed; the content must say what to do next. The documented principle is explicit: “Write instructive error messages. Instead of generic errors like ‘failed’, include what went wrong and what Claude should try next, e.g., ‘Rate limit exceeded. Retry after 60 seconds.’ This gives Claude the context it needs to recover or adapt without guessing.” [Official] Handle tool calls · AnthropicT1-official original
Execution vs protocol errors: the MCP channel split
Within MCP, a failure travels down one of two channels, and the choice is normative, not stylistic. The specification draws the line: isError: true inside a successful result is for execution errors the model should self-correct on — input validation, API failures, business-logic errors — while a JSON-RPC error response is for protocol errors the model cannot fix, such as an unknown tool or a malformed request.
[Official]
Tools — Model Context Protocol Specification 2025-11-25 · AnthropicT1-official original
The most-tested trap lives on this line: a validation failure belongs in the isError channel, not in a JSON-RPC -32602. The 2025-11-25 spec (per SEP-1303) is explicit that input-validation errors return as isError: true content — for example, Invalid departure date: must be in the future. Current date is 08/08/2025. — so the model can correct and retry.
[Official]
Tools — Model Context Protocol Specification 2025-11-25 · AnthropicT1-official original
Retryability is documented behavior, not a parameter
The model already retries failed calls on its own: “If a tool request is invalid or missing parameters, Claude will retry 2-3 times with corrections before apologizing to the user.” [Official] Handle tool calls · AnthropicT1-official original That loop is why the channel choice and the content quality matter so much — a failure returned as legible error content feeds each retry something to correct against, whereas a protocol error the model cannot read gives it nothing to adjust and burns the budget toward an apology.
Prevent the error: strict: true
The cheapest error to handle is the one that never happens. For the largest class of failures — malformed inputs — there is a prevention switch: “To eliminate invalid tool calls entirely, use strict tool use with strict: true on your tool definitions. This guarantees that tool inputs will always match your schema exactly, preventing missing parameters and type mismatches.”
[Official]
Handle tool calls · AnthropicT1-official original Define tools · AnthropicT1-official original With strict: true the schema-violation error class disappears before it can reach your handler.
Practice
Exercise solutions
(a) Return isError: true content, not a JSON-RPC error. A past departure date is an input-validation / business-logic failure the model can self-correct, and the MCP spec (SEP-1303) routes validation errors to the isError channel, reserving JSON-RPC errors for protocol problems the model cannot fix. (b) Make it actionable, e.g. “Invalid departure date: must be in the future. Current date is 2026-06-02.” (c) No. strict: true guarantees the input matches the schema (a correctly-typed date string), but “must be in the future” is a semantic constraint a JSON Schema type cannot express, so this failure must be caught at runtime and returned as legible error content. (d) Over the Claude Messages API the flag is is_error (snake_case) on the tool_result block — same meaning, different spelling — and there is no JSON-RPC channel at all (protocol problems would be HTTP 400s).
Two things are wrong. (1) Wrong regime: the Claude Messages API has no JSON-RPC error channel — JSON-RPC -32602 is an MCP protocol error. On the direct API a tool failure is a tool_result with is_error: true; protocol-level problems are HTTP errors (e.g. 400), not JSON-RPC. (2) Wrong channel even in MCP: an internal API timeout is an execution error the model could retry against, so even under MCP it belongs in isError: true content, not a protocol -32602. The candidate conflated the two regimes and mis-routed a recoverable error.
Claude retries the call 2–3 times with corrections before apologizing to the user — that is documented default behavior, not something you configure. Making the error content actionable matters because each retry reads that content to decide its correction; there is no retry-count knob, so the legibility of the error text is the only lever you have over whether those 2–3 attempts converge on a fix or burn down to an apology.
Exam essentials
- Two regimes, two spellings: Claude Messages API uses
is_error(snake_case) on atool_result, with one tool-failure signal (protocol problems are HTTP errors). MCP usesisError(camelCase) on aCallToolResult, plus a separate JSON-RPC channel for protocol errors. Don’t conflate them. is_error/isErroris the canonical failure signal — it turns a failed result into a message the model reasons over and may retry against. Flag it and make the content actionable (“Rate limit exceeded. Retry after 60 seconds.” beats"failed").- MCP’s two channels, two audiences —
isError: truecontent = execution errors the model self-corrects (validation, API, business logic); a JSON-RPC error = protocol errors it cannot fix (unknown tool, malformed request). Validation errors go in theisErrorchannel (SEP-1303), never JSON-RPC-32602. - Retry is default behavior, not a parameter — Claude retries 2–3 times with corrections; there is no retry-count knob, so error-content quality is your only lever.
- Prevent with
strict: true— eliminates schema-violation errors entirely; reserve error content for runtime, business-logic, and semantic failures you cannot prevent.