Spend Guard
Spending limits and human approval for AI agents that move money on mobile money.
What it does
AI agents never hold payment keys. They ask Spend Guard to pay, and each request is checked against that agent's policy: allowed and paid out through MoMo One-API, held until a person approves it from a link on their phone, or denied with the exact rule that stopped it. It comes with an MCP server, so Claude and other agents can use it directly. It never holds money.
The problem
When an AI agent holds a payment key, one prompt injection, bug or retry loop can send any amount to anyone, with no record of why. Teams either build a permission layer by hand or don't build one at all.
What you get

How to use it
Run Spend Guard, add your payees, give each agent a key and a policy, then point the agent at the MCP server or the HTTP API.
1. Run it
git clone https://github.com/Jacksonoseikojo1122/spend-guard.git
cd spend-guard && npm install
OWNER_KEYS=a-long-random-owner-key npm start # http://localhost:87902. Add a payee and an agent with a policy
curl -X PUT localhost:8790/v1/recipients/supplier:kofi-hardware -H "authorization: Bearer $OWNER_KEY" \
-H "content-type: application/json" -d '{"name":"Kofi Hardware","phone":"024 123 4567","network":"mtn"}'
curl -X POST localhost:8790/v1/agents -H "authorization: Bearer $OWNER_KEY" \
-H "content-type: application/json" -d '{"name":"procurement-bot"}' # returns an sg_agent_ key once
curl -X PUT localhost:8790/v1/agents/$AGENT_ID/policy -H "authorization: Bearer $OWNER_KEY" \
-H "content-type: application/json" -d @examples/policy.json3. Let Claude use it (Claude Code)
claude mcp add spend-guard -e SPEND_GUARD_URL=http://localhost:8790 -e SPEND_GUARD_AGENT_KEY=sg_agent_... -- npx tsx /path/to/spend-guard/src/mcp-stdio.ts4. Turn on payouts
MOMO_URL=https://your-momo-one-api MOMO_API_KEY=... MOMO_WEBHOOK_SECRET=... npm startDocumentation
- Design Policies, decision order, data model, approvals, payouts, security
- AI agents (MCP) The three tools and setup for Claude Desktop and Claude Code
- OpenAPI spec Every endpoint, checked against the app in its tests
- Example policy Limits, payees, approval and working hours
Features
- Limits per agent: per payment, per day, per month and per hour, in the agent's own time zone
- A payee directory only the owner can change, so an agent can't invent where money goes
- Single-use approval links; opening one never approves, because chat apps preview links
- Holds that expire, and revoking an agent cancels its pending holds
- Payouts through MoMo One-API that can't happen twice, with webhook results checked before they're believed
- An audit log where every entry hashes the one before, so an edited entry shows
- An MCP server whose answers tell the model not to retry, split a payment or switch recipient
What building it covers
- AI agent safety
- Authorisation and policy engines
- Concurrency and locking
- Tamper-evident audit logs
- MCP servers
Stack
Questions
Does Spend Guard hold money?
No. It decides and records. Payouts go through MoMo One-API from your own account at a licensed provider such as Paystack or MTN.
Can an agent raise its own limits or approve its own payment?
No. Agents have their own keys, which can only ask to pay and read their own data. Policies, payees, approvals and revoking need the owner key.
What stops a prompt injection from talking the agent into paying?
The decision only reads typed fields: amount, currency, recipient and the agent's policy. The agent's free-text reason is shown to the human approver, clearly marked as unchecked, but the decision never reads it. And the agent can only pay payees the owner has put in the directory.
What if two payments arrive at the same moment?
Each decision takes a database lock, so the limit check and the save happen as one step. A test sends 20 payments at once against a limit with room for 6, and exactly 6 go through, on SQLite and on Postgres.
Can it pay twice?
No. Every request needs an idempotency key, and the payout to MoMo One-API uses the request's id as its own key, so a retry or a crash returns the same payout instead of making a second one.
Is it ready for real money?
It's a pilot. It has run end to end against MoMo One-API's sandbox, including a failed payout and an approved one, but not yet against a real provider.
Is it free?
Yes, it's open source under the MIT licence.