Skip to main content
metal-agent is the CLI and MCP server of the @metalntwx/metal-agent npm package:
Commands print name value lines, one per line. A failure prints error: <message> to stderr and exits 1; pull also exits 1 on a refusal, 3 on pending and 4 on held, and verify exits 1 on a failed check. Amounts are decimal *USDZ; amounts under a mandate are in *USDZ, to the cent. metal-agent --version prints the installed version alone on one line.

CLI

Every command that reads or writes the chain takes these flags:

Keys and funds

key create

Generates the agent’s key and writes its key file. Refuses to overwrite one.
Prints address and keyfile.

key import

Writes a key you already hold to the key file. Takes a hex key, with or without 0x, a file holding one, or a key file.
Prints address and keyfile, never the key. Like key create, it does not overwrite a key file.

fund

Asks the testnet faucet to fund an address; the faucet sets the amount. The faucet limits every client behind one IP address together, so a refusal of this client names the Faucet page in the Metal console, which grants per signed-in user.
Prints funded, transaction and explorer, or reports an address that already holds a full grant. An IP address gets ten grants a UTC day, one a minute, out of the faucet’s 1,000 a day for every client; a client_rate_limited error names the seconds to wait.

Paying

pay

Requests a URL. If it answers 402 with an exact offer in *USDZ, signs the payment with the key file, retries, and prints the settlement, the response headers and the body.
Prints nonce as soon as the payment is signed, then status, payer, payee, amount, network, transaction and explorer, then one header <name>: <value> line per response header, except those it hides such as content-type, date and CORS headers. body comes last. Under a mandate, mandate <mandate id> comes first. A refusal exits with one of three prefixes: An offer with no exact option on the network named fails before signing with Failed to create payment payload: …; one on another network names it, as the offer is on testnet (eip155:63825), not devnet (eip155:1337). These errors, and a seller that cannot be reached, end nothing was signed. If the answer to a signed payment is lost, or the seller fails or answers 409 after it, the error says a payment was signed and may have settled. payment in doubt at settlement: <reason> means the same: any failed settlement whose reason is not one that moves nothing, such as settlement_in_doubt, already_settled or a seller’s own fetch failed when the facilitator’s answer to it was lost. Before paying again, look for it in the facilitator’s /settlements?payTo=<payee>, by an id ending in the nonce line printed first; <payee> is the payTo of the seller’s 402 offer, in its base64 JSON payment-required header. A seller that answers without a settlement prints payment unconfirmed; one that answers with a settlement prints it, whatever its status. A paid request redirected to another origin is not followed, so no other site receives the payment; status and header location show where it points.

pay under a mandate

Pays from the owner’s account under a mandate, signed by the mandate key. The mandate’s limits apply in place of --max and --key, which the command refuses.
Prints mandate, then the same lines as pay, with payer the owner’s account. When the seller’s 402 asks for a signed order, prints signed-offer <text>. An offer the mandate cannot pay, such as one in another token or not a whole number of cents, fails before signing with Failed to create payment payload: …. A payment outside the mandate exits with payment refused: mandate_…; the reasons are below.

status

Shows a settlement from chain data.
Prints transaction, status, block, submitter, gas and one transfer <amount> from <address> to <address> line per transfer the transaction carried.

Spending limits

account create

Creates the owner’s Metal account on chain, the one a mandate’s --principal is. The owner key pays the gas; one that holds too little is funded first, from the faucet on testnet. An account that exists is named, and nothing is sent.
Prints account, owner and keyfile, funded when it funds the owner key, then deployed and the deployment’s transaction, explorer and gas. When a later step fails, such as a faucet refusal, the error says why and names the key file, which is kept; the same command continues from it.

mandate credential create

Writes a credential stating a mandate’s limits, and beside it the mandate key the agent signs with.
Prints credential and mandate, the terms (description, principal, mandate-key, token, per-payment-cap, budget, starts, expiry, max-occurrences when it is set, and payee per address, with its name and website, or payees any), then mandate-key-file, issuer-key-file and issuer-public-file.

mandate bind

Records the mandate on the owner’s account, signed by the account’s owners. The account must exist already; account create creates a one-owner one.
Prints bound, transaction, explorer and gas.

mandate replace

Binds a new credential in place of an old one. The spend carries into the new budget, and the old mandate is revoked in the same transaction. Issue the new credential with --mandate-key-file naming the old one’s key, in the same directory, and the agent’s commands find that key.
--replaces names the credential being replaced; the other flags are bind’s. Prints replaced, bound, spent, transaction, explorer and gas.

mandate renew

Binds a new credential as a fresh mandate and revokes the one it replaces, in one transaction. The new budget starts with nothing spent; a budget never resets on its own.
The flags are replace’s. Prints revoked, bound, spent, transaction, explorer and gas.

