We build the MCP server for Wiro AI, an API platform where one account can run image, video, audio and LLM models, chain them into workflows and deploy hosted agents. Most of those actions cost money, and the thing calling our tools is a language model that can retry, guess or misread a reply.
So the hardest design question was not which tools to expose. It was how to let an assistant spend money on a user's behalf without ever spending it by surprise. These are the patterns we ended up with. None of them are specific to our product, so they should carry over to any MCP server that touches billing.
1. A known charge returns a quote, not a charge
Some of our tools charge a fixed, known amount: deploying an agent, changing its plan, adding skills to it. Called without confirm: true, these tools do nothing except return a quote: the amount in USD, what will happen and which wallet pays.
The quote ends with a nextAction: the same tool, the same arguments plus confirm: true and confirm_charge_usd set to the quoted amount, and a short reason. The assistant shows the quote, waits for a clear yes, and only then makes that call.
2. The server prices it again on confirm
The confirmed call is not trusted blindly. The server works out the price again, and if it changed since the quote, nothing is charged and a new quote comes back. The assistant has to show the new number and ask again.
For a prorated upgrade the exact amount is only known at charge time, so there confirm_charge_usd means "the most the user agreed to pay". One tool, apply_agent_skills, takes an idempotency key from the quote instead of an amount.
3. Write the rules into the server instructions
An MCP server can send instructions to the client, and we use them for the money rules. A few lines from ours:
Never set confirm: true on your own, and never reuse a yes for a different charge or change. A yes given before you showed the amount, such as "just upgrade me", does not cover it. For a batch, one yes covers exactly the list or total you showed.
The "just upgrade me" case matters because people often agree before they know the price, and a model will happily treat that as consent.
4. Deletions get the same shape
The same two-step call covers anything you cannot take back: deleting a workflow or an agent, cancelling a plan, replacing a workflow's contents, disconnecting an account. The first call describes the change. Only confirm: true makes it happen.
5. Long jobs: bounded waits and an explicit next step
Model runs, workflow runs and agent replies can take minutes. Many clients cut off a tool call that blocks that long, and an assistant that sees a timeout tends to call the starting tool again. For a paid model run, that bills a second job.
Our tools that wait stop after 45 seconds by default and return the job's state plus a nextAction. This one comes straight from our server:
{
"nextAction": {
"tool": "wait_for_task",
"arguments": { "tasktoken": "<task token>" },
"reason": "Continue this exact task. Do not call run_model again."
}
}
The reason is written for the model, and it says the important part out loud. If the client gave up before a result came back, the instructions tell the assistant to look the job up in the task list first, and to offer a new run only if it is not there.
6. Usage-priced work gets a one-line estimate
Model runs are priced by usage (per request, second, token or output), so there is no exact quote to confirm. The rule here is softer: the assistant states the expected cost in one line before it runs, goes ahead when the user asked for exactly this work, and asks first for large or open-ended jobs and for runs the user did not ask for, such as a retry.
What we would tell anyone building this
- Keep the price on the server. The model relays a quote; it never works one out.
- Make the next step structured. A
nextActionwith a tool name and exact arguments is easier for a model to follow than a sentence like "please confirm". - Treat a timeout as "unknown", not "failed". A retry after a timeout is the easiest way to pay twice.
- Write the money rules down in the server instructions, in plain words, including the cases that surprised you.
The server is open source under the MIT license: https://github.com/wiroai/Wiro-MCP. The docs for the hosted version are at https://wiro.ai/docs/wiro-mcp-server.
Top comments (1)
Pricing again on confirm and sending a new quote if it changed is the step most servers would skip, and it is the one that stops a model from replaying an old confirm. Passing confirm_charge_usd as the quoted amount also makes the yes specific to a number rather than a general approval. How do you handle a quote the user confirms hours later?