Operating

Security model

Handles, their lifetime, what binds them, and the network guards on the reference server.

Handle minting

A handle is tb_s_plus 16 bytes of CSPRNG output, base64url. No counters, no timestamps, nothing derived from the provider. It must be unguessable rather than merely unique, because it is a bearer credential that will sit in a model’s context window. Comparison is constant-time. Charge nonces are minted the same way and are never the same value.design property

Lifetime: a sliding window

A handle expires 30 days after it was last used, and every successful call pushes that forward. The floor is one hour; the store refuses shorter.

Why sliding rather than short and fixed: the handle is also the only thing identifying the owner of paid credits, and MCP offers no identity to recover them with. A short fixed expiry would not log someone out — it would destroy money they spent, with no recovery and no refund. So a handle in an abandoned transcript goes dead after a month of disuse, while somebody actively spending never loses anything.

The charge nonce is the opposite case and expires hard: it is tied to one checkout and dies with the link, 60 minutes by default.

Why the handle must not be the only factor

The handle will be logged, summarised, and pasted into transcripts. In 0/71 measured trials the model printed it for the user measured — reassuring, not a guarantee. For a $5 pack, a leaked handle is a $5 problem. For anything you would mind losing, it should not be the whole story:

  • Bind it to a principal when the transport gives you one. On authenticated Streamable HTTP, Tollbooth binds the handle to the OAuth sub (or client id) that first presented it, and refuses it from anyone else. A test proves a bound handle is worthless to a second caller.
  • Keep packs sized to what you would forgive. The $5 floor and the session-sized default are as much about blast radius as about interruption.
  • Never use a link id as a handle. Link ids are readable by anyone holding them (why); a test guards that no handle is derived from one.

What is logged

One structured line per paid call, carrying a handle fingerprint — first nine and last four characters — never the handle. Enough to tell whether the same one came back, useless if the log leaks. Checkout URLs and link ids are never logged. The API key is never logged and never attached to an error.

one log line
[tollbooth] {"evt":"call","tool":"fetch_readable","sku":"research",
  "tokenPresented":true,"tokenFingerprint":"tb_s_KLeV..EPcw",
  "tokenRecognised":true,"outcome":"authorised"}

SSRF guards

The reference server’s tools take a hostname from a model and connect to it. The first guard checked the hostname string and stopped nothing cleverer than typing localhost. The tests then found three more holes, and all three are closed by checking resolved addresses:

holefix
DNS rebinding: a public name resolving to a private addressHostname resolved; every answer must be public. Tested against localtest.me, which resolves to ::1.
Redirects: redirect: 'follow' walks from a public URL to link-local metadata, first hop only ever checkedredirect: 'manual'; every hop re-validated.
Ports: http://host:22/ as a connectivity oracleRestricted to 80, 443, 8080, 8443.
IPv6 literals and IPv4-mapped addressesBrackets stripped; ::ffff: refused outright, since Node normalises the dotted form to hex.
Bodies that lie about their lengthCapped while streaming, not after buffering.

Plus a per-handle token bucket (2/s sustained, burst 10) and a 20-second deadline on every call. Credits stop free use; they do not stop somebody who bought a pack from spending it in seconds probing hosts. If you add a tool that reaches the network, use assertFetchableUrl or safeFetch, not the string check.

The API key

Warning
A Moove key can create payment requests that pay its owner, and nothing else — no endpoint moves funds. That bounds the damage of a leak, but a leaked key can still create links in your name. Store it in a secret manager, one key per service, and revoke anything you are unsure about; revocation is instant and permanent.Moove documentation
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.