MCP signals tool failure through an isError flag — but the flag alone is close to useless on its own. An agent that sees only "failed" has no way to decide whether to retry, fix its input, explain to the user, or escalate.
Four categories matter, each implying a different agent response:
| Category | Example | Agent should |
|---|---|---|
| Transient | Timeout, service unavailable | Retry |
| Validation | Malformed input | Fix input, retry |
| Business | Policy violation (e.g. refund over limit) | Explain to the user, or escalate |
| Permission | Insufficient access | Escalate or request access |
A well-structured error response returns errorCategory, an isRetryable boolean, and a human-readable description. For business-rule violations specifically, include retriable: false plus a customer-friendly explanation, so the agent can communicate the reason rather than guess at one.
The discriminator: access failure ≠ valid empty result. A search that timed out and a search that legitimately found nothing must never look identical in the response — one needs a retry decision, the other is a genuine finding to report. Uniform, undifferentiated errors ("Operation failed") are actively harmful rather than merely unhelpful: they make an agent retry things that will never succeed (business-rule violations) and give up on things a simple retry would have fixed (transient failures).
Each category implies a different next move. Collapsing all four into one generic error string is what produces the harmful behavior described above.