paybondpaybond
Sign in

MCP agent spend controls

Stop an MCP host's agent spending on work nobody verified — MCP agent spend controls via @paybond/mcp and paybond-mcp-server, with completion evidence and receipts. TypeScript and Python parity.

An MCP host hands your agent tools that can charge real money. A per-transaction cap does not help much: twelve $399 tool calls under a $500 cap still clear $4,788, because each approval has no memory of the last — and a cleared charge is not proof the work happened.

Paybond ships a tenant-bound MCP server that closes that gap. It binds delegated spend to a signed intent, releases or refunds against submitted completion evidence, and returns a receipt finance can replay — while preserving the same tenant boundary as the SDKs and APIs. Use it for internal agent runtimes and orchestration systems that prefer MCP over custom HTTP wrappers.

First-class adapter surface. MCP hosts are a supported framework integration path alongside in-process adapters — see Agent middleware, Coding-agent setup, and the Kit support matrix.

For coding-agent setup, including Codex and generic stdio MCP snippets using npx -y -p @paybond/kit paybond-mcp-server, start with Coding-agent setup.

For a first guardrail integration outside MCP, start with the sandbox scaffold:

npx -p @paybond/kit paybond-init \
  --preset paid-tool-guard \
  --framework provider-agnostic \
  --out paybond-paid-tool-guard.ts
paybond-kit-init \
  --preset paid-tool-guard \
  --framework provider-agnostic \
  --out paybond_paid_tool_guard.py

For MCP-native hosts, the matching sandbox tools are paybond_bootstrap_sandbox_guardrail and paybond_submit_sandbox_guardrail_evidence.

The server is stdio-first by default — most desktop hosts launch it as a local child process. Hosts that need a network URL instead can use the hosted Streamable HTTP endpoint or self-host the same HTTP transport; see Remote HTTP (Streamable HTTP) below.

Paybond does not assume a specific model provider or agent framework. The only assumption is that your host can speak MCP tool calls, either over stdio or Streamable HTTP.

Install

Install

npm install @paybond/mcp

Import from @paybond/mcp

import { createPaybondMcpToolSurface } from "@paybond/mcp";
  • Equivalent subpath on the core package: `@paybond/kit/mcp` — use `@paybond/kit` when you need multiple adapters in one app.
  • For stdio MCP hosts, launch npx -y -p @paybond/kit paybond-mcp-server — see Coding-agent setup in the docs.
  • Python: `paybond agent demo mcp smoke` requires the optional `mcp` extra. Use `pip install "paybond-kit[mcp]"`, `pipx install 'paybond-kit[mcp]'`, or `pipx inject paybond-kit mcp` (when base paybond-kit is already installed).
  • Smoke: `paybond agent demo mcp smoke --operation paid-tool --requested-spend-cents 100 --evidence-preset cost_and_completion --format json` (in-process MCP).

Required environment

Always required. For sandbox setup, use one of the login CLIs first:

npx -p @paybond/kit paybond login
paybond-kit-login

The CLIs write PAYBOND_API_KEY to .env.local; the packaged MCP servers load .env.local by default when PAYBOND_API_KEY is not already present. Set PAYBOND_ENV_FILE for a different local secrets file, or pass PAYBOND_API_KEY in the MCP host launch environment. Production keys are created in Console and stored in deployment secret managers.

For Cursor, Claude Desktop, Codex, and other MCP hosts, prefer a restricted key (paybond_rk_*) over a standard service-account key (paybond_sk_*). Restricted keys carry an explicit MCP scope grant; tools/list and tools/call expose only the tools those scopes unlock, and the gateway enforces the same scopes on the underlying routes. Standard keys keep role-based RBAC and optional PAYBOND_MCP_TOOL_POLICY for local dev.

Create one from Console (Machine access → API keys → Restricted MCP key) or the CLI:

paybond keys create \
  --name cursor-discovery \
  --role analyst \
  --kind restricted \
  --preset mcp-readonly \
  --label cursor-discovery

Presets (settlement / live-money write is never included — add --scope mcp.settlement:write only when you intentionally need fund/confirm):

PresetScopes
mcp-readonlydiscovery, signal, compliance, receipts (read)
mcp-spend-operatorreadonly + spend + evidence (write)
mcp-sandbox-agentdiscovery + sandbox + spend (write)

