← Back to tutorials

Spending Guardrails for a Muse Agent (Spend Guard)

Connect your Muse agent to A2AWire Spend Guard: pre-purchase checks, monthly caps, a per-purchase approval threshold, a merchant blocklist, and a month-to-date spend ledger. Free, and it never moves money.

Author
A2AWire
Published
Category
Security
Difficulty
beginner
Reading time
8 min read
On this page

[requires: agent key]

Muse can buy things for you: it executes purchases on its own payment methods. Nothing in that loop verifies a spending cap, asks you before an unusually large single purchase, or keeps a ledger of what your agent actually spent. Spend Guard is that missing loop, and it sits beside the money: it advises and records, it never moves money itself.

This tutorial walks the full connector: the three-step setup in Muse, the tool semantics your agent will rely on, REST and MCP examples you can run yourself, and a stdlib-only live validation script.

The public setup page is a2awire.com/muse/.


The three setup steps#

  1. Create a free A2AWire wallet. Sign up with your email at a2awire.com/wallet/ and copy your API key from the dashboard. Muse stores it through its secure credential flow.
  2. Paste the setup prompt into Muse. The prompt names the MCP server URL, the tools to call before and after each purchase, and asks you for your rules. It is reproduced verbatim below and on the Muse connector page.
  3. Talk to Muse naturally. For example: "Cap my agent at $50 a month and check with me over $25." Rule changes are one sentence, applied in one call.

The setup prompt, verbatim:

text
Build a custom integration to the A2AWire Spend Guard service. Its MCP server URL is https://a2awire.com/mcp/connectors/spend-guard/http (remote, streamable HTTP). I will give you my A2AWire API key through the secure credential flow. Before you buy anything for me, call spend_guard_check_purchase and follow its decision: approve, deny, or ask me. After any purchase, call spend_guard_record_purchase with the same tx_ref you used at check time. Set my rules with spend_guard_set_rules in one call: cap $[X] per month, ask me above $[Y], and no purchases from [merchants]. Ask me for X, Y, and the merchants now. Test every tool end to end and save the integration as a reusable skill.

The four tools#

ToolWhen the agent calls itWhat it returns
spend_guard_check_purchaseBEFORE checkoutapprove, deny, or ask_user, with a plain-English reason, the remaining monthly budget when a cap exists, and a check_id
spend_guard_record_purchaseAFTER the purchase completesthe recorded purchase, whether it was a duplicate replay, and the month-to-date total
spend_guard_set_ruleswhen the user states or changes rulesthe effective rules
spend_guard_reportwhen the user asks about spendmonth-to-date total, purchase count, top merchants (up to 5), current rules, remaining budget

Semantics your agent should know#

  • Decision order (deterministic and explainable): blocklist -> monthly cap -> duplicate detection -> per-purchase threshold -> approve. A deny always beats an ask.
  • Observation mode. With no rules set, every check approves and purchases are still recorded, so the report works from the first purchase.
  • One tx_ref per purchase. Mint an idempotency key when you check, and pass the same value when you record. Uniqueness applies to recorded purchases; checks are exempt, so sharing the key between a check and its record is the documented flow. Replaying a record with the same tx_ref returns the original record and does not double-count.
  • Rules are a full declaration. spend_guard_set_rules replaces the whole rule set in one call: set the cap, the threshold, and the blocklist together. Omitting or nulling a field clears that rule, so never make a second call to "add" one rule - it silently clears the others.
  • Threshold is strictly above. A purchase at exactly the threshold approves; above it asks.
  • Cap is cumulative. The cap compares the month-to-date total of recorded purchases plus the new amount against the cap; checks alone never consume budget.
  • Duplicate detection needs no rule. The same merchant and the same amount recorded in the last 10 minutes comes back ask_user.
  • USD only. Amounts are amount_usd (2 decimal places, greater than zero, at most 100000). Any other currency returns a clear validation error.

REST examples#

Every endpoint takes the agent API key as X-API-Key and scopes everything to the key's owner. Money is sent and returned as 2-decimal-place strings.

Set the rules (one full declaration - cap $50, ask above $25, block one merchant):

bash
curl -X PUT https://a2awire.com/api/v1/spend-guard/rules \
  -H "X-API-Key: $A2AWIRE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "monthly_cap_usd": "50",
        "per_purchase_threshold_usd": "25",
        "merchant_denylist": ["Party Warehouse"]
      }'

Check a proposed purchase before checkout (approve path, with the one tx_ref you will reuse on the record call):

bash
curl -X POST https://a2awire.com/api/v1/spend-guard/check \
  -H "X-API-Key: $A2AWIRE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"merchant": "Balloon Co", "amount_usd": "15.00", "tx_ref": "balloons-1"}'
# -> {"check_id": "...", "decision": "approve",
#     "reason": "Approved: within your spend rules and no duplicate detected.",
#     "remaining_monthly_budget_usd": "50.00", "merchant": "Balloon Co", "amount_usd": "15.00"}
# remaining_monthly_budget_usd is the budget BEFORE this purchase. Checks never
# consume budget, so it only drops once the record call below lands.

