Developer docs

Build agents that can pay, with a wallet they cannot drain.

AgentsPay is a local MCP server. It gives an agent five tools for balance, budget, x402 payment, audit logs, and top-up instructions. The current product is devnet-first, self-custodial, and designed for developers shipping inside Claude Code, Cursor, Cline, or Zed.

01

What You Are Installing

Local binary

The MCP host launches agentspay-mcp over stdio. There is no hosted AgentsPay control plane in the v0.3 path.

Self-custodial signer

The keypair lives on your machine at ~/.agentspay/keypair.json unless AGENTSPAY_KEYPAIR_PATH overrides it.

Policy before signature

max_amount_usdc and configured budgets are checked before the code builds a Solana transaction.

MCP host
agentspay-mcp
x402 endpoint
Solana devnet
SQLite audit log

02

Install In 60 Seconds

Build the release binary and register it with your MCP host. Use an absolute path once you move beyond a local checkout.

$ git clone https://github.com/h4ckm1n-dev/agentspay && cd agentspay
$ cargo build --release -p agentspay-mcp
$ claude mcp add agentspay $PWD/target/release/agentspay-mcp

Important

Call agentspay_set_budget before the first paid call. The balance view displays a default budget when no row exists, but the active enforcement policy is created by agentspay_set_budget.

03

MCP Host Configs

Every host needs the same thing: a command that launches the binary. Environment variables are optional and can be added when you want sandbox mode, a custom ledger, or a custom keypair path.

Claude Code

claude mcp add agentspay /abs/path/agentspay-mcp

Cursor

{
  "mcpServers": {
    "agentspay": {
      "command": "/abs/path/agentspay-mcp"
    }
  }
}

Cline

{
  "mcpServers": {
    "agentspay": {
      "command": "/abs/path/agentspay-mcp",
      "env": {
        "AGENTSPAY_NETWORK": "solana-devnet"
      }
    }
  }
}

Zed

{
  "context_servers": {
    "agentspay": {
      "command": "/abs/path/agentspay-mcp"
    }
  }
}

04

First Run Checklist

  1. 1Start the MCP host and confirm it lists the five AgentsPay tools.
  2. 2Call agentspay_topup_info and copy the returned pubkey.
  3. 3Fund that pubkey with devnet SOL and devnet USDC.
  4. 4Call agentspay_set_budget with a small daily and per-call cap.
  5. 5Call agentspay_balance and verify the environment is solana-devnet.
  6. 6Try a sandbox endpoint first, then a devnet x402 endpoint.
Suggested first prompts
Call agentspay_topup_info.
Set my AgentsPay budget to 5 USDC per day and 0.25 USDC per call.
Show my AgentsPay balance.
Pay http://localhost:3001/real-quote/AAPL with max_amount_usdc 0.25.

05

TypeScript SDK and CLI

The MCP binary is the primary surface, but you don't need an MCP host to use AgentsPay. Two npm packages wrap the binary:@agentspay/sdk-js for Node.js apps, and@agentspay/cli for a terminal command.

TypeScript SDK
pnpm add @agentspay/sdk-js
# also requires agentspay-mcp on PATH

import { AgentsPayClient } from "@agentspay/sdk-js";
const client = new AgentsPayClient({ network: "solana-devnet" });
const balance = await client.balance();
const r = await client.payUrl({
  url: "https://api.example.com/quote",
  maxAmountUsdc: "0.50",
});
CLI
pnpm add -g @agentspay/cli
agentspay --help

agentspay balance
agentspay pay-url <url> --max 0.50
agentspay set-budget --daily 25 --per-call 1
agentspay audit-log --limit 5
agentspay topup-info
# add --json for raw output

Same binary under the hood

Both the SDK and CLI spawn the same agentspay-mcp binary as a subprocess and talk JSON-RPC over stdio. Whatever your MCP host sees, the SDK and CLI see the same. Typed errors (BudgetExceededError, PerCallCapExceededError, X402SettlementError, ...) let you handle each failure mode discretely.

Full package documentation: @agentspay/sdk-js README · @agentspay/cli README. Both ship the same five tools with typed errors and pretty + JSON output.

06

Tool Reference

agentspay_balance

Inspect the local wallet row, budget settings, today's spend, and signing pubkey.

