When an MCP tool fails, the error response it returns determines whether the agent can recover intelligently or fail blindly. Generic messages like "Operation failed" are useless to an LLM. No signal about what went wrong, whether to retry, or what to try instead.
The MCP protocol provides the isError flag specifically for communicating tool failures back to the agent. Set it and the model knows the execution failed, so it can reason about recovery instead of treating the error text as a normal successful result.
The Four Error Categories
Every tool failure falls into one of four categories. Each demands a different recovery strategy, and the agent needs structured metadata to distinguish them.
1. Transient Errors Timeouts, service unavailability, rate limits. The underlying system is temporarily unreachable but the request itself is valid. Recovery: retry after a brief delay.
{
"isError": true,
"content": [{
"type": "text",
"text": "Service temporarily unavailable"
}],
"errorCategory": "transient",
"isRetryable": true,
"description": "The order database is experiencing high load. The request is valid and should succeed on retry."
}
2. Validation Errors Invalid input format, missing required fields, out-of-range values. The request itself is malformed. Recovery: fix the input, then send a corrected call.
{
"isError": true,
"content": [{
"type": "text",
"text": "Invalid order ID format"
}],
"errorCategory": "validation",
"isRetryable": false,
"description": "Order ID must be in format #NNNNN (e.g. #12345). Received: 'order-abc'. Reformat the ID and call again."
}
isRetryable: false here is not "give up". It means resending this call is pointless: order-abc fails the same format check every time. The agent still recovers, just by correcting the input first - and the description tells it exactly how. The boolean says whether to resend; errorCategory says what to do instead.
3. Business Errors Policy violations, limit exceedances, business rule conflicts. The request is technically valid but violates a business constraint. Recovery: do NOT retry - the same request will always fail. The agent needs an alternative workflow.
{
"isError": true,
"content": [{
"type": "text",
"text": "Refund exceeds policy limit"
}],
"errorCategory": "business",
"isRetryable": false,
"description": "Refund amount of £750 exceeds the £500 automatic refund limit. This requires manager approval. Please escalate to a human agent with the refund details."
}
Note the isRetryable: false flag. Business errors never resolve through retrying - the same policy violation applies every time. The agent has to take a fundamentally different path, usually escalation or an alternative workflow, and a customer-friendly explanation in the description lets it communicate that properly.
4. Permission Errors Access denied, insufficient credentials, authorisation failures. The tool cannot execute because the caller lacks the required permissions. Recovery: escalate or use different credentials.
{
"isError": true,
"content": [{
"type": "text",
"text": "Access denied"
}],
"errorCategory": "permission",
"isRetryable": false,
"description": "The current service account does not have permission to access financial records. Escalate to a senior agent with financial system access."
}
What isRetryable Really Signals
isRetryable answers one narrow question: will resending this exact request work? Only transient errors get true - the call was valid, the system was briefly not. Everything else is false, because something has to change first: the input (validation), the request itself (business), or the caller (permission).
Read isRetryable to decide whether to resend as-is, then read errorCategory to decide what to do when you can't:
| Category | isRetryable |
Recovery |
|---|---|---|
transient |
true |
Resend the same call after a delay |
validation |
false |
Correct the input, send a new call |
business |
false |
Take an alternative path or escalate |
permission |
false |
Retry as a principal with the right access |
The distinction that matters most is between the three false rows. Validation is recoverable by the agent alone. Business and permission are not - a policy limit applies no matter how the request is worded, and a permission error needs a different account, not a better call. false means "not this call again", not "stop".
Current state: where this table comes from
The exam guide (v1.0) states retriable: false for business rule violations and never assigns a value to validation. The false above is the convention the wider ecosystem uses - gRPC treats INVALID_ARGUMENT as non-retryable, and AWS-style retry metadata does the same - applied to a gap the guide leaves open. Expect the exam to test which category a failure belongs to and what recovery it needs, which the guide does specify. If a question turns on the boolean for validation, reason from "will resending this exact call work" and you will land on false. (Verified against the exam guide, August 2026.)
Access Failure vs Valid Empty Result
Of everything in this domain, this is the distinction to nail. The exam tests it directly.
Access failure: The tool couldn't reach the data source. A timeout occurred, authentication failed, or the service was down. The data might exist, but the tool couldn't check. The agent needs to decide whether to retry.
Valid empty result: The tool successfully queried the data source and found no matches. The query executed correctly - there simply is no data matching the criteria. The agent should NOT retry. The answer is "no results found."
Confusing the two breaks recovery logic entirely. Here's how that plays out:
A tool returns an empty array after a customer lookup. The agent retries 3 times, then escalates to a human. Analysis reveals the customer's account simply does not exist.
The tool succeeded. It queried the database, found no matching customer, and correctly returned an empty result. But because the response doesn't distinguish between "I couldn't reach the database" and "I reached the database and found nothing", the agent treats both the same way - as a failure worth retrying.
The fix: structure your tool responses so a successful query with no results looks nothing like a failed query.
// Valid empty result - NOT an error
{
"isError": false,
"content": [{
"type": "text",
"text": "No customer found matching email 'john@example.com'. The query executed successfully but returned no matches."
}],
"resultCount": 0
}
// Access failure - IS an error
{
"isError": true,
"content": [{
"type": "text",
"text": "Could not reach customer database"
}],
"errorCategory": "transient",
"isRetryable": true,
"description": "Connection to the customer database timed out after 5 seconds. The query did not execute."
}
Error Propagation in Multi-Agent Systems
In multi-agent architectures, error handling follows a principle of local recovery with selective propagation:
- Subagents implement local recovery for transient failures. If a web search times out, the search subagent retries before bothering the coordinator.
- Only propagate errors that cannot be resolved locally. If all retries fail, the subagent reports the failure upward.
- Include partial results and what was attempted. The coordinator needs context: "I searched 3 of 5 sources successfully. Sources 4 and 5 timed out. Here are partial results from the 3 successful sources."
This prevents two anti-patterns: silently suppressing errors (returning empty results as success) and terminating entire workflows on a single failure. Both leave the coordinator making decisions blind.
Key Concept
The distinction between access failures (tool could not reach the data source) and valid empty results (tool successfully queried and found nothing) is critical. Confusing the two causes wasted retries and incorrect escalations. The exam tests this directly.