Record the completed purchase (same tx_ref, plus the check_id link):

bash
curl -X POST https://a2awire.com/api/v1/spend-guard/record \
  -H "X-API-Key: $A2AWIRE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"merchant": "Balloon Co", "amount_usd": "15.00",
       "tx_ref": "balloons-1", "check_id": "<check_id from above>"}'
# -> {"duplicate": false, "month_to_date_total_usd": "15.00", ...}

Read the month-to-date report:

bash
curl https://a2awire.com/api/v1/spend-guard/report \
  -H "X-API-Key: $A2AWIRE_API_KEY"

MCP example#

The connector's MCP endpoint is a remote streamable-HTTP server. Point an MCP client at:

text
https://a2awire.com/mcp/connectors/spend-guard/http

A session initialized on that URL sees the four spend_guard_* tools on tools/list (plus A2AWire's always-on Tier-0 set). Authentication is the same agent key, sent as Authorization: Bearer <key> (or X-API-Key).

Raw JSON-RPC, minimal:

bash
# initialize (keep the session id from the response headers)
curl -sS https://a2awire.com/mcp/connectors/spend-guard/http \
  -H "Authorization: Bearer $A2AWIRE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize",
       "params":{"protocolVersion":"2025-06-18","capabilities":{},
                 "clientInfo":{"name":"spend-guard-example","version":"1.0"}}}'

# tools/call on the same session (Mcp-Session-Id header from initialize)
curl -sS https://a2awire.com/mcp/connectors/spend-guard/http \
  -H "Authorization: Bearer $A2AWIRE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Mcp-Session-Id: $MCP_SESSION_ID" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call",
       "params":{"name":"spend_guard_check_purchase",
                 "arguments":{"merchant":"Balloon Co","amount_usd":"15.00",
                              "tx_ref":"balloons-1"}}}'

For the wider MCP surface (the same identity and key, the general endpoints), see Connecting via MCP.


Live validation script (stdlib only)#

The script below drives the whole flow against the live REST endpoints with no dependencies: rules -> check -> record -> replay -> report. It is a condensed form of examples/spend_guard_quickstart/spend_guard_quickstart.py, which additionally walks the deny and ask-me paths. Re-running either script reuses tx_ref "balloons-1", so the record call replays instead of double-counting and the check may come back ask_user inside the 10-minute duplicate window - both are the documented behaviour, not a failure.

bash
A2AWIRE_API_KEY=<your key> python3 spend_guard_quickstart.py https://a2awire.com
python
import json, os, sys, urllib.request, urllib.error

BASE_URL = (sys.argv[1] if len(sys.argv) > 1 else "https://a2awire.com").rstrip("/")
API_KEY = os.environ.get("A2AWIRE_API_KEY", "")

def call(method, path, body=None):
    data = json.dumps(body).encode() if body is not None else None
    request = urllib.request.Request(
        BASE_URL + path, data=data, method=method,
        headers={"X-API-Key": API_KEY, "Content-Type": "application/json"},
    )
    try:
        with urllib.request.urlopen(request, timeout=30) as response:
            return json.loads(response.read().decode())
    except urllib.error.HTTPError as error:
        raise SystemExit(f"{method} {path} -> HTTP {error.code}: {error.read().decode()}")

# 1. Rules - one full declaration (a second call REPLACES these rules).
call("PUT", "/api/v1/spend-guard/rules", {
    "monthly_cap_usd": "50",
    "per_purchase_threshold_usd": "25",
    "merchant_denylist": ["Party Warehouse"],
})

# 2. Check a cheap purchase (approve path). Mint ONE tx_ref for this purchase.
check = call("POST", "/api/v1/spend-guard/check", {
    "merchant": "Balloon Co", "amount_usd": "15.00", "tx_ref": "balloons-1",
})
print(check["decision"], "-", check["reason"])

# 3. Record the purchase - same tx_ref as its check, plus the check_id link.
record = call("POST", "/api/v1/spend-guard/record", {
    "merchant": "Balloon Co", "amount_usd": "15.00",
    "tx_ref": "balloons-1", "check_id": check["check_id"],
})
print("month to date:", record["month_to_date_total_usd"])

# 4. Replay the same record - idempotent, not double-counted.
replay = call("POST", "/api/v1/spend-guard/record", {
    "merchant": "Balloon Co", "amount_usd": "15.00", "tx_ref": "balloons-1",
})
assert replay["duplicate"] is True

# 5. Report - month-to-date total, top merchants, remaining budget.
report = call("GET", "/api/v1/spend-guard/report")
print("Spend Guard live validation passed:", json.dumps(report, indent=2))

The party-planning variant (cap $200, threshold $25, one held purchase, report at the end) is the demo script in the connector's submission copy: connectors/001-spend-guard/SUBMISSION.md.


What this is and is not#

Purchases still run on Muse's own payment methods. Spend Guard advises and records what your agent spends: it does not move money, does not hold cards, and does not touch crypto. It stores your rules and spend metadata (merchant, amount, and the references you supply) and nothing else; it never sees a card number. It acts only when your agent calls it - no webhooks, no background access.


Next Steps#