List the catalog (offline):

paybond mcp scopes list
paybond mcp scopes list --format json

When mcp install detects a restricted key in the env file, it omits PAYBOND_MCP_TOOL_POLICY from the generated host config — scopes come from the key. Pairing --tool-policy / --tool-allowlist with a restricted key is rejected.

Optional:

export PAYBOND_PRINCIPAL_PATH="/v1/auth/principal"
export PAYBOND_MCP_MAX_RETRIES="3"
export PAYBOND_MCP_EVIDENCE_POLICY="strict"
export PAYBOND_ENV_FILE=".env.local"

PAYBOND_MCP_EVIDENCE_POLICY defaults to strict. In strict mode, evidence submit tools refuse calls until paybond_validate_completion_evidence succeeds for the same preset and payload. Set off only for local debugging. Harbor predicate and schema validation remain authoritative at submit time.

Optional policy hot-reload for long-lived MCP processes:

export PAYBOND_POLICY_FILE="./paybond.policy.yaml"
export PAYBOND_POLICY_RELOAD="watch"   # watch | poll | off (default off)
export PAYBOND_POLICY_RELOAD_ALLOW_LOOSEN="0"

When PAYBOND_POLICY_FILE is set, paybond_authorize_agent_spend and paybond_verify_capability enforce the policy registry before Harbor verification. Spend caps resolve from the policy file when requested_spend_cents is omitted. Reload waits for in-flight MCP tool calls to finish before swapping the registry; failed reloads keep the previous snapshot. Use poll with tenant overlay policies to refresh effective policy from the Gateway.

Run

Python

paybond-mcp-server

TypeScript

npx paybond-mcp-server

Remote HTTP (Streamable HTTP)

Stdio remains the default for desktop hosts (Cursor, Claude Desktop, Codex CLI). For hosts that need a network URL instead of a local subprocess — remote agent runtimes, MCP Inspector's HTTP mode, or any orchestrator that cannot launch child processes — Paybond also runs the same tool surface over Streamable HTTP, MCP's current remote transport (a single POST /mcp with a JSON response; no legacy HTTP+SSE).

Hosted endpoint: https://mcp.paybond.ai/mcp

curl https://mcp.paybond.ai/mcp \
  -X POST \
  -H "content-type: application/json" \
  -H "authorization: Bearer $PAYBOND_API_KEY" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Every request supplies its own service-account or restricted MCP key as Authorization: Bearer paybond_sk_... or Bearer paybond_rk_... — the hosted endpoint is multi-tenant and stateless: tenant scope comes from the key alone (never from a client-supplied tenant_id), and each request is handled independently with nothing cached or shared across callers. Restricted keys filter the tool surface per request from principal.mcp_scopes. Because there is no per-connection session, always pass capability_token explicitly to paybond_authorize_agent_spend / paybond_verify_capability rather than relying on the token being remembered from an earlier call in the same process (that convenience only applies to a single long-lived stdio session).

Example MCP client config for an HTTP-capable host (prefer a restricted key in production):

{
  "url": "https://mcp.paybond.ai/mcp",
  "headers": {
    "Authorization": "Bearer paybond_rk_sandbox_..."
  }
}

Self-hosting the same contract: both Kit CLIs can run the identical Bearer/Origin//healthz contract locally instead of depending on the hosted endpoint:

# TypeScript
npx paybond-kit mcp serve --transport http

# Python (requires the optional `mcp` extra: pip install "paybond-kit[mcp]")
paybond-kit mcp serve --transport http

The Python CLI binds to one tenant for the life of the process: set PAYBOND_API_KEY in the process environment and every incoming Bearer token is checked against that single key (a shared-secret gate on network access, not a way to serve multiple tenants from one process). The TypeScript CLI is multi-tenant like the hosted endpoint — it derives tenant scope per request from whatever key the caller presents. Both accept the same PAYBOND_MCP_HTTP_* environment variables:

export PAYBOND_MCP_HTTP_ADDR="0.0.0.0:8080"                       # default
export PAYBOND_MCP_HTTP_ALLOWED_ORIGINS="https://example.com"     # comma-separated; only enforced when a client sends an Origin header
export PAYBOND_MCP_HTTP_MAX_BODY_BYTES="1048576"                  # 1 MiB default
export PAYBOND_MCP_HTTP_RATE_LIMIT_PER_MINUTE="120"                # per authenticated API key
export PAYBOND_MCP_HTTP_RATE_LIMIT_UNAUTH_PER_MINUTE="30"          # per source IP, slows credential scanning

