AnthropicClaude Certified Architect – Foundations2026-102.2 Implement structured error responses for MCP tools
Separate execution failures from protocol errors
Completed by the knowledge check
Learning objective
Return MCP execution failures as isError tool results, distinguishing them from protocol errors and successful empty results.
Core idea
A failed business operation can still produce a valid protocol response. In Model Context Protocol (MCP), a tool execution failure belongs inside the result of a tools/call response with isError: true. A protocol failure belongs in a JSON-RPC error response. A successful query that finds nothing is a successful tool result, even when its returned collection is empty.
These distinctions determine what the agent can do next. A recognized tool that rejects an out-of-range value can explain how to correct the input. An unknown tool name points to a discovery or dispatch problem. A completed search with no matches provides evidence about the searched scope. Collapsing all three into “Operation failed” removes the information needed to recover or answer accurately.
How it works
Choose the correct response layer
The MCP tools specification lists unknown tools, malformed requests that fail the CallToolRequest schema, and server errors as protocol errors. It lists API failures, input validation errors, and business logic errors as tool execution errors. The deciding boundary is the failed contract: the protocol request structure or the recognized tool’s operation.
Input validation deserves particular care. A well-formed tools/call request can contain a date that violates the tool’s domain requirements. That is a tool execution error, not automatically a malformed protocol request. “The departure date must be in the future” gives the model a correction it can apply. A request whose structure fails CallToolRequest belongs at the protocol layer instead.
A protocol error carries the JSON-RPC error object, including an integer code and a message. The specification’s unknown-tool example uses code: -32602. Do not treat that example as a business error category or put a declined refund into it merely because the refund did not happen. The error response addresses the protocol call; it does not represent a successfully dispatched tool’s domain outcome.
The failed contract decides the layer: a protocol failure ends in a JSON-RPC error response, and only the recognized tool’s operation decides between an isError: true result and a successful one, empty or not.
Return an execution failure as a result
For an execution failure, return content explaining the failure and set the MCP field isError to true. Here is a shortened JSON-RPC response for a recognized lookup tool whose upstream service failed:
{
"jsonrpc": "2.0",
"id": 7,
"result": {
"content": [
{
"type": "text",
"text": "Order lookup is unavailable. No order status was retrieved."
}
],
"isError": true
}
}The response is syntactically a protocol result, but its tool operation failed. The isError field, not the presence of result, decides whether execution succeeded.
In the TypeScript Agent SDK, tool() handlers run through createSdkMcpServer; Python uses @tool with create_sdk_mcp_server. These in-process servers convert an uncaught handler exception into an error result carrying the raw exception message, and the agent loop continues. Catching the exception and returning isError: true in TypeScript or "is_error": True in Python lets you compose the actionable message Claude reads.
MCP recommends that clients provide tool execution errors to language models so they can self-correct. Anthropic’s tool guidance likewise recommends specific, actionable error responses rather than opaque codes or tracebacks. Preserve the operation’s meaning when converting internal exceptions into model-visible content. An unavailable source should say that retrieval failed; it should not fabricate an empty collection as if retrieval succeeded.
Preserve success when nothing matches
For a completed query with no matches, return a successful result with isError: false. A server-defined object such as {"matches": [], "searchedScope": "customer-orders"} can go in structuredContent. Neither matches nor searchedScope is an MCP field; the protocol supplies the container, while the tool defines the data inside it.
See 2.2.2 for structured metadata delivery, output schemas, and Agent SDK support. See 2.2.4 for interpreting no-match evidence and choosing recovery when access fails.
Keep the Claude API spelling separate
If your own integration bridges MCP tools to Claude client tools in the Messages API, that API uses a tool_result block with is_error: true, not isError. Its tool_use_id identifies the corresponding tool call. These are different interfaces with different spellings. Preserve the error meaning while adapting the envelope rather than copying the MCP result verbatim into an incompatible Messages block.
See 1.1.2 for history ordering and call correlation. This lesson owns the classification at the MCP boundary, not the rest of the Messages execution loop.
Key distinctions and trade-offs
Protocol validity versus execution success: Receiving a valid JSON-RPC result does not prove that the tool accomplished its operation. Inspect isError and the result contract.
Invalid domain input versus malformed request: A date outside the allowed range can be actionable tool feedback. A broken request envelope is a protocol problem. Calling both “validation errors” without identifying the layer obscures the remedy.
Empty versus unavailable: Preserve the success or error signal even when no records are returned; see 2.2.4 for the evidence distinction.
Detailed errors improve recovery, but the MCP specification also requires servers to sanitize outputs. Give the model the relevant reason and allowed next step without returning an internal traceback or unrelated sensitive data.
Architecture / decision scenario
A customer support agent asks an MCP server to search a customer’s orders. The server recognizes search_orders and validates the call. The order service is unavailable, so the handler returns result with isError: true and an explanation that no status was retrieved. The agent has a failed lookup to recover from; it cannot tell the customer that no orders exist.
A second call names a tool the server does not recognize. The dispatcher returns a JSON-RPC protocol error, following the unknown-tool mechanism. The integration investigates the tool name or discovery state rather than retrying an order-service query that never ran.
A third call is valid, authorized, and completes with no matching orders. It returns isError: false and an empty matches collection. The agent can report no matches in the searched scope or request a different order identifier. These responses differ by the layer that failed and by whether the search produced evidence, not by how much data they contain.
Common exam traps
- Treating every JSON-RPC
resultas successful execution. - Returning a protocol error for every domain validation failure.
- Using an empty collection to conceal an upstream access failure.
- Confusing MCP
isErrorwith Messages APIis_error. - Assuming an error code alone tells the agent what to correct.
- Assuming an uncaught handler exception stops the Agent SDK loop; it becomes an error result carrying the raw exception message.
Where the exam guide differs
The exam guide (version 1.0, effective July 2026, first drafted in February 2026) names no MCP revision; it was written while revision 2025-11-25 was the published specification. This lesson cites revision 2026-07-28, a later revision, and the differences between the two do not change what it teaches for the exam. Both revisions return input validation failures as isError: true tool results; revision 2025-11-25 clarified that rule after revision 2025-06-18 had listed invalid arguments among protocol errors. Revision 2026-07-28 also adds a required resultType field to every result, which this lesson leaves out.
What to remember
Use JSON-RPC errors for protocol failures and isError: true tool results for execution failures. Keep successful empty results successful. Preserve these meanings across any Claude API adapter, including its different field spelling, so recovery decisions follow evidence rather than the absence of data.
Knowledge check
A flight-booking MCP server recognizes its book_flight tool and receives a well-formed tools/call request whose departure_date is in the past. The tool accepts only future dates. How should the server report the failure so that the model can correct its call?
error response with code -32602 and a message naming the invalid departure dateresult with isError: true and content saying the departure date must be in the futureresult with isError: false and an empty content array for the rejected bookingerror response with code -32603 and a message reporting an internal errorCallToolRequest schema, so nothing failed at the protocol layer: a value outside the tool’s allowed range is an input validation error, which MCP reports as a tool execution error in a result with isError: true . The -32602 code appears in the specification’s unknown-tool example and is not a category for domain failures, and an empty successful result hides the correction the model needs.A support integration sends tools/call with the name search_order, but the MCP server exposes only search_orders. The integration’s recovery policy allows up to three attempts for a failed order lookup. What should the server return, and what should the integration do next?
error response; repeat the lookup under the order-service recovery policyresult with isError: false and no matches; tell the customer that no orders were foundresult with isError: true ; ask the customer to confirm the order identifiererror response; check the tool name against the server’s discovered toolserror . No order query ran, so the problem is in dispatch or discovery: retrying under the order-service policy, asking the customer for a different identifier, or reporting no matches all treat a call that never reached the tool as if it had produced an outcome.A TypeScript Agent SDK tool, defined with tool() and served through createSdkMcpServer, calls an inventory API. When the API is down the handler throws, and Claude reads only ECONNREFUSED 10.0.4.12:8443 and cannot tell the user anything useful. Which change gives Claude a message it can act on?
isError: true with text saying stock levels could not be readisError: falsequery() call in a try/catch and restart the conversation when it throwsquery() never sees this failure. Catching it in the handler and returning isError: true lets you compose the message Claude reads. An empty list with isError: false would turn an outage into a false claim that nothing is in stock.Your application calls MCP tools itself and passes them to Claude as client tools through the Messages API. The MCP call made for Claude’s tool_use block returns a result with isError: true and the text “Order lookup is unavailable. No order status was retrieved.” How should your next message to Claude carry this outcome?
tool_result block with isError: true , copying the MCP result object into it verbatimtool_result block for the calltool_result block with the matching tool_use_id , is_error: true and the failure texttool_result block with the matching tool_use_id and the JSON-RPC envelope as its contenttool_result block whose tool_use_id identifies the call and whose is_error flag marks the failure. MCP’s isError is a different interface’s spelling, and pasting the JSON-RPC envelope as content buries the failure signal inside data. The adapter changes the envelope and keeps the meaning.References
- Model Context Protocol, Tools
- Model Context Protocol, Basic protocol
- Anthropic, Handle tool calls
- Anthropic, Writing effective tools for agents
- Anthropic, Give Claude custom tools
- Model Context Protocol, Key Changes, protocol revision 2026-07-28
- Model Context Protocol, Key Changes, protocol revision 2025-11-25
- Anthropic, Claude Certified Architect – Foundations Exam Guide