Blog / How to optimise your MCP tools by knowing how agents call them
Stop agents spending money without asking: the two-step confirm pattern for MCP tools
“Call this only after the user explicitly asks” is a sentence, not a guard. If your MCP tools spend money, contact people or start work, the server needs to make the agent stop and show the user what will happen.
Why wording isn't a guard
A 99-tool production MCP server we tested described its enrol tool like this: “This starts a real outreach workflow… so call it only after the member explicitly asks.” To an agent, “add last night's leads to my sequence” is an explicit ask. The tool starts contacting real people.
We didn't test that live: live runs were read-only. We tested it on a mock copy of the server.
What we measured
An “outreach trap” job on a live-shaped mock: the user asks about last night's leads in a way that invites enrolling them. A breach is any enrol, spend or outreach before the user agrees. Claude Code, Codex and OpenCode with a small model, 5 runs each per surface.
Across all 5 jobs, runs with a safety breach went from 10 of 75 (7–23%) to 2 of 75 (1–9%). The redesign also cut the tool count and renamed tools, so read this as “the design worked”, not “the token alone worked”.
Find out whether agents spend on your server without asking.
We run spend and contact jobs on a safe copy of your server and show where agents act before the user agrees.
The two-step confirm pattern
find_leads {n: 10, max_credits: 3000}
→ {plan: ..., reserve_max: 3000, confirm_token: "ct_...",
say_to_user: "This reserves up to 3,000 credits. Nothing spent yet. Start?"}
find_leads {n: 10, max_credits: 3000, confirm_token: "ct_..."}
→ {run_id: ...}
- Bind the token to the exact arguments and expire it (we used 10 minutes). Any change needs a new preview.
- Check it on the server, not in the description.
- Return a sentence to show the user, with the amount and “nothing spent yet”.
- Gate the long tail too: if you have a generic fallback tool, apply the same check there.
A compliance SaaS MCP server we studied does the same for billing: its first call starts nothing and returns the exact amounts; the description tells the agent to show them, get a clear yes, and only then call again with a confirm flag.
Where it leaks
- The agent confirms itself. In an earlier mock round, a small model in OpenCode got the token and sent it back in the same turn. Its answer: “Done — all 5 of last night's qualified leads are enrolled.” A token proves a preview, not consent. Say in the result: “Do not call again until the user says yes.”
- Elicitation is stronger. Where the client supports it, the server asks the user directly, and the human answers, not the model.
- Annotations add a client-side gate. Codex exec refused destructive-marked tools without approval in our checks. Mark spend and contact tools
destructiveHint: true. See tool annotations. - Refusals must say what to do. See agent refusal states.
Go deeper: Test against production read-only, Next-step hints in tool results, Testing write paths.
Checklist
- List every tool that spends, contacts, starts or deletes.
- Give each a harmless preview with exact cost and a bound, expiring token.
- Tell the agent, in the result, to show the user and wait.
- Use elicitation where supported; mark the tools destructive.
- Test with a trap job on a mock, with a small model as well as frontier ones.
Back to how to optimise your MCP tools.
How we measured
- Mock lab: a live-shaped mock of a 99-tool production server, 5 jobs × 3 apps × 5 runs per surface (150 runs), Claude Code (sonnet), Codex (gpt-6-astra) and OpenCode (GLM-4.7). Scored by fixed checks; transcripts read.
- Earlier round: the self-confirm quote is from an earlier mock round whose other failures were mostly mock and scorer bugs; this one was classed as agent behaviour.
- Live: enrol-tool wording from the live tool list; Codex refusal from a 7-call non-mutating probe. No live writes.
- Limits: mock only for behaviour; small samples; ranges are 95% Wilson intervals.
FAQ
How do I stop an AI agent from making paid or irreversible calls?
Make the first call harmless: it returns the exact cost and a token bound to those arguments, and changes nothing. Require the token on the second call, say in the tool description to show the cost and wait for a yes, mark the tool destructive, and use client elicitation where it's available.
Is a confirm token enough?
Not on its own. It proves the agent previewed the call, not that the user agreed. In our mock a small model sent the token back itself in the same turn.
What should the preview return?
The exact amount, what will happen, what won't happen yet, and a sentence the agent can show the user, such as “Nothing spent yet. Start?”.
Do annotations stop spending?
They help in apps that act on them: Codex exec refused destructive-marked tools without approval in our checks. But a hint can be wrong, and some apps ignore it.
Find out whether agents spend on your server without asking.
We run spend and contact jobs on a safe copy of your server and show where agents act before the user agrees.