agentspay_balance()

Args

No arguments.

Returns

  • available_usdc: local wallet-row balance string
  • today_spent_usdc: sum of paid ledger rows since UTC midnight
  • daily_cap_usdc and per_call_cap_usdc: configured budget or displayed default
  • budget_remaining_today_usdc: daily cap minus today's ledger spend
  • environment: sandbox, solana-devnet, or solana-mainnet
  • solana_pubkey: base58 signer pubkey
Example response
{
  "available_usdc": "100.00",
  "today_spent_usdc": "0.10",
  "daily_cap_usdc": "25.00",
  "per_call_cap_usdc": "1.00",
  "budget_remaining_today_usdc": "24.90",
  "environment": "solana-devnet",
  "solana_pubkey": "GmBDzsdcPBNpeGchxX2GkZTKYtuCKnj7wyHiYaL9zPEm"
}

agentspay_pay_url

Call an x402-priced URL, cap the quoted amount, sign if allowed, retry, and persist proof.

agentspay_pay_url(url, max_amount_usdc)

Args

{
  "url": "http://localhost:3001/real-quote/AAPL",
  "max_amount_usdc": "0.50"
}

Returns

  • status and payment_id
  • amount_charged_usdc quoted from the x402 requirement
  • ledger_entry_id when a paid call is recorded
  • transaction and explorer_url for Solana settlements
  • payment_status: paid or none
  • body: upstream response body as a string
Example response
{
  "status": "ok",
  "amount_charged_usdc": "0.10",
  "payment_status": "paid",
  "network": "solana-devnet",
  "transaction": "4pGRMVgu7j5itCs7Vf6G9FTQW2Q1B2SjCEKHszLjvF9eVagWvtWq8aJWuYz1JNpBQr4CsbYRXSb9aWAu5hv6jYau",
  "explorer_url": "https://solscan.io/tx/4pGR...jYau?cluster=devnet"
}

agentspay_set_budget

Create or update the active per-call and daily spend policy.

agentspay_set_budget(daily_usd, per_call_usd)

Args

{
  "daily_usd": 25,
  "per_call_usd": 1
}

Returns

  • agent_id: currently default
  • daily_usd and per_call_usd as accepted
  • updated_at_rfc3339 for auditability
Example response
{
  "agent_id": "default",
  "daily_usd": 25,
  "per_call_usd": 1,
  "updated_at_rfc3339": "2026-05-16T13:00:00Z"
}

agentspay_audit_log

Return recent tool attempts, including rejected and successful payment calls.

agentspay_audit_log(limit?)

Args

{
  "limit": 20
}

Returns

  • entries ordered newest first
  • total count in the local audit table
  • returned count after the limit is applied
  • limit defaults to 20 and clamps at 100
Example response
{
  "returned": 2,
  "total": 2,
  "entries": [
    {
      "tool": "agentspay_pay_url",
      "amount_usdc": "0.10",
      "status": "ok payment_status=paid network=solana-devnet"
    }
  ]
}

agentspay_topup_info

Show the pubkey and faucet instructions needed to fund the local signer.

agentspay_topup_info()

Args

No arguments.

Returns

  • pubkey to paste into faucets
  • network
  • Circle USDC faucet URL
  • Solana SOL faucet URL
  • manual funding instructions
Example response
{
  "pubkey": "GmBDzsdcPBNpeGchxX2GkZTKYtuCKnj7wyHiYaL9zPEm",
  "network": "solana-devnet",
  "faucet_url": "https://faucet.circle.com",
  "sol_faucet_url": "https://faucet.solana.com"
}

07

Payment Flow

  1. 1agentspay_pay_url validates the URL and max_amount_usdc.
  2. 2It performs a GET probe against the target URL.
  3. 3If the target returns 200, AgentsPay records a no-payment audit entry and returns the body.
  4. 4If the target returns 402, AgentsPay parses accepts[] and selects the entry matching AGENTSPAY_NETWORK.
  5. 5The x402 quoted amount must be <= max_amount_usdc.
  6. 6If a budget row exists, per_call_usd and daily_usd are checked while a Tokio mutex serializes payment calls.
  7. 7Sandbox mode emits a placeholder X-Payment payload.
  8. 8Solana devnet mode builds an SPL transfer_checked transaction and base64 encodes it into X-Payment.
  9. 9AgentsPay retries the same URL with X-Payment and parses X-Payment-Response.
  10. 10Paid calls write a ledger row and an audit row atomically.

