What you’ll be able to do
- Return tool errors as structured results the model can reason about
- Use the MCP isError flag to mark a failed call without hiding what failed
- Distinguish an error the agent can act on from one only an operator can
- Design retryable versus terminal failures differently in the tool result
What you need to know
An exception is for your logs; a structured result is for the model
A tool call that throws and lets the runtime turn that into an opaque failure has thrown away the one chance to tell the calling agent something useful. The model didn't see a stack trace — it saw either nothing, or a message with no structure it can reason about. Either way, the agent's next move is a guess.
The architectural fix is to treat "the call failed" as a normal, expected outcome with its own shape, not an exceptional path that bypasses the contract. A tool result for a failure should look like data: what failed, why, and — where it's knowable — whether trying again makes sense.
Before / after: passthrough versus structured failure
Both branches set isError, and both give the agent something it can act on: one implies "wait and retry," the other implies "the input was wrong, don't retry unchanged." A single generic catch collapses that distinction and leaves the agent to guess which kind of failure it's looking at.
Retryable and terminal failures need different shapes
The exam's favorite framing here is a tool that "sometimes returns item not found or rate-limit errors" and asks how the tool should handle them to maximize the agent's ability to recover. The answer is never "retry silently inside the tool until it works" (that hides genuinely terminal failures behind a timeout) and never "pass the raw exception through" (that gives the agent nothing to reason about). It's a structured result naming the failure type, so the agent — which has the full conversation and task context the tool doesn't — decides whether to retry, ask the user, or try a different approach.
- Retryable — rate limits, transient network errors, timeouts. Say so explicitly, and give a wait hint if you have one.
- Terminal — not-found, invalid input, permission denied. Retrying the exact same call won't change the outcome; say what would.
Key concept
A failed tool call is not an exception to route around — it’s a result with its own shape. Structure it so the agent can tell a “try again” failure from a “this input was wrong” failure.
When a scenario asks how a tool should handle intermittent errors "to maximize the agent's ability to recover," the answer under test is a structured result describing the failure — not silent retries and not a raw passthrough.
Practice scenario
Work it through, then open this
Two separate problems are stacked here. First, retrying internally on every failure type treats a permission error the same as a rate limit — five wasted calls before eventually reporting the same unhelpful “failed.” Second, the bare string gives the agent nothing to act on regardless of which kind of failure occurred. The fix: classify the failure at the point it happens, return a structured, typed result immediately for terminal failures, and reserve internal retry for genuinely transient ones — with the result still saying what happened.
Build exercise — Turn one tool’s failures into structured results
Intermediate · 25 min
What you’ll learn
- Classifying a tool’s possible failures as retryable or terminal
- Writing a structured error result the agent can act on
- Spotting where a raw exception currently reaches model-facing content
-
List every way a tool call in your system can fail, and label each as retryable or terminal.
- Why: You can’t design the result shape until you know which failures need which shape.
- You should see: A short table of failure types, most of which currently collapse into one generic error path.
-
Rewrite one failure path to return a structured result with
isError: trueand a message naming the failure type and next step.- Why: This is the difference between an agent that can recover and one that has to ask the user to try again blind.
- You should see: A result the agent could plausibly reason about, versus the raw string or exception it replaced.
Exam traps
Letting an unhandled exception propagate as the tool result
The agent receives noise it can’t act on instead of a structured, typed failure.
Returning an empty or generic failure with no actionable detail
“Failed” tells the agent nothing about whether to retry, rephrase, or stop.
Setting isError without including a message the model can act on
The flag alone tells the agent something went wrong, not what to do about it.
Treating every failure as retryable, or every failure as terminal
Both directions waste the agent’s next move — retrying a permission error or giving up on a rate limit are both wrong.
Logging the error server-side and returning nothing useful to the agent
Server logs help an operator later; they do nothing for the agent that needs to act now.
Exposing internal stack traces or secrets in an error message
Error content is still model-facing content — treat it with the same care as any other tool output.