send21-budget
OfficialThis server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
send21 agent budget ๐ก๏ธ
Give an AI agent a spending budget, not your keys. The agent proposes, a human signs. Built on send21, crypto pay links and API for people and AI agents.
A real npm run demo run in mock mode. No keys, no money.
๐ก๏ธ budget-guard sits between the agent and send21's MCP server: per-period cap, per-payment max, allowed recipients and coins
โ๏ธ The agent can only propose a send21 draft; a human reviews and signs it in their own wallet
๐ฐ Spend counts only verified, matching
draft.confirmedwebhooks; pending proposals reserve budget
Quickstart
Try it now, no key:
npm install && npm run demo. Add-- --interactiveto play the human yourself, or-- --llmwithAGENT_LLM_BASE_URL+AGENT_LLM_MODEL(any OpenAI-compatible endpoint, such as a local model server) to replace the scripted agent.Budget.
cp budget.example.json budget.json: unit (USDC, or a fiat likeEUR), cap, period (day/week/month, UTC), per-payment max, coins and named recipients with their addresses.๐ Approve each recipient address in send21. New send21 accounts (created on or after 20 Oct 2026) only let a key pay addresses the owner approved. Add them in the send21 app, or propose them (MCP
propose_receiving_addresswithdirection: "Sending"), then sign in and approve each with a passkey or authenticator code. Older accounts can opt in under Settings.
send21 key for the guard only. On send21.io/api-keys, create a key with
drafts:writeanddrafts:read(addwebhooks:managefor step 4).cp .env.example .env, fill in the key and two different tokens:GUARD_TOKEN(for the agent) andADMIN_TOKEN(for you), each fromopenssl rand -hex 24.Webhook. Put only
WEBHOOK_PORTbehind your own HTTPS address (for example a reverse proxy with a free Let's Encrypt certificate), setPUBLIC_URL, runnpm run webhook:registerand paste the printedSEND21_WEBHOOK_SECRETinto.env.Run and connect your agent.
npm run build && npm start, then add the MCP config below to your client.
Without SEND21_API_KEY, npm start also runs in mock mode: sign a proposal with curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" http://127.0.0.1:8787/admin/mock/<proposal id>/sign.
Related MCP server: AgenticBTC MCP
MCP config
Put budget-guard where you would put send21's MCP server. The agent's config holds only GUARD_TOKEN; the send21 key stays inside budget-guard.
Cursor (.cursor/mcp.json) and other clients that speak Streamable HTTP:
{
"mcpServers": {
"send21-budget": {
"url": "http://127.0.0.1:8787/mcp",
"headers": { "Authorization": "Bearer <GUARD_TOKEN>" }
}
}
}Claude Desktop and other clients that launch a command (a stdio bridge to the running guard):
{
"mcpServers": {
"send21-budget": {
"command": "node",
"args": ["/absolute/path/to/send21-agent-budget/dist/src/stdio.js"],
"env": { "GUARD_URL": "http://127.0.0.1:8787/mcp", "GUARD_TOKEN": "<GUARD_TOKEN>" }
}
}
}Both are in client-config/. Compare send21's own config for the direct connection: MCP docs.
The agent gets | From |
| budget-guard: checks, reserves, then calls send21 |
| budget-guard: spent, reserved, left, reset time, recipients |
| send21, read-only, only for drafts this guard created |
| send21, read-only |
resources | send21, public |
prompt | send21 |
Not offered to the agent: create_draft, create_payment_request, submit_lightning_proof, list_address_book, get_payment_request, propose_receiving_address, get_account_status, create_account, and the prompts create_pay_link and handle_amount_mismatch. Proposing is the only way to ask for money.
How it works
sequenceDiagram
participant A as AI agent
participant G as budget-guard
participant S as send21 MCP
participant H as Human + own wallet
A->>G: propose_payment { to: "domains", amount: "9", reason }
Note over G: allowed recipient? allowed coin?<br/>โค per-payment max?<br/>spent + reserved + 9 โค cap?<br/>check and reserve in one transaction
alt blocked
G-->>A: โ over_budget + reason (nothing reaches send21)
else allowed
G->>S: create_draft (receiver, network, currency,<br/>amount, memo, idempotencyKey)
S-->>G: draft (core, coreSha256)
Note over G: draft core = what was proposed?<br/>coreSha256 holds?
G-->>A: ๐ proposed, waiting for a human
H->>S: reviews the draft, signs in own wallet
S->>G: POST /webhooks/send21 draft.confirmed (signed V2)
Note over G: verify HMAC + 300 s window, dedupe,<br/>draftId, amount and asset match<br/>โ reserved becomes spent
endA draft is the send21 object for "pay someone else": the human is the sender and signs it in their own wallet. Every proposal reserves its amount until the draft is confirmed (spent), expires, is cancelled or is declined by you (POST /admin/proposals/<id>/decline). A purchase is one purchaseId: a retry returns the same draft and never reserves twice; the send21 idempotencyKey is derived from it.
Webhook | Proposal | Budget |
(created) | ๐ proposed | reserved |
| ๐ seen | reserved |
| โ confirmed | spent (what really arrived, if an accepted amount differed) |
| ๐ฉ flagged | reserved until you look |
| โ ๏ธ review | reserved |
| ๐ฉ flagged (money moved, refunds are manual) | reserved |
| โ / โ๏ธ | released |
Lightning is not in the coin list: send21's MCP create_draft has no paymentMethod argument, and Lightning drafts need paymentMethod: "Lightning".
Agent onboarding: approved addresses
Agents onboard themselves; humans approve where the money lands. Since send21 MCP 1.5.0, new accounts only let a key use addresses the owner approved. Set up the account behind budget-guard with send21's own MCP server (docs):
get_account_status: what still stands between the agent and a live pay link (owner confirmed, receiving-address policy, approved and waiting addresses, fees, next steps).propose_receiving_address: the agent proposes an address (direction: "Receiving"for the owner's own pay-link address,"Sending"for a recipient paid with drafts, like the ones inbudget.json). It is saved unapproved.The owner approves it: signed in to send21, with a passkey or authenticator code. No API key, scope or agent can approve an address.
create_payment_request(or here,propose_paymentโcreate_draft) with approved addresses only. Any other address gets 422address_not_approvedand nothing is created.
No key yet? The agent can call create_account (POST /api/v1/accounts/agent-signup) with the owner's email and gets a limited bootstrap key. Until the owner takes the account over from the emailed link, creating anything returns 403 owner_not_confirmed. The agent never approves addresses, spends or signs: a human signs every payment in their own wallet.
Troubleshooting
address_not_approved(422): the recipient's address isn't approved in your send21 account, so no draft was created and the budget is released. Sign in to send21, approve it (passkey or authenticator code), then propose again.owner_not_confirmed(403): the account was created by an agent and the owner hasn't taken it over yet. Open the link send21 emailed you (valid 72 hours) and set up a passkey or password.
Security notes
No keys for the agent. The agent holds a guard token, not a send21 key, and no send21 key can move funds anyway. A human signs every payment in a wallet the agent does not control.
Blocked before send21. Unknown recipients, raw addresses (prompt-injection bait), other coins, amounts above the per-payment max and anything over the period cap are refused with a reason and never reach send21.
Concurrency-safe. The budget check and the reservation happen in one synchronous store transaction before the
create_draftcall, so parallel proposals cannot both pass. Tested with 50 at once. One process per data file; for several, use a database with row locks.Trust, then verify. The draft send21 returns must match the proposal (receiver, network, currency, amount) and its
coreSha256(RFC 8785) must hold, or it is ๐ฉ and the agent is told not to have it signed.Signature V2 only. Webhooks are checked with
X-Send21-Signature-V2: HMAC-SHA256 over<t>.<delivery id>.<raw body>, constant-time compare, refused when more than 300 s old or in the future. A bad signature gets401and nothing is stored.The receiver dedupes deliveries. send21 does not dedupe deliveries. Retries reuse the same
X-Send21-Deliveryid, and the receiver ignores deliveries it already handled: a delivery id, or the same event body under a new id, is applied once, written together with the budget change before the2xx. A failed write answers500so send21 retries. Separately, theidempotencyKeyreturns the same draft.Spend = confirmed and matching. Only a
draft.confirmedfor a draft this guard created, with the proposed coin, network and exact amount (base units, or fiat cents for a fiat budget), a txId andstatus: Confirmed, counts as spent. Other integrations' drafts on the same account are ignored.Clean memos. The agent's reason becomes the draft memo the human reads: plain text, max 140, no links, no hidden or bidi characters.
Local by default. The MCP port binds to
127.0.0.1with a bearer token; the admin API needs a second, different token; only the webhook port should be public. Data is one JSON file (DATA_FILE, mode 600).
Develop
npm run check # typecheck + lint + tests (budget, concurrency, idempotency, signature, dedupe, matching, MCP, LLM hook)
FORCE_COLOR=1 DEMO_FAST=1 DEMO_WIDTH=108 npm run demo > /tmp/demo.ansi && npm run render:terminal -- /tmp/demo.ansi docs/terminal.htmlThe mock uses a snapshot of the live tools/list, resources/list and prompts/list (src/mock/send21-mcp-snapshot.json). API reference: send21.io/docs ยท MCP ยท webhooks ยท llms-full.txt. More examples: send21-examples.
Questions: info@send21.io. License: MIT.
This server cannot be deployed
Maintenance
Related MCP Connectors
Give your AI an allowance: pay approved people within on-chain spending limits.
Give AI agents a wallet with a spending limit. Non-custodial USDC on Base, server-enforced limits.
Pre-spend firewall for AI agents. Approves, blocks, flags transactions against policy rules.
Spending control for AI agents: approved budgets, a decision before each payment, signed receipts.
Related MCP Servers
AlicenseAqualityDmaintenanceEnables AI agents to make payments with strict spending limits enforced by signed mandates, preventing unauthorized fund movement beyond predefined caps and allowlists.412 npmMIT- AlicenseAqualityCmaintenanceConnects AI agents to Bitcoin payment rails with owner-defined spending policies and limits.2622 npmMIT
- AlicenseNot gradedqualityDmaintenanceEnables AI agents to make autonomous payments on Solana with on-chain enforced spending limits, preventing unauthorized fund drainage.2 npm1MIT
- FlicenseNot gradedqualityCmaintenanceEnables AI agents to propose wallet payments while a local, human-authored policy decides whether each transaction is approved, requires human confirmation, or is refused, and records every decision in a signed, append-only ledger.-