GET /healthz returns 200 without auth for load-balancer and container health checks. PAYBOND_POLICY_RELOAD=watch|poll (policy hot reload) is rejected at HTTP startup — it depends on one long-lived process instance and is only supported for stdio; use PAYBOND_POLICY_RELOAD=off (the default) or omit it entirely when running HTTP.

Tool categories

Read-only discovery and compliance (allowed under --tool-policy readonly):

  • paybond_get_principal
  • paybond_list_intents
  • paybond_get_intent
  • paybond_list_audit_exports
  • paybond_get_audit_export
  • paybond_get_reputation_receipt
  • paybond_get_portfolio_summary
  • paybond_get_signed_portfolio_artifact
  • paybond_get_fraud_assessment
  • paybond_get_fraud_metrics
  • paybond_get_a2a_agent_card
  • paybond_list_a2a_task_contracts
  • paybond_get_a2a_task_contract
  • paybond_verify_agent_mandate_v1
  • paybond_verify_agent_recognition_proof_v1
  • paybond_get_settlement_receipt_v1
  • paybond_verify_protocol_receipt_v1
  • paybond_get_agent_receipt_v1
  • paybond_verify_agent_receipt_v1
  • paybond_validate_completion_evidence
  • paybond_get_budget_remaining
  • paybond_explain_policy

Spend and mutation tools (default --tool-policy spend-write; live-money tools such as paybond_fund_intent and paybond_confirm_settlement stay blocked unless explicitly allowlisted):

  • paybond_verify_capability
  • paybond_authorize_agent_spend
  • paybond_bootstrap_sandbox_guardrail
  • paybond_submit_sandbox_guardrail_evidence
  • paybond_import_agent_mandate_v1
  • paybond_create_intent
  • paybond_create_spend_intent
  • paybond_fund_intent
  • paybond_submit_evidence
  • paybond_submit_spend_evidence
  • paybond_confirm_settlement

For production hosts, mint a restricted key (see Restricted MCP keys) so the credential itself limits the tool surface. For local standard-key installs, a readonly env policy still works:

# Preferred (scopes on the key)
paybond keys create --name cursor --role analyst --kind restricted --preset mcp-readonly
paybond mcp install --host generic --scope project

# Local standard-key override (dev only)
paybond mcp install --host generic --scope project --tool-policy readonly

Local audit bundle verification (paybond audit exports verify <path> or paybond.audit.exports.verify(...)) is SDK/CLI only. MCP hosts cannot verify downloaded ZIP paths on disk. Compliance bundles that include agent_receipts may also contain PEF companion files (*.pef.json) alongside each signed receipt.

The spend-named tools are aliases over the same tenant-bound Harbor and Gateway routes. They exist so agent hosts can match user requests like "control agent spend", "add tool-call spend limits", or "authorize paid vendor actions" without guessing from lower-level capability names.

paybond_get_budget_remaining and paybond_explain_policy call the side-effect-free gateway route POST /v1/spend/preflight. They evaluate the same spend-control policy as authorize without creating decisions, reservations, or approval requests. Use them before paybond_authorize_agent_spend when an agent needs remaining budget or a human-readable allow / approval_required / deny explanation. Matching CLI commands: paybond spend budget-remaining and paybond spend explain-policy.

paybond_verify_protocol_receipt_v1 is a read-only offline verify of a signed protocol-v2 authorization or settlement receipt (POST /protocol/v2/receipts/verify). Pass the full receipt object (not a receipt_id). Use paybond_verify_agent_mandate_v1 for mandate envelopes and paybond_verify_capability / paybond_authorize_agent_spend for Harbor capability gates. Fetch a settlement receipt first with paybond_get_settlement_receipt_v1 when you only have an intent UUID.

paybond_get_agent_receipt_v1 fetches a signed paybond.agent_receipt_v1 by receipt_id (tenant-bound GET /protocol/v2/agent-receipts/{receipt_id}). paybond_verify_agent_receipt_v1 runs the same offline operational-tier signature check as resources/read on paybond://receipt/{receipt_id}; pass optional validity_tier=primary|attested when you need a stronger bar. Continuity-chain, inclusion proofs, owner disclosure, and ACTA/PEF/SCITT adapters remain Kit TypeScript/Python and CLI/Gateway auditor surfaces—not MCP’s full job.

