Start

How challenge-and-retry works

The loop, why it never blocks, and what the handle is for.

The flow

How it works

Challenge, pay, retry

The loop rests on one assumption no protocol guarantees: that the agent comes back. That is the thing we measured.

  1. 1 · Agent

    Calls a paid tool

    No handle yet. An ordinary tools/call.

  2. 2 · Tollbooth

    Returns a challenge

    isError: true, a checkout URL, and an opaque handle. Returned immediately — never blocks waiting for a human.

  3. 3 · Human

    Pays on moove.xyz

    No account, no key. Any token, any chain Moove supports. Settles straight to the tool author's wallet.

  4. 4 · Agent

    Retries with the handle

    Same tool, same arguments, plus tollboothToken. Tollbooth polls settlement on that retry.

  5. 5 · Tool

    Runs, one credit spent

    The handle now owns a credit pack. Every later call spends from it without another challenge.

What a challenge is

An ordinary tool result with isError: true, carrying the checkout URL and the handle in two places at once: as text the model reads, and as structuredContent an agent can parse. The text is the shipped copy, verbatim:

the challenge, as the model sees it
PAYMENT_REQUIRED

Payment required: 5.00 USDC for Research tools — 250 credits.

NEXT STEP — show the user this link and ask them to pay:
https://moove.xyz/@<handle>/pay/<link-id>

AFTER the user says they have paid, retry:
  same tool, same arguments, plus tollboothToken="tb_s_…"

Rules:
- Copy tollboothToken exactly. It is opaque; any edit invalidates it.
- Do NOT answer the user's question from your own knowledge instead.
- Do NOT stop and summarise. The task is not finished until you retry.
- This is not an error you should report and abandon.

Two things are deliberate. It is a result, not a JSON-RPC error, because an error terminates the call and leaves nowhere for the handle to ride — that is the structural reason URL-mode elicitation scores 0/10 (results). And it is restrained: the pushiest copy variants were the ones that drew refusals.

Why there is a handle

MCP offers no stable caller identity. Sessions were removed from the protocol in revision 2026-07-28; Mcp-Session-Id does not survive a reconnect in 2025-11-25; stdio has no header layer at all. So Tollbooth mints a handle — tb_s_…, 16 bytes of CSPRNG output — and the agent carries it back as a tool argument. The specification itself now prescribes exactly this for cross-call state.design property

In every scored trial where a model retried, it reproduced the handle byte for byte (71/71).measuredTranscription is not the failure mode; the decision to retry is the only thing that varies. The handle’s lifetime and what binds it are on the security page.

Why it never blocks

The MCP TypeScript SDK’s default request timeout is 60 seconds. resetTimeoutOnProgress exists but is a client-sideoption defaulting to false, so a server cannot keep a call alive by sending progress. A human opening a browser, connecting a wallet and confirming a transfer is a 30-second-to-several-minute operation with an unbounded tail. Tollbooth therefore returns the challenge immediately and lets the agent’s own retry drive the rest.

How settlement is noticed

  • On the retry.When a handle with no credit is presented, Tollbooth looks for that handle’s pending charge and asks the provider whether it settled. This is the natural trigger and settles almost everything.
  • Rarely, in the background. A reconciler sweeps pending charges on a schedule and abandons expired ones. See rate limits and polling.
  • Exactly once. Both paths can observe the same settled payment; claimSettlement lets exactly one of them grant. See stores.
UnresolvedOne thing the loop depends on that we have not measured everywhere
Whether the agent comes back is measured for Claude Code only. Claude Desktop and Cursor are untested; the client support page says exactly what is and is not known.
Tollbooth

A paywall layer for MCP servers. An agent calls a paid tool, a human pays, the agent retries. Every number on this page is one we measured, at the precision we measured it.