MCP tools reference
@burnbound/mcp is the MCP server that lets an agent pay x402 APIs under its Burnbound policy. It runs over stdio with npx -y @burnbound/mcp, exposes five tools (fetch_paid, get_budget, list_payments, get_approval_status and search_paid_apis), and includes a wallet CLI for client-side signing. This page documents version 0.3.3.
How do I configure the server?
The server is configured only through environment variables in your MCP client config. BURNBOUND_KEY is the only required one. The server checks the configuration before it starts and exits with a message on stderr if something is missing or unsafe.
| Variable | Required | Meaning |
|---|---|---|
BURNBOUND_KEY |
yes | The agent's key (bb_agent_…). |
BURNBOUND_API |
no | Burnbound API base URL. Default https://app.burnbound.dev. Must be https:// (http:// only for a loopback host). |
BURNBOUND_AGENT_ID |
only with an org key | The agent the tools act for. Ignored with an agent key, which already names its agent. |
BURNBOUND_MAX_RESPONSE_BYTES |
no | Cap on the response body fetch_paid returns. Default 1048576 (1 MiB), maximum 10485760 (10 MiB). Longer bodies are truncated. |
BURNBOUND_FETCH_TIMEOUT_MS |
no | Deadline for the whole exchange with the URL in fetch_paid. Default 30000, from 1000 to 120000. |
BURNBOUND_ALLOW_PRIVATE_HOSTS |
no | 1 lets fetch_paid reach loopback and private networks, and http:// on loopback. For local development only. Link-local stays blocked. |
BURNBOUND_WALLET_PROFILE |
no | Local wallet used for client-side signing. Default default. 1 to 32 characters of a-z, 0-9, _ and -. |
BURNBOUND_HOME |
no | Moves the ~/.burnbound directory where file-based wallets are stored. |
The server refuses to start if any BURNBOUND_* variable looks like a private key: wallet keys never go in the MCP config. An organization key still works together with BURNBOUND_AGENT_ID, with a warning on stderr: org keys have admin scope, so use an agent key.
What does fetch_paid do?
fetch_paid requests a URL on one of the agent's allowed hosts and, if the URL answers HTTP 402 with an x402 challenge, pays it under the agent's policy and returns the paid response. It is the only tool that can spend money, and it is marked to the MCP client as non-read-only and open-world.
| Input | Type | Notes |
|---|---|---|
url |
string, required | https:// URL, up to 2048 characters, on one of the agent's allowed hosts. |
method |
string | GET (default), HEAD, POST, PUT, PATCH or DELETE. |
headers |
object of strings | Up to 32 extra headers. Authorization, payment headers (PAYMENT-SIGNATURE, X-PAYMENT…), x-org-api-key, Host, proxy-* and hop-by-hop headers are refused. |
body |
string | Up to 256 KB. Not allowed with GET or HEAD. |
idempotencyKey |
string | Only to retry a purchase: the key from an earlier pending_approval result or payment_outcome_unknown error. 8 to 128 characters of letters, digits, ., _, : and -. |
maxAmountUsd |
string or number | Refuse to pay more than this many USD for this call, for example "0.05". Your Burnbound policy still applies on top. |
maxAmountUsd is checked against the seller's challenge before anything is authorized, and again against what is signed. It applies to the price Burnbound can pay: exact USDC on Base or Base Sepolia. Prices on other chains, in other tokens or in other schemes are ignored, because they are never paid.
Which results can fetch_paid return?
fetch_paid returns one of three statuses. Anything else (a denial, a limit, a network problem) comes back as a tool error with a code.
status |
Meaning and fields |
|---|---|
ok |
No payment was needed. httpStatus, host, body. |
paid |
Paid. httpStatus, host, intentId, idempotencyKey, replayed (the same purchase was answered again and nothing new was charged), mode (server_signed or client_signs), amountUsd, network, payTo, receipt.state (settled, pending, failed or unknown), receipt.txHash, body. |
pending_approval |
A human must approve first. approvalId, amountUsd, reason, thresholdUsd, host, idempotencyKey. reason is threshold (the amount is at or above the agent's approval threshold, in thresholdUsd) or catalog_new_host (the first payment to a catalog host, whatever the amount; thresholdUsd is null). Poll get_approval_status; once it is approved, call fetch_paid again with the same url, method, headers, body and idempotencyKey. |
body is { untrusted: true, contentType, encoding, bytes, truncated, text }. Binary content types come back base64-encoded. In the tool result the body is a separate text block, fenced and labelled as untrusted third-party content, never mixed with the metadata. Response headers are never returned.
status is paid only when the seller answered the paid request with a 2xx and did not report the settlement as failed. Any other answer after paying (another 402, a redirect, an error) is a tool error, payment_rejected_by_seller or payment_not_completed, never paid. fetch_paid sends a signed payment once per call and never signs again on its own.
receipt.state is unknown when the receipt could not be recorded; the payment itself still happened when status is paid. If the paid response body could not be read, bodyError says why and the payment is still reported.
How does fetch_paid handle redirects?
Same-origin redirects before the 402 are followed, up to 3. A redirect to another origin is returned as is with redirectNotFollowed: true. A redirect after paying is not followed either: it ends in a payment_not_completed error with redirectNotFollowed: true. A payment is never sent anywhere other than the URL that asked for it.
What does get_budget return?
get_budget returns the agent's budget for today. It takes no input and is read-only; agents should call it before paying for something.
| Field | Meaning |
|---|---|
agentId |
The agent the key belongs to. |
spentTodayUsd |
USD spent today (UTC day). |
dailyCapUsd |
The daily cap. |
remainingTodayUsd |
What remains today under the daily cap. |
maxUsdPerTx |
The maximum per payment. |
stepUpThresholdUsd |
Payments at or above this amount wait for a human approval; null when step-up approval is off. |
allowHosts |
The hosts the agent may pay. |
catalogHosts |
Verified catalog hosts outside allowHosts the agent may also pay when the agent's catalog-hosts rule is on: the first payment to each waits for a human approval, then the host joins allowHosts. Empty when the rule is off. |
capLevel |
ok, warning (from 80% of the daily cap) or exhausted. |
network, asset |
The agent's network and asset: eip155:8453 (Base) or eip155:84532 (Base Sepolia), and USDC. |
dayResetsAt |
When today's spend resets to 0: the next midnight UTC, as an ISO 8601 timestamp. |
What does list_payments return?
list_payments returns the agent's most recent payments, newest first. It is read-only.
| Input | Notes |
|---|---|
limit |
1 to 50, default 10. |
status |
Only payments in this status: created, authorized, submitted, settlement_pending, settled, failed or expired. |
The result is { agentId, payments }. Each payment has intentId, status, amountUsd, network, asset, host, transactionHash (once settled) and createdAt. It returns the host that was paid, not the full URL.
What does get_approval_status return?
get_approval_status tells the agent whether a human has decided on a payment that needed approval. It takes the approvalId from a pending_approval result and is read-only.
It returns approvalId, status (pending, approved, denied or expired), intentId (set once the approved payment was made), amountUsd, host, createdAt, decidedAt and expiresAt. An agent key only sees its own agent's approvals; any other id is approval_not_found.
What does search_paid_apis return?
search_paid_apis searches Burnbound's catalog of verified x402 APIs and says, for each one, whether fetch_paid would pay it under the agent's policy. It is read-only. Available since @burnbound/mcp 0.3.0. People can browse the same catalog at /apis, with one page per API.
| Input | Notes |
|---|---|
query |
Required, 1 to 200 characters: what the API should do, for example crypto prices. |
category |
Only this category, for example search, market-data, onchain-data or scraping. |
network |
Only APIs payable on eip155:8453 (Base) or eip155:84532 (Base Sepolia). |
limit |
1 to 10, default 5. |
The result is { agentId, query, total, results }. Each result has:
| Field | Meaning |
|---|---|
id, name, host, category |
The seller. host is the exact host that charges. |
summary |
A short description written by Burnbound. |
url, method, exampleBody |
Burnbound's example request, always on host. Pass them to fetch_paid. |
priceUsd, network, testnet |
The price per call in USDC and the network fetch_paid would pay on: the agent's network when the seller accepts it. |
verifiedAt |
When Burnbound last checked, without paying, that the URL answers a valid x402 challenge it can pay. Results older than 24 hours never show. |
payToMode |
pinned: Burnbound knows the seller's receiving addresses and suspends the seller if they change. rotating: the seller uses a new one each time. |
reliability |
{ sample, successRate }. The success rate of real payments through Burnbound over 30 days, only when at least 5 organizations made at least 20 payments; otherwise sample is insufficient and successRate is null. |
allowedByPolicy, requiresApproval, policyBlockers |
Whether the agent's policy lets fetch_paid pay it, whether a human must approve it first, and the policy codes that block it (host_not_allowed, network_not_allowed, amount_exceeds_per_transaction…). |
sellerDescription |
{ untrusted: true } when the seller describes itself. The text is only in a separate block, fenced and labelled as untrusted. |
The catalog never widens what an agent may pay on its own. An agent without a policy still gets results, each with policyBlockers: ["no_policy"]: it cannot pay anyone until its policy is set in the dashboard. A result with host_not_allowed stays blocked in fetch_paid until a human adds the host to the agent's allowed hosts, or turns on the agent's catalog-hosts rule (allowCatalogHosts): then a listed seller is allowed with requiresApproval: true, because its first payment always waits for a human. With the verified-sellers-only rule (verifiedSellersOnly), policyBlockers also carries seller_not_verified or pay_to_not_verified (see Concepts). Ranking uses only Burnbound's own text, the agent's policy, measured reliability, freshness and price; there are no paid placements.
What errors can a tool return?
Errors come back as an MCP tool error whose content is {"error": {"code", "message", …}}. The code is stable; the message is fixed text written by the server, never copied from the Burnbound API or from the seller. Extra fields are only ids, amounts and codes the server validated, and text the server writes from them (summary). The one exception is sellerReason, the seller's own words, which always comes fenced and labelled as untrusted (as do seller descriptions in search_paid_apis).
{
"error": {
"code": "policy_denied",
"message": "The agent's spending policy denied this payment, so nothing was paid. See reasons.",
"reasons": ["daily_cap_exceeded"],
"policyVersion": 4
}
}
Any tool:
| Code | Meaning |
|---|---|
invalid_input |
The input is invalid. reason says why (see below). |
unauthorized |
The API rejected BURNBOUND_KEY. |
forbidden |
The key is not allowed to do this. |
not_found |
The API did not find the resource. |
agent_not_found |
The configured agent does not exist for this key. |
approval_not_found |
No approval with that id for this agent. |
rate_limited |
The API is rate limiting requests. Retry later. |
api_unavailable, api_unreachable, api_timeout, api_error |
The Burnbound API could not answer. Retry later. |
invalid_response, response_too_large |
The API answer could not be read. |
config_invalid |
The server is misconfigured. |
internal_error |
Unexpected error in the server. |
invalid_input reasons from fetch_paid include https_required, invalid_url, url_credentials, burnbound_api_url (the URL is the Burnbound API itself), contains_credential (the input contains the Burnbound key or a wallet key), body_not_allowed, body_too_large, too_many_headers, invalid_header, reserved_header and invalid_max_amount.
fetch_paid, nothing was sent:
| Code | Meaning |
|---|---|
host_not_allowed |
The URL's host is not in the agent's allowed hosts nor in its catalogHosts. host. |
private_address_blocked |
The URL resolves to a private, loopback or link-local address. |
control_plane_unavailable |
The agent's allowed hosts could not be loaded, so nothing was requested. |
fetch_paid, nothing was paid:
| Code | Meaning |
|---|---|
seller_unreachable, seller_timeout |
The URL could not be reached, or did not answer in time. |
invalid_payment_required |
The URL answered 402 without a readable PAYMENT-REQUIRED header. |
no_payable_entry |
No price in the 402 is exact USDC on Base or Base Sepolia with a whole amount. reason. |
amount_exceeds_max |
The price is over maxAmountUsd. maxAmountUsd, amountUsd. |
resource_mismatch |
The seller asked to be paid for a different host than the one requested. |
policy_denied |
The policy denied the payment. reasons (see Concepts), policyVersion. With the catalog rules on, reasons can carry seller_not_verified (the host is not a listed catalog seller) or pay_to_not_verified (the seller asked to be paid at an address Burnbound has not pinned for it). |
approval_required |
The payment needs an approval this API cannot track. |
approval_denied |
A human denied the payment. Do not retry it. |
approval_expired |
The approval expired. Call fetch_paid without idempotencyKey to ask again. |
approval_consumed |
That approval was already used for a payment. Check list_payments. |
approval_queue_full |
Too many payments are waiting for approval. Retry later. |
idempotency_key_reused |
That idempotencyKey belongs to a different request. |
intent_not_replayable |
The payment for that idempotencyKey failed or expired. Omit the key to start a new payment. |
payment_in_progress |
Another payment of this agent is in progress. Retry shortly with the same idempotencyKey. |
payment_request_rejected |
The API rejected the payment request. apiCode. |
client_signing_unavailable |
The agent uses client-side signing, which this server could not set up. |
insufficient_funds |
The paying wallet holds less USDC than the payment on that network. summary, payer, network, networkName, balanceUsdc, amountUsdc. Nothing was signed. See below. |
With a catalog rule on, the API reads the catalog before it decides. If it cannot, it decides nothing and answers catalog_unavailable, which fetch_paid reports as api_unavailable: retry later.
insufficient_funds comes from a balance check the Burnbound API runs before it reserves or signs anything, when it has an RPC for the payment's network and knows the paying wallet's address. summary reads, for example, Wallet 0x1234…5678 has 0.00 USDC on Base; this payment needs 0.01 USDC. Nothing was signed or paid. Add USDC to that wallet on that network, then call fetch_paid again. The check is advisory: the balance can change between the check and the seller's settlement, so a payment that passes it can still be refused by the seller. If the RPC fails, the API skips the check and goes on.
fetch_paid with client-side signing, nothing was paid:
| Code | Meaning |
|---|---|
client_wallet_not_initialized |
No local wallet. Run npx -y @burnbound/mcp wallet init and register the address. |
client_wallet_unavailable |
The local wallet could not be opened (reason: keychain unavailable, insecure_permissions…). |
client_wallet_mismatch |
The local wallet is not the one registered for the agent. localAddress, registeredAddress. |
client_sign_refused |
The local signer refused what it was asked to sign. reason, for example agent_key_required with an org key. |
fetch_paid, after paying:
| Code | Meaning |
|---|---|
payment_outcome_unknown |
The signed payment was sent but the seller's answer was lost. intentId, idempotencyKey. Check list_payments; retrying with the same idempotencyKey never charges twice. |
payment_rejected_by_seller |
The seller refused the signed payment: it answered the paid request with another 402, or reported the settlement as failed. The resource was not served. httpStatus, host, intentId, idempotencyKey, mode, amountUsd, network, payTo, receipt, txHash, and sellerReason when the seller gave one. Not retried. |
payment_not_completed |
The signed payment was sent, but the seller answered with a status other than 2xx or 402 (a redirect, an error) instead of serving the resource. Same fields as above, plus redirectNotFollowed for a redirect. It may still settle: check list_payments; retrying with the same idempotencyKey never charges twice. |
sellerReason is the seller's own explanation, read from its PAYMENT-RESPONSE or PAYMENT-REQUIRED header or its JSON error body, for example Facilitator validation failed: Invalid payment. It is third-party text: in the tool result it comes in a separate block fenced and labelled as untrusted, never in message, and structuredContent carries it as sellerReason: { untrusted: true, text }. In both cases fetch_paid reports the receipt to Burnbound as failed, with the seller's status and reason, so the payment's record and the audit log keep what went wrong. A failed receipt does not free the daily cap: the payment counts until it expires unsettled.
Which wallet commands are there?
The wallet CLI manages the local wallet used for client-side signing. Run it with npx -y @burnbound/mcp wallet <command>; it never starts the MCP server. The private key is stored in the macOS keychain, the Linux Secret Service (GNOME Keyring, KWallet) when a session bus is available, or otherwise a file in ~/.burnbound/wallets/ (directory 0700, file 0600).
| Command | What it does |
|---|---|
wallet init |
Creates a wallet and prints its address. Refuses if the profile already has one. |
wallet address [--all] |
Prints the address (--all: also retired ones). Does not unlock the key. |
wallet import [--from-env VAR] |
Stores an existing key, read from stdin (hidden on a terminal) or once from the variable VAR. Remove the variable afterwards. |
wallet export [--address ADDR] |
Prints the key, only on an interactive terminal (never to a pipe or a file), after you type export <address> to confirm. |
wallet rotate |
Switches to a new key and keeps the old one so you can move its funds (wallet export --address <old>). Register the new address. |
Options:
--profile <name>: wallet profile. DefaultBURNBOUND_WALLET_PROFILE, elsedefault. Use one profile per agent.--backend <kind>: forinitandimport, the storage:macos-keychain,secret-serviceorfile. The storage is chosen once, atinit; later runs never fall back to another one. On Windows onlyfileis available, and it is experimental.
npx -y @burnbound/mcp wallet init
npx -y @burnbound/mcp wallet address