Blog / Tool results and errors
Retry, fix the arguments, or ask the human? Refusal states agents can act on
When your tool says no, the agent has to decide: fix its call, try again, wait, or stop and ask the user. If every refusal says “ask a human”, a careful agent interrupts the user for its own typo. If none do, a careless one retries a spend. Here's what we found on a production server, and a small set of states that fixes it.
One state for every refusal
A 99-tool production MCP server we tested adds a guidance block to every refusal. One of its states, “human input”, means a person must decide before the agent continues. We sent it the kinds of mistakes agents make, with no changes to data:
| Call | What was wrong | Guidance state |
|---|---|---|
| Update a record with id “not-a-uuid” | Malformed id: the agent's mistake | Needs a person |
| Submit feedback with id “not-a-uuid” | Malformed id | Needs a person |
| Read a record with id “not-a-uuid” | Malformed id | Needs a person |
| Change a record with a stale version | Someone changed it since the read | Needs a person, “do not repeat the change automatically” |
The version conflict is the clearest case. Its text said: “The record changed since you read it … re-read its current version, then retry.” Its guidance said the opposite. An agent that trusts the structured field stops and asks the user about a routine retry. One that trusts the text retries. Which half it sees depends on the app; see structuredContent or text.
Four states agents can act on
| State | Means | Agent should | Examples |
|---|---|---|---|
fix_args | The call was wrong | Fix it from the message and call again | Malformed id, unknown enum value, wrong field name |
retry | The world moved; the call was fine | Re-read (or wait briefly), then retry once | Version conflict, rate limit, timeout |
wrong_state | Not possible yet | Call the named tool that changes the state, or wait for it | Must preview before start, run already in progress |
needs_user | A person must decide | Ask the user one clear question | Spend over a budget, send a message, delete data |
Another production server we looked at uses a similar typed vocabulary (states for missing input, needed confirmation, wrong phase, not open yet and locked) and tells the agent to do what a refusal names once before reporting a problem. The names matter less than the split: the agent's mistake, a transient state, a state to change, a human decision.
Find out what your refusals tell agents to do.
We send your server the mistakes agents actually make, record what each refusal says, and test whether Claude Code, Codex and other apps recover.
Before and after
Before
text: The record changed since you read it. Re-read it, then retry.
guidance: {"state": "human_input",
"reason": "Do not repeat the change automatically."}
After
text: version 3 is stale (current: 4). Call get_record, then retry with expectedVersion 4.
guidance: {"state": "retry", "next": {"do": "get_record", "for": {"id": "r_123"}}}
Before
text: Invalid arguments … (type tree)
guidance: {"state": "human_input"}
After
text: id "not-a-uuid" is not a valid id. Use an id from list_records.
guidance: {"state": "fix_args", "next": {"do": "list_records"}}
Keep the text and the state saying the same thing, so it doesn't matter which half the app shows. More on wording: error messages for AI agents.
Refusals from the agent app
Not every refusal comes from your server. In our live write-shape check, Codex in headless mode refused every tool the server marked destructive, because its approval policy was “never”, unless that tool was approved one by one. That's the app protecting the user, and it depends on your annotations being honest. On the same server, 2 credit-spending tools were marked non-destructive, so that protection wouldn't apply to them. See tool annotations agents rely on.
Go deeper: Results that say what to do next, Confirmation flows for spending tools, The 12,689-token typo.
Rules
- Return refusals as tool results with
isError: true, as the MCP spec recommends, so the model sees them. - Pick one of four states; reserve “needs the user” for real decisions.
- Name the next step: the tool, the value, the id.
- Say “once”: retry or fix once, then report, so agents don't loop.
- Make text and structured guidance agree.
- Test it: send the mistakes agents make and check what each refusal says. Our read-only live jobs never hit these errors, so we haven't measured how often agents stop on them; the mismatch is visible from the responses alone.
All the rules for results and errors: tool results and errors for AI agents.
How we measured
- Refusal capture: 7 calls to a 99-tool production MCP server on 16 Oct 2026: 1 preview, 1 no-op withdraw, 1 stage refusal, 1 version conflict and 3 validation errors. The record was re-read before and after and was unchanged; nothing was spent or sent.
- Malformed ids: 3 calls with “not-a-uuid”, repeated in 3 identical snapshot runs.
- Codex: Codex CLI 0.161.0 in headless exec mode, same day.
- Limits: one server; behaviour after a refusal not measured live. Tool names and texts are simplified.
FAQ
How should an MCP tool tell an agent whether to retry?
Return the refusal as a tool result with isError: true, one line saying what went wrong, and a state the agent can act on: fix the arguments, retry (after re-reading), wait or change state first, or needs the user. Name the exact next step.
When should a tool ask the agent to involve the user?
Only for real decisions: spending money or credits, sending messages, deleting data, or choosing between options the user cares about. Not for typos, wrong ids or stale versions, which the agent can fix itself.
What's wrong with telling agents never to retry?
Agents that follow it stop on mistakes they could fix. On the server we tested, a stale-version conflict, normally fixed by re-reading and retrying, told the agent not to repeat the change.
Should refusal states be machine-readable?
Yes. Put the state in a small field such as guidance.state, and keep the text consistent with it. Our two-marker test showed some apps show the model only structuredContent and others only text, so the essential instruction should appear in both.
Find out what your refusals tell agents to do.
We send your server the mistakes agents actually make, record what each refusal says, and test whether Claude Code, Codex and other apps recover.