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 four tools (fetch_paid, get_budget, list_payments, get_approval_status), and includes a wallet CLI for client-side signing. This page documents version 0.1.0.
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. When the seller offers several prices, every one must be within the maximum.
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 |
The amount is at or above the agent's approval threshold. approvalId, amountUsd, thresholdUsd, host, idempotencyKey. 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.
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, and any redirect after paying, is returned as is with redirectNotFollowed: true, so 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 |
The approval threshold, or null when the API does not report one. |
allowHosts |
The hosts the agent may pay. |
capLevel |
ok, warning (from 80% of the daily cap) or exhausted. |
network, asset |
Network and asset of the budget, for example eip155:8453 and USDC. |
dayResetsAt |
When the daily spend resets, or null when the API does not report it. |
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 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.
{
"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. 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. |
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. |
plan_limit_reached |
The organization reached a plan limit. plan, limit (agents or monthly_managed_spend), max, current, upgradeUrl. |
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. |
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. |
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