Buy
Use one seller's tools directly
@zanora/mcp-proxy puts one paid MCP server in front of your agent. Its tools appear under their own names with their own schemas, and each call is paid for as it's made.
With @zanora/mcp, the model decides to buy. It calls zanora_invoke with a capability id. With the proxy, a paid tool is just a tool. The model calls extract_invoice(imageUrl), and payment happens during the call.
@zanora/mcp | @zanora/mcp-proxy | |
|---|---|---|
| The model calls | zanora_invoke(capabilityId, body) | extract_invoice(imageUrl) |
| Finding a seller | zanora_discover searches the marketplace | you name one seller in the config |
| Paying | a tool the model chooses to call | happens during the tool call |
| Best for | an agent that shops across many sellers | an agent that uses a seller you've already chosen |
You can use both. The model can shop, and also have your chosen seller's tools to hand.
Add it
{
"mcpServers": {
"acme-ocr": {
"command": "npx",
"args": ["-y", "@zanora/mcp-proxy", "https://demo-seller.zanora.dev/mcp"],
"env": {
"ZANORA_API_KEY": "zk.akey_…",
"ZANORA_AGENT_KEY_FILE": "/absolute/path/to/agent-key.pem",
"ZANORA_MAX_PRICE_MICROS": "5000000"
}
}
}
}The model now sees the seller's tools, with the price added to each description. The demo seller's tool is free, so it reads $0.00; a paid tool shows its price the same way:
extract_invoice(imageUrl) Extract text and structured line items from scanned invoices.
Costs $0.00 USD per call, charged to this agent's wallet.Configuration
The same variables as @zanora/mcp, so one agent running both sets them once:
| Variable | Required | Meaning |
|---|---|---|
ZANORA_AGENT_KEY_FILE / ZANORA_AGENT_KEY | yes (one of) | the agent wallet's private key |
ZANORA_API_KEY | yes on any real deployment | workspace key |
ZANORA_MAX_PRICE_MICROS | no, but set it | per-call ceiling in micro-dollars (2000000 = $2.00). Without it, the proxy pays whatever a tool costs, up to the wallet's balance. The seller sets its own prices, so this is the limit you control |
ZANORA_WALLET_ID | no | only when the workspace has several agent wallets |
ZANORA_GATEWAY_URL | no | defaults to https://api.zanora.dev |
ZANORA_SELLER_HEADERS | no | JSON keyed by origin, for a seller behind its own auth |
ZANORA_TOOLS_POLL_MS | no | how often to re-read the seller's tool list. Default 60000, minimum 15000, 0 to turn off |
ZANORA_APPROVAL_ELICITATION | no | on (default) or off: whether to ask the user about held spends and an empty wallet |
The seller URL is the argument. Use one proxy entry per seller, so two sellers' tool names can't collide.
Behaviour to know
- The seller's result is passed through unchanged: content blocks,
structuredContent, the seller's_meta. You get exactly what you paid for. - It only calls URLs. It won't run a seller's package. That needs a separate opt-in in
@zanora/mcp. - It refuses plain
http://sellers, except loopback for local development. The answer you paid for comes back over that connection. - It keeps the tool list current. It declares
tools/listChanged, and notices price changes both from each purchase's receipt and from a slow poll. It never announces a change on a guess: a seller that's briefly unreachable hasn't changed its list. - Refusals come back as readable results (
POLICY_DENIED,APPROVAL_REQUIRED,INSUFFICIENT_FUNDS), not protocol errors, and nothing is charged. - It asks the user when it can, exactly as
@zanora/mcpdoes (see below). - Cancelling stops the purchase only until the payment proof is sent, the same as the SDK.
When a call needs a person
If your MCP client supports elicitation, the proxy puts two refusals to the user directly, with the same prompts as @zanora/mcp:
| Refusal | What the user is asked | What happens next |
|---|---|---|
APPROVAL_REQUIRED | approve or deny this spend. The amount, wallet, capability and policy rule come from the gateway's approval record, never from the model | approve: the purchase is retried once and the tool returns the seller's answer. Deny: APPROVAL_DENIED, which the model must not retry. Dismissed: APPROVAL_REQUIRED, still pending |
INSUFFICIENT_FUNDS | top up the wallet. They're shown the price, the shortfall, a one-time card payment link (where card payments are available) and the wallet's deposit addresses | when they say it's sent, the balance is re-read. The purchase is retried once, only if the balance actually went up |
A purchase that went through after the user was asked ends with one extra [zanora] text line saying so. The seller's own content blocks come first, unchanged.
A client that doesn't declare elicitation isn't asked, and gets the approval id or the funding refusal as before. ZANORA_APPROVAL_ELICITATION=off turns asking off. With a key without approvals:write (every agent key), approval prompts aren't shown: the key couldn't record the answer, so the refusal says up front that the purchase is waiting in the console under Buyer → Approvals.