# 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

```json title="MCP config"
{
  "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.

> **Note — 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](https://docs.zanora.dev/buyers/signup.md) and [Wallets and agent keys](https://docs.zanora.dev/buyers/wallets.md), then [fund the wallet](https://docs.zanora.dev/buyers/funding.md).

## Configuration

| Variable | Required | Meaning |
|---|---|---|
| `ZANORA_API_KEY` | **yes** | a workspace key. Use a dedicated agent key rather than the root one (see [Approvals](https://docs.zanora.dev/buyers/approvals.md#keeping-approval-with-people)) |
| `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](https://docs.zanora.dev/buyers/local-packages.md) |
| `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](https://docs.zanora.dev/buyers/approvals.md)) |
| `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:

```text
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_MINOR` is 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.
