What you need to know
Flag the failure
An MCP tool reports a failure by returning its result with isError: true. If a tool instead returns "Error: could not parse document" as an ordinary, successful text result, the agent may treat the error message as the document's content and reason from it. The flag tells the agent "this call failed", not "this is what the document says".
Not all errors are the same
| Category | Example | Right response |
|---|---|---|
| Transient | Timeout, service unavailable | Retry, perhaps after a short wait |
| Validation | Order number in the wrong format | Fix the input or ask the user; retrying unchanged will fail again |
| Business | Refund outside the 45-day window | Do not retry; explain the policy to the customer |
| Permission | Agent not allowed to see payroll data | Do not retry; escalate or explain |
A uniform "Operation failed" hides all of this. The agent cannot tell a timeout (retry) from a policy rejection (explain), so it either retries things that will never succeed or gives up on things that would.
Return structured metadata
{"isError": true,
"content": [{"type": "text", "text": "{\"errorCategory\": \"business\", \"isRetryable\": false, \"message\": \"Order 55107 was delivered 61 days ago; returns close after 45 days.\", \"customerMessage\": \"This order is outside our 45-day return window.\"}"}]}
errorCategorylets the agent pick a recovery strategy.isRetryableprevents wasted retries.- A human-readable
messageexplains what happened; for business errors, a customer-friendly explanation lets the agent tell the customer something accurate.
Recover locally, propagate what you cannot fix
In a multi-agent system, a subagent should retry transient failures itself. Only errors it cannot resolve go up to the coordinator, together with what was attempted and any partial results. The coordinator then decides: try another source, re-scope the task, or report a gap.
Empty is not an error
A search that runs successfully and finds nothing is a valid empty result: "no orders for this customer". A search that could not run is an access failure. Reporting the first as an error triggers pointless retries; reporting the second as an empty success silently hides a gap. Keep them distinct.
In the exam
Look for "Operation failed" everywhere, an agent retrying policy rejections, an error parsed as content, or a subagent that returns "no results" when the search actually timed out. Pick structured categories, the isError flag, local retry for transient failures, and a clear split between failure and empty.
Check your understanding
An MCP tool returns "Error: could not parse document" as a normal text result, and the agent summarises the error as if it were the document. What fixes this?
Show answer
Answer: C. isError marks the result as a failed call. Without it, the text is indistinguishable from content. An empty result would hide the failure instead.
A refund tool returns "Operation failed" for timeouts, bad order numbers and policy rejections alike. What should it return instead?
Show answer
Answer: B. Category and retryability let the agent respond correctly: retry a timeout, correct a bad input, explain a policy rejection.
A search subagent finds no matching documents. How should it report this?
Show answer
Answer: D. A search that ran and found nothing is a valid result. Calling it a failure triggers retries; staying silent loses information. Report it as an empty success.
Apply it: Cedar Ridge's error contract
Cedar Ridge's process_refund tool returns "Operation failed" for everything. Design its error contract.
- List four failure cases the tool can hit and assign each a category.
- Write the error payload for one business error, including a customer-friendly message.
- Write the agent's recovery rule for each category.
Done when: no category leads to a retry that cannot succeed. - Say how an empty result differs from a failure for
get_customer.
Show model answer
1. Cases
| Case | Category | Retryable |
|---|---|---|
| Payment gateway timeout | transient | yes |
| Amount has more than two decimals | validation | no (fix input) |
| Order outside the 45-day window | business | no |
| Agent lacks refund rights for gift cards | permission | no |
2. Payload. isError: true with {"errorCategory": "business", "isRetryable": false, "message": "Order 55107 delivered 2026-08-14, 61 days ago; window is 45 days.", "customerMessage": "This order is outside our 45-day return window, so I can't refund it automatically."}
3. Recovery rules. Transient: retry up to twice with a short wait, then escalate. Validation: correct the input (or ask the customer) and call again. Business: do not retry; explain using customerMessage, and escalate only if a policy exception is requested. Permission: do not retry; escalate with a handoff summary.
4. Empty versus failure. get_customer returning matches: [] means the search ran and found nobody: ask for another identifier. A timeout means the search did not run: it is a transient error and may be retried.