mandate revoke

Revokes a mandate. Payments under it are refused from this transaction on.
Prints revoked, transaction, explorer and gas.

mandate status

Shows a mandate’s terms and where it stands on the owner’s account.
Prints mandate, the terms as credential create prints them, account-type, bound, and when bound revoked, spent <amount> of <budget> and payments. After a replace, spent and payments include what the mandates it replaced spent and paid; mandate verify lists this credential’s payments only.

mandate pull

Pays an address the mandate allows, with no seller. The facilitator submits it and pays the gas, unless --key names a key to submit it.
Checks the payout against the mandate first. One the mandate allows prints transaction, then outcome paid with spent <amount> of <budget>, payer, payee, amount, explorer and gas. One it refuses prints outcome refused, not sent with reason and recorded no, and exits 1, spending no gas; with --record-refusal it prints outcome refused with the transaction that recorded it. One that passes the check and is refused when its transaction runs prints outcome refused the same way. One the receive policy contract holds in escrow prints outcome held: the payee was not paid, and the amount counts against the budget; the payee, or the authority its policy names, releases it. paid exits 0, a refusal 1, pending 3 and held 4. A payout the facilitator refuses without sending a transaction fails with the facilitator’s reason; it is kept, and the same command sends it again, which is refused again until what the reason names changes. A run that sends a kept payout again and is refused, cannot reach the facilitator, has a facilitator URL that is not http or https, or finds the payout rejected by its check, fails saying the pull kept before may have been sent and can land until a time: it can still land until then. The outcome is read from the chain. A sent payout with none yet prints outcome pending, a reason, can-land-until, the time it can still land until, and kept, the payment id it is kept under, or its amount and payee, in ~/.metal-agent/pulls/<mandate>/. Running the same command again sends that same payout, which pays at most once, or reports how it ended; runs at once send the same one. Once its outcome is reported, the same command makes a new payment; under a --payment-id, a payout that was paid or held is reported again, and nothing is sent, or fails, sending nothing, when the chain does not hold it, as after a chain reset. The report starts already paid, nothing sent, or already held, nothing sent, and its spent line is the mandate’s current spend. One the account refused paid nothing: the same --payment-id may be sent with other values. An outcome that cannot be recorded prints kept …: the outcome was not recorded after it, and the same command reports it again. A kept payout that can no longer land is followed by a new one, signed and checked in the same run, and a payout this run sent that can no longer land prints outcome refused, not paid with its reason. Without --payment-id, a payout is known by its --to and --amount: a second payout of the same amount to the same payee, while the first has no reported outcome, sends the first again. Pass a --payment-id for each payment that can repeat, such as refunds; a retry passes it, or leaves it out, as the first run did. A payout is kept under this home directory and this credential: a retry from another machine, or under a credential that renewed or replaced it, signs a new payout. An event under the mandate that the SDK does not decode fails the command with its topic and the account: the payout may have paid, so do not pay it under another payment id.

mandate verify

Checks the mandate and every payment under it from the chain alone, or one transaction. It reads the chain through an RPC endpoint and nothing else.
Prints mandate, four step <n> pass|fail|skipped: <title> lines each with a detail line, one line per payment or refusal found, and verified yes|no. With --tx, step 4’s detail says whether a signed order was checked, and names an order of another payment as such. The payments listed are this credential’s only: after a replace, verify each credential. A failed check exits 1. An event under the mandate that the SDK does not decode, as an account of another version emits, fails the verification with its topic and the account.

MCP

Serves pay, fund and status as MCP tools over stdio, and under a mandate mandate_pull, mandate_status and mandate_verify. What pay and mandate_pull spend is set when the server starts, by whoever starts it: a key file and a per-payment limit, or one mandate’s credential file, which each call reads. The model calling the tools chooses no key, no mandate and no higher limit. A server started with a mandate keeps that credential file, so restart it with the new credential after mandate replace or mandate renew. Under a mandate on testnet, set METAL_RPC_URL in the server’s environment, such as an env block in the host’s configuration.
Every tool takes network, with no default; fund and status also take rpcUrl. The mandate tools read the chain through the server’s METAL_RPC_URL, or devnet’s default endpoint, take no rpcUrl, and fail naming the endpoint’s chain when it is not the network’s. mandate_pull submits pulls to the server’s METAL_FACILITATOR_URL, or the network’s facilitator. Each tool returns the lines its command prints, with the next: lines above. Keys are created and imported through the CLI only, and no tool sets or changes a mandate.

What a mandate enforces

A mandate’s limits are checked by the owner’s account in the transaction that moves the money, so no client, seller or facilitator can settle a payment outside them. A payment under a mandate can also be refused for these:

Pay under spending limits

The mandate commands in the order the owner and the agent run them.

Facilitator

Endpoints, limits, refusal reasons and retries.