Skip to content

2.2 Structured Error Responses

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.

Every tool failure falls into one of four categories. Each demands a different recovery strategy, and the agent needs structured metadata to distinguish them.

One shape note before the examples. errorCategory, isRetryable and description are an application-level convention, not part of the MCP envelope: the protocol’s CallToolResult defines only content, structuredContent and isError. The examples below carry the metadata in structuredContent, which is where structured data belongs. The exam guide names these four categories and the isRetryable boolean, so know them by name; just do not expect to find them in the MCP specification.

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"
}],
"structuredContent": {
"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"
}],
"structuredContent": {
"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"
}],
"structuredContent": {
"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"
}],
"structuredContent": {
"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."
}
}

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”.

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."
}],
"structuredContent": {
"resultCount": 0
}
}
// Access failure — IS an error
{
"isError": true,
"content": [{
"type": "text",
"text": "Could not reach customer database"
}],
"structuredContent": {
"errorCategory": "transient",
"isRetryable": true,
"description": "Connection to the customer database timed out after 5 seconds. The query did not execute."
}
}

In multi-agent architectures, error handling follows a principle of local recovery with selective propagation:

  1. Subagents implement local recovery for transient failures. If a web search times out, the search subagent retries before bothering the coordinator.
  2. Only propagate errors that cannot be resolved locally. If all retries fail, the subagent reports the failure upward.
  3. 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.

A tool returns an empty array after a customer lookup. The agent retries 3 times, then escalates to a human agent. Analysis shows the customer’s account simply does not exist. What is the root cause of this wasted effort?

  • A. The retry limit is too low. Raising it to 5 attempts would give the lookup enough chances to return the account before escalation triggers.
  • B. The system prompt should instruct the agent never to retry a customer lookup, so that every failed search escalates to a human immediately.
  • C. The escalation threshold is too aggressive. The agent should exhaust more retries before involving a human in the loop.
  • D. The tool does not distinguish between access failures and valid empty results, so the agent treats no matches as a retriable failure.
Answer & explanation

Correct: D

  • A — More retries make the problem worse. The tool succeeded — it found no matching customer. Retrying a successful query with no matches will never produce different results.
  • B — Hard-coding retry rules per tool in the system prompt is brittle and does not generalise. The proper fix is structured error metadata that tells the agent whether the result is retryable.
  • C — The problem is not the escalation threshold. The problem is that the agent retries at all. A valid empty result requires no retry and no escalation — it is the correct answer.
  • D — The tool successfully queried the data source and found no matches. This is a valid empty result, not an access failure. Without structured metadata distinguishing these two cases, the agent treats both as failures.

Five exam-style multiple-choice questions on Structured Error Responses. Pick an answer, then open the explanation.

A tool returns an empty array after a customer lookup. The agent retries 3 times, then escalates to a human agent. Analysis shows the customer’s account simply does not exist. What is the root cause of this wasted effort?

  • A. The tool does not distinguish an access failure from a valid empty result, so the agent retries a success.
  • B. The retry limit is too low, and raising it to five would resolve this.
  • C. The escalation threshold is too aggressive; the agent should exhaust considerably more retries before it involves a human operator.
  • D. The system prompt should instruct the agent not to retry customer lookups.
Answer & explanation

Correct: A

  • A is correct because the tool queried the data source successfully and found no matches. That is a valid empty result rather than an access failure, but without structured metadata separating the two, the agent reads both as something worth retrying.
  • B is wrong because more retries make it worse. The query already succeeded, so repeating it returns the same empty array every time.
  • C is wrong because the escalation threshold is not the problem. The agent should not be retrying at all, and a valid empty result needs neither retry nor escalation.
  • D is wrong because hard-coding per-tool retry rules in the system prompt is brittle and does not generalise to the next tool. Structured error metadata is the durable fix.

A refund tool returns an error: “Refund amount of 750 pounds exceeds the 500 pounds automatic refund limit.” The agent retries 3 times. Why does each retry fail?

  • A. The retry delay is too short for the rule to update.
  • B. The tool is not returning the isError flag, so the agent never recognises that the call has failed at all.
  • C. The agent should automatically reduce the refund to the 500-pound limit and retry the call at the lower amount.
  • D. This is a business error, and the same policy applies on every attempt, so the agent should escalate.
