Part 8 · One Agent, Five Platforms
When a tool returns "400 Bad Request", a person opens the logs. An agent can't. It guesses: it changes an argument, calls again, tries another tool, and sometimes invents a value to get past the error. Each guess costs tokens and time, and a few of them are dangerous.
A named error removes the guessing. It tells the agent what went wrong in business terms and what to do next.
Part 7 defined the tool contract and showed four errors with their next steps. This part completes the list: every named error the invoice agent can see, what it should do for each, and the limits that stop it looping.
Two kinds of failure, handled in two places
MCP already separates them. Protocol errors, such as an unknown tool or a malformed request, come back as JSON-RPC errors. They're bugs in the integration, and the agent can rarely fix them. Tool execution errors come back inside the tool result with isError: true, so the model can read them and correct course.
There's a third kind worth naming: transient failures, such as a timeout or the ERP being briefly unavailable. Those belong to the integration layer, not the agent. The process API retries them with backoff, as any good integration does, and only reports a failure to the agent once its own retries are used up. The agent shouldn't be the retry engine for your network.
That leaves business errors. Those are what the agent must understand, and they're what this part is about.
The error list
The contract has ten named outcomes. Nine are errors; one is information.
Check the input, retry once:
-
INVOICE_NOT_FOUND: the invoice ID is wrong or not yet in the ERP. Check the ID against the source document, retry once with the corrected value, then route to the AP queue. -
PO_NOT_FOUND: often a typo or a reference read wrongly from the document. Same rule: one corrected retry, then route. -
SUPPLIER_NOT_FOUND: the supplier ID doesn't match the supplier master. Don't guess a similar supplier. One corrected retry, then route to master data.
Wait, don't loop:
-
GR_MISSING: the goods haven't been receipted yet. This is often a timing issue, not a mistake. Put the invoice on hold and let the scheduled re-check pick it up. Never callget_goods_receiptagain in the same run.
Route to a person:
-
PO_CLOSED: procurement decides whether to reopen or reject. The agent summarises the evidence and routes it. -
DUPLICATE_INVOICE: the AP team confirms. The agent never rejects or deletes an invoice on its own, because a near-duplicate can be a legitimate second delivery. -
APPROVER_NOT_FOUND: the approval routing has no approver for this type and amount. That's a configuration gap, so it goes to the AP lead, not back to the agent.
Stop and go back a step:
-
APPROVAL_MISSING: the write was attempted without a validapproval_id. The only correct move isrequest_approval. Never retry the write, and never invent an ID. This is the gate from Part 6 doing its job. -
PERIOD_CLOSED: the accounting period is locked. Nothing the agent does will change that. Stop and escalate to finance.
Information, not an error:
-
TOLERANCE_EXCEEDED: returned byrun_three_way_matchinside a normal result, not withisError: true. It's the reason the agent exists. The agent's next step is to investigate and propose a fix for approval.
Notice the pattern. Only three errors allow a retry, each only once, and only with a corrected input. Everything else is wait, route or stop.
What an error looks like
Every error comes back in the same shape, so the agent can parse it and dashboards can count it:
{
"content": [
{
"type": "text",
"text": "{\"code\": \"GR_MISSING\", \"message\": \"No goods receipt found for PO 4500018822 line 20.\", \"retryable\": false, \"next_action\": \"hold_and_recheck\", \"details\": {\"po_id\": \"4500018822\", \"line\": 20}}"
}
],
"isError": true
}
Five fields, and each has a job:
-
codeis stable. Agents, tests and dashboards depend on it, so it never changes once published. -
messageis written for the model: plain English, specific, and free of stack traces or internal hostnames. -
retryablesays whether calling again could ever help. For business errors it's almost alwaysfalse. -
next_actionnames the expected move:fix_input_and_retry,hold_and_recheck,route_to_person,request_approvalorstop_and_escalate. -
detailscarries the identifiers the next step needs, so the agent doesn't have to look them up again.
The text block holds the JSON because MCP error results are read as content. Keeping the same five fields on every platform is what makes errors comparable in the builds.
Limits that stop loops
Named errors cut most retries, but an agent can still loop between tools. Three limits catch that:
- One corrected retry per tool per invoice. If the second call fails too, route it.
- No identical repeat. The same tool with the same arguments twice in one run is a loop. Stop and hand over.
- A tool-call budget per invoice. When it's used up, the agent stops and passes a summary of what it found to a person.
The budget sits outside the model, in the agent runtime or the process layer, for the same reason the approval check does: a limit the model can talk itself out of isn't a limit.
When the agent hands over, the person should get the invoice, the errors seen, the evidence gathered and the agent's best explanation. A handover with context is still useful work, even if the agent couldn't finish.
How this shows up in the series
In series 1, Part 4 turned Mule APIs into MCP tools with the MCP Connector. Whatever each platform uses in that role here, the same ten outcomes have to reach the agent unchanged.
For each build I'll check three things:
- The platform passes the error result through to the agent intact, including
isErrorand the JSON fields. - The agent follows
next_action: retry, wait, route, request approval or stop. - The loop limits hold.
The golden set in Part 10 includes invoices designed to trigger each error, so the evaluation counts retries and handovers, not just correct answers. Part 9 comes next: why the agent starts with read-only tools and earns write access.
When a tool fails, should your agent retry, ask, or stop?
This series describes a reference build on a fictional company. Protocol details are based on the MCP 2026-07-28 specification as of October 2026; check current specs before you build.

Top comments (0)