Current HTTP shape

The implemented pay_url path uses GET for the probe and retry. POST support is a future extension, not current behavior.

08

Budgets, Ledger, And Audit Proof

Budget table

One default agent row stores daily_usd and per_call_usd. Updates are audited.

Daily spend

The repo sums ledger_entry.amount_usdc for rows since UTC midnight.

Critical section

pay_url holds a Tokio mutex while checking budget, settling, and recording the result.

Audit trail

Rejected calls, no-payment calls, budget updates, and paid calls are visible through agentspay_audit_log.

SQLite state
wallet
budget
policy
ledger_entry
audit_log
seaql_migrations

09

Devnet Funding

Devnet settlement needs two balances on the signer: SOL for fees and USDC for the SPL transfer. Circle's USDC faucet requires a browser captcha, so AgentsPay returns instructions instead of trying to auto-fund.

Local sandbox mode
AGENTSPAY_NETWORK=sandbox \
AGENTSPAY_DATABASE_URL=sqlite:///tmp/agentspay-sandbox.db?mode=rwc \
./target/release/agentspay-mcp

10

Website Demo Stack

The public demo does not reimplement the product in JavaScript. Browser requests go through a Rust shim that spawns the same MCP binary per call.

Browser
Next.js API proxy
web-shim
agentspay-mcp
paid-endpoint
Solana RPC

Sandbox sessions

POST /api/sandbox/session creates an isolated tempdir, keypair, and SQLite ledger for 30 minutes.

Sandbox calls

POST /api/sandbox/call is limited to 60 calls per minute per session.

Devnet trigger

POST /api/devnet/trigger is limited to 1 call per IP per hour.

Latest proof

GET /api/stats/latest-tx returns the most recent settlement for 24 hours and hydrates from SQLite on restart.

$ cp docker/.env.example docker/.env
$ docker compose -f docker/docker-compose.yml -f docker/docker-compose.local.yml up --build

11

Environment Variables

VariableDefaultPurpose
AGENTSPAY_NETWORKsolana-devnetMCP settlement mode. Use sandbox for no-chain local testing.
AGENTSPAY_KEYPAIR_PATH~/.agentspay/keypair.jsonSolana CLI-compatible signer JSON, created mode 0600.
AGENTSPAY_DATABASE_URLsqlite://~/.agentspay/agentspay-mcp.db?mode=rwcSeaORM SQLite ledger and audit database.
AGENTSPAY_SOLANA_RPC_URLhttps://api.devnet.solana.comRPC endpoint used to fetch blockhashes and balances.
RUST_LOGagentspay_mcp=infoStructured logs and warning verbosity.
NO_COLORunsetDisable stderr ANSI color from the MCP banner.
FORCE_COLORunsetForce ANSI color for Docker logs or CI output.
AGENTSPAY_SHIM_LISTEN_ADDR0.0.0.0:8080HTTP bind address for the web shim.
AGENTSPAY_MCP_BINARY/usr/local/bin/agentspay-mcpBinary spawned by the shim for browser demos.
AGENTSPAY_REDIS_URLunsetRedis sessions and rate limits. Unset uses in-memory stores.
AGENTSPAY_DEVNET_WALLET_PATH/data/devnet-wallet.jsonFunded demo signer used by /api/devnet/trigger.
AGENTSPAY_DEVNET_LEDGER_PATH/data/devnet-ledger.dbPersistent demo ledger and latest-tx cache.
AGENTSPAY_PAID_ENDPOINT_URLhttp://localhost:3001Demo x402 provider target.
AGENTSPAY_PROVIDER_KEYPAIR~/.agentspay/provider-keypair.jsonProvider receiver keypair for the demo endpoint.
AGENTSPAY_DEMO_PAYTOderived provider pubkeyExplicit receiver pubkey override for demo quotes.
AGENTSPAY_USE_FACILITATORfalseOpt-in x402.org facilitator path in the demo provider.
AGENTSPAY_FACILITATOR_URLhttps://x402.org/facilitatorFacilitator base URL when enabled.
AGENTSPAY_ALLOW_PRIVATE_HOSTSunset (= disabled)When set to 1, allow pay_url to fetch loopback / RFC1918 / link-local hosts. Required for local dev against the demo provider; never set in production.
AGENTSPAY_ALLOWED_ORIGINSunset (= origin guard disabled)Comma-separated allowlist of browser origins for mutating shim endpoints. Production should set this to your deployed frontend origin.