paybond_get_principal returns the tenant-bound service-account principal for the configured PAYBOND_API_KEY (tenant_id, subject, roles) via a read-only gateway GET. Use it when you need to confirm auth identity; call early as a prerequisite before intent lifecycle calls, Signal reads, or other tenant-scoped tools when tenant identity is unknown. Not required before every later call once tenant_id is already known. Do not use it when you need intent detail—use paybond_get_intent instead when you have an intent_id. Do not use it for A2A discovery—use paybond_get_a2a_agent_card instead.

paybond_get_portfolio_summary returns a tenant-scoped Signal aggregate (counts, average score, volume, operators under review). Omit score_version to use the gateway default (1.0). Prefer paybond_get_signed_portfolio_artifact when you need a portable signed operator list for partner or verifier sharing, and paybond_get_reputation_receipt for one operator.

paybond_get_signed_portfolio_artifact returns a tenant-scoped signed Signal portfolio snapshot (operator list plus Ed25519 signing material) for offline verifier checks or partner sharing. Omit score_version to use the gateway default (1.0). Prefer paybond_get_portfolio_summary for unsigned aggregates, paybond_get_reputation_receipt for one operator, and paybond_get_fraud_assessment for fraud review posture.

paybond_get_fraud_metrics returns tenant-wide fraud backtesting metrics for a rolling window (24h default, or 7d / 30d). Unsupported windows fail with HTTP 400. Use paybond_get_fraud_assessment when you need one operator's fraud posture instead of tenant aggregates.

paybond_get_reputation_receipt fetches the signed Signal reputation receipt for one operator DID (GET /reputation/{operator_did}). Omit score_version to use the gateway default (1.0). Returns null when no receipt exists. Prefer paybond_get_portfolio_summary for tenant aggregates, paybond_get_signed_portfolio_artifact for a portable signed operator list, and paybond_get_fraud_assessment for fraud review posture.

The sandbox guardrail tools are separate developer-only helpers. They call /v1/sandbox/guardrails/..., derive tenant scope from the configured service-account API key, and do not replace the production Harbor create/fund/evidence tools.

Typical spend flow

  1. Call paybond_create_spend_intent to create the signed spend intent.
  2. If the intent is not funded immediately, call paybond_fund_intent.
  3. Optionally call paybond_get_budget_remaining or paybond_explain_policy for a read-only preflight of remaining budget and policy outcome.
  4. Use the returned intent_id and capability_token with paybond_authorize_agent_spend before any paid API call, vendor action, settlement step, or other side-effecting tool.
  5. Call paybond_validate_completion_evidence with the completion preset and payload you plan to submit.
  6. After the guarded work completes, call paybond_submit_spend_evidence with the same preset and payload.

If you are writing SDK code instead of exposing MCP tools, use paybond.spendGuard(intentId, capabilityToken) in TypeScript or paybond.spend_guard(intent_id, capability_token) in Python. PaybondCapabilityBinding is only needed for Python framework adapters that require a run-context object.

Sandbox guardrail smoke flow

  1. Call paybond_bootstrap_sandbox_guardrail with an operation and sandbox spend amount.
  2. Use the returned intent_id and capability_token with paybond_authorize_agent_spend before the sample paid tool executes.
  3. Call paybond_validate_completion_evidence when using a completion preset (for example api_response_ok).
  4. Call paybond_submit_sandbox_guardrail_evidence with the sandbox intent_id, completion_preset_id, and evidence payload.

Bundled smoke command

Validate the MCP authorize and evidence path without launching a stdio subprocess or an LLM:

paybond agent demo mcp smoke \
  --operation paid-tool \
  --requested-spend-cents 100 \
  --evidence-preset cost_and_completion \
  --format json

The smoke uses in-process PaybondMCPServer.callTool() (TypeScript) or build_mcp_server().call_tool() (Python): sandbox bind, paybond_authorize_agent_spend, mock side-effect completion, and paybond_submit_sandbox_guardrail_evidence. Python requires the optional mcp extra (pip install "paybond-kit[mcp]").