Answer & explanation

Correct: D

  • D is correct because business errors such as policy violations and limit breaches are non-retryable by nature. The 500-pound limit applies identically on every attempt, so the agent has to take an alternative path, normally escalation to a manager.
  • A is wrong because business rules do not change between retries inside a session. The limit is policy, not a transient condition.
  • B is wrong because the isError flag is not the gap. The metadata fails to say isRetryable: false, so the agent treats a permanent refusal as a temporary one.
  • C is wrong because the agent should not quietly reduce the refund. The customer asked for 750 pounds, and only a human can approve or refuse that amount.

Which combination of error metadata fields enables an agent to make appropriate recovery decisions for any tool failure?

  • A. errorCode (integer), errorMessage (string), and timestamp (ISO date).
  • B. httpStatus (integer), retryAfter (seconds), and errorBody (JSON) carried straight through from the upstream service.
  • C. errorCategory (transient/validation/business/permission), isRetryable (boolean), and description.
  • D. severity (low, medium or high), businessImpact (free-text string), and resolution (free-text string).
Answer & explanation

Correct: C

  • C is correct because those three cover everything the agent has to decide: errorCategory says what kind of failure occurred, isRetryable says whether to retry or take an alternative path, and description carries human-readable guidance for recovery or escalation.
  • A is wrong because a code and a timestamp say nothing about whether to retry or what to do instead.
  • B is wrong because HTTP-centric metadata does not map cleanly onto MCP tool responses, and retryAfter speaks only to transient failures. Business and permission failures are left uncovered.
  • D is wrong because severity does not imply retryability. A high-severity transient error may be retryable while a low-severity business error is not.

In a multi-agent system, a web search subagent encounters a timeout on 2 of 5 sources. It has successful results from the other 3 sources. What should the subagent do?

  • A. Return only the 3 successful results, presenting them as though all 5 sources had been searched successfully.
  • B. Report partial results from the 3 sources that succeeded, noting that sources 4 and 5 timed out.
  • C. Report failure to the coordinator and discard every result, including those already gathered successfully.
  • D. Retry all 5 sources from scratch to guarantee coverage.
Answer & explanation

Correct: B

  • B is correct because the coordinator needs the findings and the shape of what was attempted: three of five sources searched, sources four and five timed out, here is what came back. That is what lets it decide whether to proceed, retry the two, or widen the search.
  • A is wrong because silently suppressing the timeouts hides failure information. The coordinator can no longer tell “nothing on those sources” from “could not reach those sources”.
  • C is wrong because discarding good results ends the whole workflow over a partial failure. Three sources’ worth of findings still have value.
  • D is wrong because retrying all five throws away work that already succeeded. Only sources four and five need another attempt.

A tool receives an identifier “order-abc” when the expected format is #NNNNN (e.g., #12345). The tool should return:

  • A. isError: true, errorCategory: “transient”, isRetryable: true, with a message telling the agent to retry later.
  • B. isError: false, with an empty result set indicating that no order matching that reference number was found.
  • C. isError: true, errorCategory: “business”, isRetryable: false, with a message telling the agent to escalate to a human.
  • D. isError: true, errorCategory: “validation”, isRetryable: false, with the expected format in the message.
Answer & explanation

Correct: D

  • D is correct because the format is wrong, which makes this a validation failure. isRetryable is false because resending “order-abc” fails the same check every time. That is not a dead end: the category tells the agent to reformat the identifier to #NNNNN and issue a new call, and the message supplies the format it needs.
  • A is wrong because nothing is temporarily unavailable. Waiting and resending the same malformed identifier produces the same error, which is exactly what isRetryable: true would invite.
  • B is wrong because an empty result hides the real problem. The tool did not search and find nothing; it could not search at all, and that is exactly the distinction the category exists to preserve.
  • C is wrong because no business rule was violated. A malformed identifier is a fixable input problem, not something that needs a human.