paybot-mcp

June 10, 2026 · View on GitHub

MCP server for PayBot — payment tools for AI agents via the Model Context Protocol.

Install

npm install paybot-mcp paybot-sdk

Get your API key

PAYBOT_API_KEY is required — without it every tool call fails with a 401. The fastest way to get a key is one PayBotClient.signup() call (from paybot-sdk) against the hosted facilitator at https://api.paybotcore.com:

node -e "import('paybot-sdk').then(async ({ PayBotClient }) => {
  const a = await PayBotClient.signup('you@example.com', 'a-strong-password', { botId: 'my-agent' });
  console.log(a.apiKey); // pb_live_... — printed ONLY once, save it now
})"

This single call registers your operator account, creates the API key, and registers the bot (botId). Put the printed key into PAYBOT_API_KEY in the MCP config below, and reuse the same bot id as PAYBOT_BOT_ID.

Notes:

  • The key is printed only once — store it securely; it cannot be retrieved later.
  • Because signup() already registered your bot, calling the paybot_register tool with the same bot id returns 409 ALREADY_EXISTS. Use paybot_register only for additional bots.
  • Full auth flow (login, extra API keys, self-hosted facilitators): see the paybot-sdk README → Get your API key.

Usage with Claude Desktop

Add to claude_desktop_config.json:

{
  "mcpServers": {
    "paybot": {
      "command": "npx",
      "args": ["paybot-mcp"],
      "env": {
        "PAYBOT_API_KEY": "pb_...",
        "PAYBOT_FACILITATOR_URL": "https://api.paybotcore.com",
        "PAYBOT_BOT_ID": "my-agent"
      }
    }
  }
}

Usage with Claude Code

{
  "mcpServers": {
    "paybot": {
      "command": "npx",
      "args": ["paybot-mcp"],
      "env": {
        "PAYBOT_API_KEY": "pb_...",
        "PAYBOT_BOT_ID": "my-agent"
      }
    }
  }
}

Environment Variables

VariableDescriptionDefault
PAYBOT_API_KEYPayBot API key (see Get your API key)(required)
PAYBOT_FACILITATOR_URLFacilitator server URLhttps://api.paybotcore.com
PAYBOT_BOT_IDDefault bot identifiermcp-agent
PAYBOT_WALLET_KEYWallet private key for real payments(optional)
PAYBOT_ENABLE_DEMO_TOOLSRegister the governed mock demo tools (delete_database, annotate_record). Must be exactly true.false (off)

If PAYBOT_API_KEY is unset or empty, the server still boots (so the MCP handshake succeeds) but prints a warning to stderr at startup, and every tool call will fail with an authentication error until a key is configured. Omitting PAYBOT_WALLET_KEY keeps the underlying SDK in mock mode (no on-chain settlement).

Available Tools

ToolDescription
paybot_payMake a payment (USDC by default; supports alternate tokens + idempotency)
paybot_balanceCheck trust level, spending limits, and remaining budget
paybot_historyView recent payment history and audit events
paybot_registerRegister a new bot with the facilitator (optional idempotency key)
paybot_list_networks_and_tokensDiscover supported networks and tokens (offline, no API key)
paybot_health_extendedExtended facilitator health (status/version/uptime + extras)
paybot_set_spending_limitSet per-transaction / daily / hourly limits and a recipient allowlist
paybot_commission_inspectInspect commission summary and a filterable ledger
paybot_pool_createCreate an in-process bot pool with an optional shared treasury
paybot_pool_allocateAdd a bot to a pool, optionally paying as it through the treasury
paybot_pool_revokeRemove a bot from a pool
paybot_pool_statusReport pool treasury and per-bot spend counters

paybot_pay

ParamTypeNotes
amountstringAmount in USD (e.g., "0.05")
recipientstringRecipient wallet address (0x...)
resourcestringURL or description of what you're paying for
botId?stringBot identifier (defaults to env)
network?stringNetwork CAIP-2 ID (default: Base Sepolia)
token?stringToken ticker (default USDC); e.g. USDC, EURC, DAI
idempotencyKey?stringRepeat call with the same key returns the cached result

paybot_balance

ParamTypeNotes
botId?stringBot identifier (defaults to env)

paybot_history

ParamTypeNotes
botId?stringBot identifier (defaults to env)
limit?numberMax events to return (default: 10)

paybot_register

ParamTypeNotes
botIdstringUnique bot identifier
trustLevel?numberInitial trust level 0-5 (default: 1)
idempotencyKey?stringRepeat register with the same key returns the cached result

paybot_list_networks_and_tokens

Read-only discovery. Requires no API key and makes zero network calls. Surfaces only the public open-core registry — operator-private mainnet addresses are never shown (e.g. EURC advertises its Base Sepolia testnet deployment only).

No parameters.

paybot_health_extended

ParamTypeNotes
botId?stringBot identifier (defaults to env)

Returns status, version, uptime, timestamp, plus any extra fields the facilitator reports.

paybot_set_spending_limit

Tightens an agent's own limits. The facilitator enforces the operator ceiling and may reject attempts to loosen beyond policy.

