VetoVetoDocs
SDK · CLI · MCP

MCP server

The agent's install — @veto-protocol/mcp adds the buyer-side checkout flow as MCP tools so an agent in Claude, Cursor, or Continue can transact against any protocol-speaking merchant.

@veto-protocol/mcp is the agent's install. It adds the buyer-side checkout flow as MCP tools so an agent in Claude / Cursor / Continue can transact against any merchant that speaks the Veto Checkout protocol — reading nothing but self-describing, reason-coded responses. Hand-rolled stdio JSON-RPC 2.0, zero runtime dependencies, no build step.

This is the merchant-protocol MCP server: seven checkout_* tools that drive a merchant's HTTP endpoints. It is distinct from the buyer-governance Veto MCP (veto_authorize, veto_policy_*, …) that gates an agent's spend before it pays. The two compose: a governed agent gets a veto mandate from its governor, then presents it via checkout_quote.

Install

Add the server to your MCP client config (or run veto mcp install --client claude):

claude_desktop_config.json
{
  "mcpServers": {
    "veto-checkout": {
      "command": "npx",
      "args": ["-y", "@veto-protocol/mcp"],
      "env": { "VETO_API_BASE": "https://api.veto-ai.com" }
    }
  }
}

Tools

ToolWhat it does
checkout_discover({ url })Fetch a merchant's manifest. Start here.
checkout_catalog({ url })List a merchant's products.
checkout_quote({ url, items, … })Create a session → self-describing payment_required.
checkout_pay({ url, session_id, payment })Settle → run the acceptance gate.
checkout_status({ url, session_id })Poll a session to its resolution.
checkout_mint_mandate({ agent_id, merchant, max_amount, … })Sign a veto mandate (self-governed/test agents; needs VETO_ISSUER_SEED).
checkout_verify_receipt({ jws, url? })Verify a merchant receipt offline against its JWKS.

Each tool result is the merchant's reason-coded body verbatim, wrapped as MCP text content; isError mirrors the HTTP ok, so the model sees the reason codes on any non-2xx.

The buyer flow, tool by tool

An agent never needs to read this page at runtime — every response carries a next field — but the happy path is:

Discover. checkout_discover({ url }) → the manifest (catalog URL, rails, pay_to, accepted mandate types, policy summary).

Quote. checkout_quote({ url, items: [{ sku, qty }], agent_id, buyer_intent?, mandate? }) → a session_id and a self-describing payment_required.

Pay. checkout_pay({ url, session_id, payment }). The payment is rail-specific — { "kind": "mock", "token": "..." } for the offline mock rail, or the EIP-3009 transferWithAuthorization envelope for x402.

Verify. checkout_verify_receipt({ jws: <receipt>, url }) fetches the merchant's /.well-known/jwks.json and checks the Ed25519 signature offline. Cross-check claims.mandateRef against the mandate subject you presented.

Environment

The server holds no merchant state — every tool takes the merchant url as an argument and calls it over fetch. Only the two mandate-minting vars are read at runtime:

VarPurpose
VETO_ISSUER_SEEDbase64 Ed25519 32-byte seed — required only for checkout_mint_mandate.
VETO_ISSUER_KIDkid stamped on minted mandates (default veto-issuer-1).

veto mcp install writes a VETO_API_BASE into the config block for forward-compatibility, but the current server ignores it — pass the merchant URL to each tool instead.

Run locally

node --experimental-strip-types packages/mcp/src/bin.ts

Each tool mirrors a wire endpoint and returns the same self-describing, reason-coded payloads — so the agent follows the next field through the whole flow.