Buy
Buy from an MCP client
Add @zanora/mcp to Claude, Cursor or any MCP client, and the model can discover, pay for and verify capabilities from its own wallet with no integration code.
@zanora/mcp is an MCP server that runs locally over stdio. It gives the model ten tools. Two of them matter most: zanora_discover finds something to buy, and zanora_invoke pays for it and calls it.
Add it to your client
{
"mcpServers": {
"zanora": {
"command": "npx",
"args": ["-y", "@zanora/mcp"],
"env": {
"ZANORA_API_KEY": "zk.akey_…",
"ZANORA_AGENT_KEY_FILE": "/absolute/path/to/agent-key.pem",
"ZANORA_MAX_PRICE_MINOR": "500"
}
}
}
}| Client | Where the config goes |
|---|---|
| Claude Desktop | ~/Library/Application Support/Claude/claude_desktop_config.json (macOS), %APPDATA%\Claude\claude_desktop_config.json (Windows) |
| Claude Code | .mcp.json in the project, or claude mcp add zanora -- npx -y @zanora/mcp |
| Cursor | .cursor/mcp.json |
| Anything else | the client's MCP server config: command npx, args ["-y", "@zanora/mcp"] |
Restart the client. The server checks the configuration at startup and prints a banner to stderr. A misconfiguration fails immediately with the name of the variable at fault.
You need a wallet first
The config uses a workspace API key and the private key of an agent wallet. If you don't have them, follow Create a workspace and Wallets and agent keys, then fund the wallet.
Configuration
| Variable | Required | Meaning |
|---|---|---|
ZANORA_API_KEY | yes | a workspace key. Use a dedicated agent key rather than the root one (see Approvals) |
ZANORA_AGENT_KEY_FILE | yes¹ | path to the agent's ed25519 private key PEM |
ZANORA_AGENT_KEY | yes¹ | the PEM inline, if you can't use a file |
ZANORA_WALLET_ID | no | only needed when the workspace has more than one agent wallet. With several and none named, it refuses to start and lists them |
ZANORA_MAX_PRICE_MINOR | no | a per-call ceiling in cents. Set one. A tool argument can lower it for one call but never raise it |
ZANORA_GATEWAY_URL | no | defaults to https://api.zanora.dev |
ZANORA_AGENT_ID | no | the agent name on payment proofs (defaults to the wallet id) |
ZANORA_APPROVAL_ELICITATION | no | on (default) or off: whether to ask the user about held spends |
ZANORA_ALLOW_LOCAL_PACKAGES | no | packages this agent may run to buy them. Unset means none. See Running seller packages |
ZANORA_SELLER_HEADERS | no | JSON keyed by seller origin, for sellers behind their own auth: {"https://seller.example":{"authorization":"Bearer …"}} |
¹ One of the two.
The tools
| Tool | Does | Spends |
|---|---|---|
zanora_discover | ranked marketplace search: price, seller reputation, request schema | |
zanora_capability | resolves a known capability id (from an earlier session, a receipt, or the user) so it can be invoked | |
zanora_invoke | pays a capability's price and returns its response and signed receipt | ✓ |
zanora_wallet_balance | balance and available balance | |
zanora_wallet_ledger | every charge, refund and deposit | |
zanora_purchases | what this wallet bought, from its receipts. Pages with after | |
zanora_receipt | fetches a receipt and checks its signature | |
zanora_rate_provider | rates a seller 1–5 for a purchase this wallet made | |
zanora_deposit_addresses | where money can be sent to this wallet | |
zanora_approval_status | whether a person approved a held spend |
The loop is discover → invoke with the capabilityId from a result. The server remembers endpoints from discovery, so the model never types a URL. An id it hasn't seen is an error, not a guess, and zanora_capability resolves it properly. REST and MCP capabilities are both bought with zanora_invoke.
Every tool returns typed structuredContent along with text. zanora_invoke reports four named progress steps if the client sends a progress token.
When the answer is "no"
Refusals come back as readable tool results, and nothing is charged:
| Result | The model should |
|---|---|
POLICY_DENIED | pick another capability. Never retry the same one |
APPROVAL_REQUIRED + id | report the id and stop. If the client supports it, the user is asked directly (see Approvals) |
APPROVAL_DENIED | stop. Don't ask again |
INSUFFICIENT_FUNDS | stop and tell the user where to send money (zanora_deposit_addresses). With elicitation, the user is shown the shortfall and the addresses, and the purchase is retried once the balance has actually changed |
PRICE_ABOVE_CLIENT_LIMIT | the price is over ZANORA_MAX_PRICE_MINOR. Choose something cheaper |
Resources
The server also offers resources a person can browse without asking the model: zanora://wallet (balance) and zanora://purchases (recent receipts). They return the same data as the matching tools.
Try it
Ask the model:
Search Zanora for invoice OCR under $2, buy the cheapest one on
https://example.com/invoice.png, verify the receipt, and rate the seller.Safety model
- The key file is the spending authority. Give each agent its own wallet, funded with what you'd accept losing to it.
- The limits are on the server. Budgets and policies are checked by the platform.
ZANORA_MAX_PRICE_MINORis an extra limit on your side, not a replacement. - Buying and reading only. The server can't create wallets, move money between them, change policies or read another tenant's data.
- Running code is off by default. A search result can never cause a seller's code to run on your machine.