12

Security Model

The agent cannot drain your wallet. The full threat model, every finding, and the adversarial test suite live in SECURITY-AUDIT.md. The regression suite runs on every push: 46 Rust tests (7 SSRF, 6 adversarial x402, 3 origin-guard, plus the rest of the workspace) and 10 TypeScript tests covering the SDK error classifier. Highlights below.

Per-call + daily caps

agentspay_pay_url checks both caps before signing. Even an attacker-controlled URL can extract at most per_call_usd per call and daily_usd per day.

SSRF guard

URLs that resolve to loopback, RFC1918, link-local (incl. AWS/GCP IMDS at 169.254.169.254), CGNAT, or IPv6 ULA are rejected. Opt-out via AGENTSPAY_ALLOW_PRIVATE_HOSTS=1 for local dev.

Asset + decimals validated

A malicious x402 seller cannot inflate the transfer by quoting funky decimals or a non-USDC mint. Validators reject decimals != 6 and asset != USDC mint in real-signing modes.

1 MiB body cap

Both the 402 probe and the post-payment retry read at most 1 MiB. No OOM from an attacker streaming gigabytes.

Local key custody, 0600

The signer is generated locally at ~/.agentspay/keypair.json with owner-only permissions on Unix. Never logged, never sent over the wire.

Non-root containers

All three Docker images run as uid 10001 (agentspay) or uid 1000 (node). No root inside any container.

Real-IP rate limits

Public web-shim rate-limits on the real client IP via X-Forwarded-For (Caddy strips client-provided values). Per-IP, not global.

Origin guard

Mutating shim endpoints require an allowlisted Origin header when AGENTSPAY_ALLOWED_ORIGINS is set. Defense in depth against cross-origin CSRF-like attacks.

Trust boundary

The MCP host is the local auth boundary for v0.3. There is no multi-tenant server, no dashboard auth, no webhook delivery, and no production custody service in this release. Mainnet is gated behind a v0.5 compliance review.

13

Troubleshooting

The agent can see tools, but payment is rejected by budget.

Cause: The quoted x402 amount is above max_amount_usdc, above per_call_usd, or would cross daily_usd.

Fix: Call agentspay_balance, then lower the URL price or raise caps with agentspay_set_budget.

Devnet payment fails with account debit or insufficient funds.

Cause: The signer has no SOL for fees, no devnet USDC, or no initialized token account.

Fix: Run agentspay_topup_info and fund the returned pubkey from both Solana and Circle faucets.

MCP host starts but cannot find AgentsPay.

Cause: The host config points at a relative path or a binary that was not built.

Fix: Use an absolute path to target/release/agentspay-mcp and run cargo build --release -p agentspay-mcp again.

The website sandbox returns session gone.

Cause: Browser session state expired. The shim keeps sandbox sessions for 30 minutes.

Fix: Refresh the page or create a new sandbox session.

The devnet demo button is disabled or drained.

Cause: The server-controlled demo wallet is below 0.05 SOL or 2 USDC.

Fix: Seed docker_wallet-data with devnet-wallet.json and refill it from the faucets.

Facilitator mode rejects the Solana payload.

Cause: The direct-RPC path signs a fee-paying transaction. Some facilitators expect a sponsored format.

Fix: Use direct RPC for the current demo path. Treat facilitator mode as experimental until the payload format is refactored.

14

Developer Commands

Rust
cargo check --workspace
cargo test --workspace
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --check
Frontend
pnpm install
pnpm -r typecheck
pnpm --filter frontend lint
pnpm --filter frontend build
Native smoke
./scripts/devnet-smoke-test.sh
cargo run -p agentspay-paid-endpoint-demo
AGENTSPAY_NETWORK=sandbox cargo run -p agentspay-mcp
Docker web image
docker compose -f docker/docker-compose.yml \
  -f docker/docker-compose.local.yml build web