Skip to main content
Glama

Agent Wallet MCP

tests

A prepaid, capped payment method for an AI agent, exposed over MCP. The agent never touches your real bank accounts — it gets an isolated float and a server that lets it request charges but never unilaterally move money.

Safety rests on five properties: isolation, capping, revocability, out-of-band confirmation, and auditability.

▶️ Try it in your browser (no signup, mock money): https://agentwallet.loopdesign.com.au/demo — make a mock agent request charges and watch the real policy engine auto-clear, hold for approval, or deny (including a prompt-injected "$5,000 to a scammer" that gets refused). Landing page: https://agentwallet.loopdesign.com.au

The one rule that matters

The agent can only call request_charge, which creates a pending intent. The server (policy engine) decides whether it clears. Charges at/above your threshold wait for you to approve on a channel the agent cannot reach (cli_approve.py or Telegram). A prompt-injected agent can ask to send $5,000 to a scammer; the server just says no.

Related MCP server: agent-verifier-mcp

Layers of protection

Layer

Setting

Hard float ceiling (blast radius)

WALLET_FLOAT_CENTS (default $200)

Per-transaction cap

WALLET_PER_TXN_CAP_CENTS (default $50)

Rolling 24h spend cap

WALLET_DAILY_CAP_CENTS (default $100)

Rolling 1h transaction count

WALLET_HOURLY_TXN_LIMIT (default 5)

Auto-approve threshold

WALLET_AUTO_APPROVE_UNDER_CENTS (default 0 = confirm everything)

Merchant allow / deny lists

WALLET_MERCHANT_ALLOWLIST / _DENYLIST

Out-of-band approval

cli_approve.py (agent cannot call it)

Audit log

every request + capture in the audit table

How a charge flows

flowchart TD
    A["AI agent<br/>(can only request)"] -->|request_charge| P{Policy engine<br/>caps · velocity · merchant}
    P -->|over cap / denylist / no float| D["DENY<br/>agent cannot override"]
    P -->|under auto-approve threshold| C["Capture against float"]
    P -->|at / above threshold| H["Pending intent<br/>held for a human"]
    H -.->|out-of-band, agent can't reach| U["You: Telegram · dashboard · CLI"]
    U -->|approve| RC{Re-check at capture}
    U -->|deny| D
    RC -->|still in policy| C
    RC -->|now out of policy| D
    C --> L[("Append-only ledger<br/>balance derived from captures")]
    D --> L

Everything the agent can touch is on the left; the approval channel on the right is deliberately out of its reach. Play with this exact flow in your browser.

Quick start (no secrets, mock money)

pip install -r requirements.txt
python test_policy.py          # 8/8 — the policy engine
python test_oauth.py           # 8/8 — the OAuth 2.1 core (16 tests total)
python server.py               # starts the MCP server (stdio)

With WALLET_AUTO_APPROVE_UNDER_CENTS=0, every charge the agent requests pauses as pending_approval and prints an approval line to the console:

python cli_approve.py                    # list what's waiting
python cli_approve.py <intent_id>        # approve + capture
python cli_approve.py <intent_id> --deny

Local dashboard

A single-page web UI to watch the balance, approve/deny pending charges, and read history — dependency-free (stdlib), bound to 127.0.0.1, and token-protected. Like the CLI and Telegram, it's an out-of-band approver the agent cannot reach.

python dashboard.py     # prints http://127.0.0.1:8787/?token=... — open it

Set DASHBOARD_TOKEN to pin a token, or leave it blank to auto-generate one each run.

Tools the agent sees

  • get_balance() — float / spent / remaining

  • get_policy() — the active limits

  • request_charge(amount, merchant, reason) — creates a pending intent only

  • get_intent(intent_id) — poll the outcome

  • list_transactions(limit) — recent history

Connecting an agent

  • Claude Desktop / MCP clients (stdio): add this server (command python, arg server.py, cwd = this folder) to the client's MCP config.

  • ChatGPT (HTTP + OAuth): run server_http.py, which wraps the same tools in an HTTP endpoint fronted by a real OAuth 2.1 flow (see below).

ChatGPT connector (OAuth)

server_http.py (Starlette) serves the MCP endpoint at /mcp plus a full OAuth 2.1 authorization server (oauth.py): Dynamic Client Registration, Authorization Code + PKCE, refresh tokens, discovery metadata, and bearer verification scoped to wallet.read + wallet.request_charge. The /authorize step is gated by your OWNER_PASSCODE, so only you can approve a connector.

pip install -r requirements.txt
export OAUTH_ISSUER=https://your-public-host   # ChatGPT requires HTTPS
export OWNER_PASSCODE=some-strong-secret
uvicorn server_http:app --port 8000            # put an HTTPS tunnel in front

