Inspiration

Agents now call paid tools: customs duty, VAT registries, job feeds, code search. Most of the time the agent holds the master API key. One retry loop can spend the whole quota, the agent can reach any tool on the account, and finance has no record that ties a charge to an agent and an input.

What it does

Agent Clearing Desk is a spend gate between agents and paid tools, over REST and MCP. Each agent gets its own cd_live_ key with a hard budget, a tool allowlist and an optional daily or monthly reset. Every call takes one clearing path:

  1. Unknown tool: 404. Arguments that break the tool's input schema: 400 with the problems. Nothing is charged and the upstream is never called.
  2. Tool not on the allowlist: 403 with a signed receipt, no upstream call. After 20 failed calls in one UTC hour, the next call gets 429.
  3. One conditional SQL UPDATE ... WHERE spent_mc + price <= budget_mc reserves the price. No room: 402 with a signed receipt.
  4. The upstream runs. On failure (502), timeout (504) or refused arguments (400, including an MCP isError answer whose code blames the input) the price is refunded in the same batch that writes the receipt, to the budget period it came from.
  5. On success the desk charges the price and signs the receipt.

Receipts are Ed25519 over canonical JSON: key, org, tool, outcome, amount, upstream cost, and SHA-256 hashes of the input and the result, both over canonical JSON so key order does not matter. Anyone can check a response offline with the public key at /.well-known/receipt-signing-key.json. The verifier checks the signature, that the returned answer hashes to the signed result_sha256, that the arguments you sent hash to the signed params_sha256, and that the readable copy matches the signed bytes. So a receipt proves which answer was paid for. Change one digit of the amount, or one character of the answer, and the check fails. Over MCP the whole signed receipt comes back in the tools/call result next to the answer.

Operators get a console behind a bearer token: organizations, keys, budgets, allowlists, per-key prices, rotation, revocation, and an audit log with upstream cost, price and margin per call. An operator can put a tool from any MCP server behind the gate with one admin call, with no deploy: the desk runs the MCP session handshake and reads that server's tools/list for the schema. For a paid server the operator adds one credential header; the desk seals it with AES-256-GCM and never shows it again. Judges get a public sandbox: a 2 cent key with 2 allowed tools that lives 60 minutes, each key in its own organization, at most 5 keys per network per day.

How we built it

  • One TypeScript Cloudflare Worker with no runtime dependencies, and one D1 database.
  • Money is integer millicents; a CHECK constraint makes spend above budget impossible.
  • Triggers make the audit log and the receipts append-only. A receipt is visible only inside its own organization.
  • Two built-in upstreams: OpenKrill (3 MCP plugins and 3 REST tools) and the public DeepWiki MCP server, plus any MCP server an operator adds at run time. Input schemas come from the upstreams; there is no fallback data.
  • Daily and monthly budgets reset inside the same atomic reservation, so a reset never lets parallel calls overspend.
  • The desk is an OpenKrill Connect OAuth client: it refreshes its own 1 hour access token and keeps the rotating refresh token in D1.

Challenges we ran into

  • Making the budget hold under load. A read-then-write check lets parallel calls overspend, so the reservation is one conditional update. 20 concurrent calls in the tests, and 10 on the live desk, give exactly one success on a key with room for one.
  • workerd and Node disagree on the alg field of an Ed25519 JWK, so the desk imports only the key material.
  • Refusals need proof too. A 402 or a 403 gets a signed receipt and the same x-clearing-* headers as a success.
  • Free failures are an attack. If failed calls cost nothing, an agent can hammer an upstream with bad arguments. The desk checks arguments against the upstream's own schema before any spend, maps an upstream refusal to 400, and caps failed calls per key per hour.
  • A refund can cross midnight. A call reserved before a daily reset and failing after it must not refund into the new day, so the refund is tied to the period it was taken from.

Accomplishments that we're proud of

  • 51 tests on real SQLite with the production migrations; only the upstreams are faked. Each attack a reviewer found became a failing test first.
  • 43 of 43 live end-to-end checks against the deployed desk, including an upstream refusal with nothing spent, the live 429 cap, the concurrency burst, key rotation, revocation, a tool added at run time, one with a sealed credential, a schema refusal with no charge and the 4-part offline proof.
  • An agent customer check: a separate model used the desk as an MCP server for real tasks.

What we learned

Budgets for agents are a money problem, not a logging problem. The useful unit is a receipt that a third party can check without trusting the desk.

What's next

  • Paid mode: top up an organization budget through a payment provider (the ledger already records price, cost and margin).
  • Budget alerts before a key runs out.
  • Receipt export for finance.

Built With

Share this project:

Updates

Submission history