ParamTypeNotes
botId?stringBot identifier (defaults to env)
maxTransactionUsd?numberMax USD per transaction
maxDailySpendUsd?numberMax USD spend per day
maxTransactionsPerHour?numberMax transactions per hour
allowedRecipients?string[]Allowlist of recipient addresses

paybot_commission_inspect

ParamTypeNotes
botId?stringBot identifier (defaults to env)
status?enumFilter ledger by pending | forwarded | deferred
startDate?stringLedger start date (ISO 8601)
endDate?stringLedger end date (ISO 8601)
limit?numberMax ledger entries (default: 50)
offset?numberLedger pagination offset

paybot_pool_create

Creates an in-process bot pool for this MCP session. Treasury accounting is in-memory; the facilitator remains the authoritative limit.

ParamTypeNotes
poolIdstringIdentifier for this pool (used by allocate/revoke/status)
sharedDailyLimitUsd?numberOptional shared daily spend cap across all bots

paybot_pool_allocate

ParamTypeNotes
poolIdstringPool identifier
botIdstringBot identifier to add to the pool
trustLevel?numberInitial trust level 0-5
pay?objectOptional { amount, recipient, resource, network?, token? } to pay as this bot

paybot_pool_revoke

ParamTypeNotes
poolIdstringPool identifier
botIdstringBot identifier to remove

paybot_pool_status

ParamTypeNotes
poolIdstringPool identifier

Governed tools (decide-before / prove-after)

PayBot MCP can wrap any tool — not just payments — so a dangerous call must pass policy before it runs, an irreversible call pauses for a named human's approval, and every outcome leaves a tamper-evident, replayable trace in core's audit chain. This is the kill-switch + black-box-recorder for MCP tool calls.

How it works

registerGovernedTool(server, def, client, opts) is a higher-order registrar. Each wrapped call:

  1. builds an ActionIntentverb = tool name, target_ref = a registrar-provided extractor over the args (opaque, never raw PII), params_hash = SHA-256 of the canonical (key-sorted) JSON of the args, actor.subject_ref = the configured bot id, channel: 'mcp';
  2. calls core POST /actions/govern;
  3. acts on the verdict:
    • allow → runs the tool, and appends executed: params_hash=…, result_hash=… to the output so the trace binds intent → execution;
    • deny → does not run; returns the gate reasons;
    • pending → (default block mode) polls GET /approvals/:id with backoff until approved/denied/expired or approvalTimeoutMs (default 120 s); on approval it re-verifies params_hash AND requires the approval to be action-shaped before executing (TOCTOU + cross-route defence), then runs once. (return mode hands back the approval_id instead.)

Approve via the ACTION route — enforced, not just advised. A paused action is approved/denied through POST /actions/approvals/:id/approve (or .../deny) — not the payment /approvals/:id/approve route, whose grant path attempts settlement. The approval row is the shared A5a store, so a payment-route approve still flips it to APPROVED; the interceptor therefore does not trust a bare decision === 'APPROVED'. The payment approve route always runs settlement and writes a state (SETTLE_FAILED / RESUME_CONTEXT_UNAVAILABLE for an action); the action route never settles and writes no state. The interceptor executes only when the approved row has no settlement state — a present state is treated as a payment-route claim and the call fails closed with WRONG_APPROVAL_ROUTE (handler never runs). This closes the cross-route hazard where a human approving what they believe is a payment would otherwise authorize an agent's irreversible action.

Cost & lifetime

Governance adds one network round-trip per governed call (/actions/govern). A pending action in block mode holds open only as long as the stdio session lives — MCP stdio servers are single-session, so a session that ends drops a blocking wait. Operators drive approval from a second terminal (curl/dashboard), exactly like the HITL payment demo.

Security properties

  • Fail closed. If governance is unreachable (network error, timeout, 5xx, malformed body) an irreversible or unknown action is refused (GOVERNANCE_UNREACHABLE) — an unreachable governor never silently allows. A reversible tool MAY set failOpen: true (demo-only convenience; ignored for irreversible verbs).
  • Risk class is set by the registrar (operator code), never by the model. An agent cannot self-declare its destructive tool "reversible."
  • No raw args leave the process. Only the params_hash and the extractor's target_ref are sent to core; raw arguments (connection strings, PII) stay local.
  • No bypass. Governance is applied at registration. A server that registers a raw tool is ungoverned by definition — we govern what is wrapped, and make no claim to intercept everything.

Demo tools (off by default)

Set PAYBOT_ENABLE_DEMO_TOOLS=true to register two mock governed tools:

ToolRisk classEffect
delete_databaseirreversibleMOCK — touches nothing; pauses for human approval, then returns a labelled mock confirmation
annotate_recordreversibleMOCK — proves the allow path; flows straight through

A published MCP server must not advertise a delete_database tool to every agent, so these stay off unless the flag is explicitly true.

See docs/runbooks/governed-action-demo.md for the exact recorded-demo script (govern → pending → approve → mock execute → replayable audit proof).

Programmatic Usage

import { createMcpServer } from 'paybot-mcp/server';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';

const server = createMcpServer();
const transport = new StdioServerTransport();
await server.connect(transport);

License

Apache 2.0