Security model

  • The server is bound to one tenant derived from the configured service-account API key.
  • Do not pass tenant IDs manually through tool arguments for normal flows.
  • Gateway-backed state-changing tools require the right proof material and fail closed when proofs are missing, stale, replayed, or mismatched.
  • Signed Harbor request bodies remain the caller's responsibility. The MCP server does not manage long-lived signing keys on behalf of the model.
  • The hosted HTTP endpoint (https://mcp.paybond.ai/mcp) accepts:
    • Restricted keys (paybond_rk_*) — preferred for agents and automation; scopes on the key are the permission model.
    • Standard keys (paybond_sk_*) — full role entitlements; use only when you intentionally want an unrestricted machine credential.
    • MCP OAuth access tokens (paybond_oat_*) — user-scoped grants from the Console consent flow for interactive hosts (Cursor, Claude, VS Code). Short-lived; refresh with paybond_ort_* via POST /v1/oauth/token.
  • Prefer a sandbox restricted key for testing; for production hosts mint a restricted key with the minimum MCP scopes in Console, or use OAuth consent for interactive hosts.

Check which credential a host config will actually use:

paybond doctor --mcp --host claude

--mcp fails when the config resolves to an unrestricted paybond_sk_* key (the gateway cannot cap its MCP surface) and again when that key is not even narrowed by PAYBOND_MCP_TOOL_POLICY. Pass --config <path> to grade an on-disk host config instead of the one paybond mcp install would generate.

MCP OAuth (interactive hosts)

Interactive MCP hosts can obtain a scoped bearer without embedding a long-lived API key:

  1. Host redirects the browser to GET /v1/oauth/authorize (PKCE S256, response_type=code).
  2. Console opens /console/authorize/mcp?request=… for a human tenant_admin.
  3. Admin reviews requested scopes / visible tools, optionally narrows the grant, and approves (live mcp.settlement:write requires MFA step-up).
  4. Host redeems the code at POST /v1/oauth/token and calls https://mcp.paybond.ai/mcp with Authorization: Bearer paybond_oat_….

Tenant admins manage the per-environment MCP kill switch, active OAuth sessions, and custom redirect URIs under Console → Machine access → MCP access. Disabling MCP blocks new grants and rejects existing paybond_oat_* bearers for that environment.

Example MCP client config

Example local stdio entry using the default .env.local written by paybond login:

{
  "command": "npx",
  "args": ["-y", "-p", "@paybond/kit", "paybond-mcp-server"],
  "env": {
    "PAYBOND_ENV_FILE": ".env.local"
  }
}

Advanced direct-key entry for hosts that cannot read env files:

{
  "command": "npx",
  "args": ["-y", "-p", "@paybond/kit", "paybond-mcp-server"],
  "env": {
    "PAYBOND_API_KEY": "paybond_sk_sandbox_..."
  }
}

Agent receipt resources (MCP)

MCP’s ARS role is agent-to-agent receipt handoff via the resource URI paybond://receipt/{receipt_id}—hosts pass the URI between agents without embedding full JSON in prompts.

ItemValue
URI templatepaybond://receipt/{receipt_id}
MIME typeapplication/json
SourceTenant-bound GET /protocol/v2/agent-receipts/{receipt_id}
Verify on readOperational-tier signature check (schema, digest, Gateway Ed25519)

TypeScript and Python stdio servers:

  • resources/templates/list — publishes the paybond://receipt/{receipt_id} template
  • resources/read — fetches the signed paybond.agent_receipt_v1 JSON and verifies at the operational tier before returning; verification failure returns a clear error (contents are not handed off unsigned)

Readonly tools (same surface as settlement-receipt tools):

  • paybond_get_agent_receipt_v1 — fetch by receipt_id
  • paybond_verify_agent_receipt_v1 — offline verify of a receipt object; optional validity_tier (operational default, or primary / attested)

Validity tiers beyond a quick handoff check, continuity-chain audits, Merkle inclusion / tree-head proofs, confidential owner disclosure, and ACTA / PEF / SCITT export adapters are Kit TypeScript/Python, CLI, and Gateway auditor surfaces—not MCP’s full job. Use Kit client libraries for those flows.

Compliance audit exports that include agent receipts may also ship PEF companions (*.pef.json) beside each signed receipt JSON.