In ChatGPT, add a custom connector pointing at <OAUTH_ISSUER>/mcp; it discovers the OAuth endpoints, registers itself, and sends you to /authorize to sign in with the passcode. The OAuth core is unit-tested (test_oauth.py), but the end-to-end ChatGPT flow is yours to deploy behind HTTPS and verify. Never expose cli_approve.py, dashboard.py, or the Telegram/Stripe processes to the agent.

Approve from your phone (Telegram)

  1. Create a bot with @BotFatherTELEGRAM_BOT_TOKEN.

  2. Message the bot once, open https://api.telegram.org/bot<token>/getUpdates, read your numeric chat id → TELEGRAM_CHAT_ID.

  3. WALLET_APPROVER=telegram, then run the poller alongside the server:

python telegram_poller.py

Now every over-threshold charge pings your phone with Approve / Deny buttons; one tap finalizes it. Only messages from your own TELEGRAM_CHAT_ID are honoured, and the agent has no access to Telegram — that's what makes it safe. You can also send /pending, /approve <id>, /deny <id>.

Stripe Issuing is ~$0.10 per virtual card with no forced monthly minimum — you fund a Stripe Issuing balance and spend against it. First confirm Issuing is enabled on your Stripe account (Dashboard → Issuing; it may need activation, and this is where you verify current availability in your country).

Stripe cards are pull-based: the merchant charges the card, so the hard ceiling is the card's own spending_controls, enforced by Stripe independently of this server.

  1. Create the card once (test mode first). In a Python REPL:

    from issuer import StripeIssuer   # needs STRIPE_API_KEY set
    print(StripeIssuer().provision_card("Agent Wallet", "you@example.com",
                                         per_auth_cap_cents=5000, monthly_cap_cents=20000))

    Paste the printed id into STRIPE_CARD_ID, set WALLET_ISSUER=stripe.

  2. Fund the Stripe Issuing balance with your set float — that's the max you can lose.

  3. Turn on real-time authorizations and run the webhook so the policy engine gates each live charge (auto-declines out-of-policy amounts/merchants):

    stripe listen --forward-to localhost:4242/webhook   # Stripe CLI, in one terminal
    python stripe_webhook.py                             # in another

    For this path set WALLET_AUTO_APPROVE_UNDER_CENTS to your per-transaction cap so the card works up to the cap while float/24h/velocity/merchant limits still bind.

  4. Set WALLET_APPROVER=telegram and run the poller so large buys the agent flags via request_charge hit your phone for a tap before it uses the card.

Alternative issuer (Airwallex, WALLET_ISSUER=airwallex): auth, a Payouts/Transfers execute_payment, and a reconciliation read are implemented, but untested against the live API and pricier — test in their demo host and verify fields first.

Kill switch

Freeze/close the card at the issuer, revoke the agent's OAuth token, or just stop server.py. The float is the maximum you can ever lose.

Files

File

Role

config.py

policy config from env

policy.py

the safety boundary — pure decision function

store.py

SQLite: intents, captures, audit; balance is derived

issuer.py

payment execution (mock / Stripe / Airwallex)

approvals.py

out-of-band notify (console / Telegram buttons)

wallet.py

orchestration shared by server + CLI + poller

server.py

MCP tools the agent can call

cli_approve.py

your approval channel — not an agent tool

telegram_poller.py

phone approvals via bot buttons — not an agent tool

dashboard.py

local web approval dashboard — not an agent tool

stripe_webhook.py

real-time Stripe authorization gate — not an agent tool

oauth.py

OAuth 2.1 authorization server for the connector

server_http.py

HTTP MCP transport + OAuth (ChatGPT connector)

test_policy.py / test_oauth.py

tests for the policy engine and OAuth core

Not implemented on purpose

OAuth for the MCP endpoint is left to you. The Stripe calls use the official SDK but should be run in test mode first; the Airwallex calls are untested against the live service. Anything that touches real money or credentials is yours to verify and authorise.

Related MCP Connectors

Related MCP Servers

  • -
    license
    Not graded
    quality
    D
    maintenance
    Enables AI agents to manage and use prepaid virtual Visa cards with hard budget limits for secure online transactions. It provides tools for creating cards, checking balances, and retrieving payment credentials with human-in-the-loop approvals.
    1
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    A wallet and guardrail for AI agents, enabling spending limits, payment approvals, and real-time ledger tracking via memory or Stripe backends.
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    AgentPay is the authorization layer between an AI agent and real spending. You define the rules — spending caps, allowed merchants, time windows — and every purchase attempt the agent makes is checked against them in real time. Approved transactions go through. Anything outside the mandate is blocked and logged. No more babysitting every agent action. No more runaway charges.
    MIT