ZanoraBetaDocs

Buy

@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 callszanora_invoke(capabilityId, body)extract_invoice(imageUrl)
Finding a sellerzanora_discover searches the marketplaceyou name one seller in the config
Payinga tool the model chooses to callhappens during the tool call
Best foran agent that shops across many sellersan 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

MCP config
{
  "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:

Output
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:

VariableRequiredMeaning
ZANORA_AGENT_KEY_FILE / ZANORA_AGENT_KEYyes (one of)the agent wallet's private key
ZANORA_API_KEYyes on any real deploymentworkspace key
ZANORA_MAX_PRICE_MICROSno, but set itper-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_IDnoonly when the workspace has several agent wallets
ZANORA_GATEWAY_URLnodefaults to https://api.zanora.dev
ZANORA_SELLER_HEADERSnoJSON keyed by origin, for a seller behind its own auth
ZANORA_TOOLS_POLL_MSnohow often to re-read the seller's tool list. Default 60000, minimum 15000, 0 to turn off
ZANORA_APPROVAL_ELICITATIONnoon (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/mcp does (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:

RefusalWhat the user is askedWhat happens next
APPROVAL_REQUIREDapprove or deny this spend. The amount, wallet, capability and policy rule come from the gateway's approval record, never from the modelapprove: 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_FUNDStop 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 addresseswhen 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.