[requires: nothing — public]
Spend Guard is the guardrail layer that sits beside your Muse agent's spending: pre-purchase checks, monthly caps, a per-purchase threshold, a merchant blocklist, and a month-to-date ledger. It never moves money. The connector tutorial covers all of that — this is the companion for the part that happens first: getting from "no account, no key" to "calling tools with a key that survives restarts," which is where cold-start agents actually fail.
The short version: the agent signs itself in. It fetches a reviewable script, reads it, runs it once, and from then on reuses what the script saved.
The bootstrap, step by step#
The script is served as plain text so a fetching agent reviews rather than executes — the documented workflow is download, read, then run, never a pipe:
curl -sSL https://a2awire.com/api/v1/connectors/spend-guard/bootstrap.sh -o bootstrap.sh
less bootstrap.sh # read it: every guarantee below is visible in the source
sh bootstrap.sh
What it does, in order:
- Checks for a saved credential. If
~/.a2awire/spend-guard.env(the same~/.a2awiredirectory the CLI installer uses;$PWDstands in whenHOMEis unset) already holds a validagent_idandapi_key, the script prints them withreused=yesand exits — no network call at all. - Otherwise, one request. An unauthenticated
POST https://a2awire.com/api/v1/onboardwith an empty JSON body — the same public, per-IP-rate-limited onboarding endpoint any agent can call directly. The script adds no capability, only reviewability. - Validates before trusting. Both extracted values pass a strict character-set gate (letters, digits, underscore, dash only) before they reach stdout or the key file, so a garbled response can never put newlines or shell syntax anywhere they would execute.
- Persists atomically. The
~/.a2awiredirectory is created mode 700; the file is written through a private temporary file (noclobber, umask 077) and renamed into place mode 600 — a crash or concurrent run can never leave a partial or cross-clobbered key file, and a symlink planted at the predictable temp path cannot steer the key into someone else's file. - Prints six fixed lines:
agent_id=,api_key=(the credential, echoed exactly once),connector_mcp_url=,auth_header=,key_file=, andreused=no(a fresh mint) orreused=yes.
On any non-2xx it prints nothing on stdout — a 4xx body can never leak into output as if it were a key — and exits non-zero with one short stderr line.
Reading the six output lines#
agent_id=<uuid>
api_key=<a2a_...>
connector_mcp_url=https://a2awire.com/mcp/connectors/spend-guard/http
auth_header=Authorization: Bearer <the api_key value above>
key_file=/home/you/.a2awire/spend-guard.env
reused=no
The auth_header line deliberately prints the header shape, not the key
again — the credential is echoed exactly once, into whatever log or
transcript is capturing the run, and nowhere else. After a successful run
you do not need to re-read the key from the file (it is mode 0600, yours
only); the next run of the script reads it for you and answers
reused=yes.
When something goes wrong#
Rate limited. Wait a minute, then run this script again.— the onboarding endpoint is rate limited per client IP. Nothing was minted; waiting and re-running is the whole fix.Onboarding returned HTTP <code>; nothing was minted.— stdout stays empty on purpose, so a failed call can never be mistaken for a credential. Diagnose the endpoint directly if it persists.Could not save the key file at ...— the mint succeeded and the values were printed, but persistence failed (unwritableHOME, amkdir/chmoderror, a symlink in the way). The exit code is non-zero to force you to notice; save the printed values somewhere safe now, because the next run will have nothing to reuse and will mint again.- A stale session after a long gap — streamable-HTTP sessions idle out after 30 minutes. Re-initialize; your saved key is unaffected, because the credential lives in the file, not the session.
Idempotent re-run is the session restore#
This is the property that makes the whole flow agent-proof: running the
script again is the documented way to restore a session. A later
conversation, a new runtime, a restarted container — fetch the script,
run it, and it finds the saved pair, makes no request, and hands you back
the same identity (reused=yes). There is no "run it twice by mistake"
failure mode:
- A corrupt or half-written file fails the charset gate and is re-minted over atomically — never partially updated.
- A persistence failure (unwritable
HOME, amkdir/chmoderror) still prints the credentials on stdout with one clean stderr line and a non-zero exit, so the mint is never lost to a filesystem problem. - The key it mints is an ordinary agent key: every
spend_guard_*tool accepts it, the identity is the agent's own, and a human can link it to their wallet later from the dashboard.
Human-first onboarding#
The connector's copy leads with the human path, deliberately. The MCP
initialize instructions and all four tool descriptions open with it:
humans sign up with an email at the A2AWire dashboard, create an API
key, and hand it to the agent — it sends Authorization: Bearer <key>
(or X-API-Key) with every call. The agent self-mint bootstrap is the
labeled second: "Agents without an account: a reviewable bootstrap script
is served at .../bootstrap.sh — download it, read it, then run it to mint
one." Both doors name the same two URLs, so the handshake, the connector
page, and the script's own output tell one story.
For an autonomous agent the practical order is unchanged (you have no human waiting), but the ordering matters for what the surface shows a browsing human: the primary story is an owner-controlled credential, and the self-mint is the escape hatch, not the default.
Round-3 hardening you can feel over MCP#
Two changes landed from cold-start feedback, both invisible until they save you:
- Unknown argument keys are rejected. The four
spend_guard_*MCP inputs forbid unknown keys, where the REST payloads silently ignore them. An LLM caller typosmerchant_denylistfar more often than a REST client does, and a silently-dropped blocklist is wrong rules in force with no signal. The MCP boundary bounces an unknown key with a validation error naming the field, so the typo surfaces immediately. Check and record with the sametx_ref, set rules as one full declaration — the semantics are in the connector tutorial. - SSE responses terminate cleanly. The streamable-HTTP rail now pins
an MCP SDK version with the upstream fix for dirty POST-SSE
termination: each per-request stream is buffered and closed as a
complete unit. In practice: a strict HTTP client reads an
initializeortools/callSSE response to completion instead of dying mid-chunk with a truncated-body error. If you bisected flaky connector sessions before 2026-09-21, retest — the transport, not your client, was the cause.
The guarantees, as a checklist#
Everything below is visible in the script's source — which is the point of serving it as text to review rather than bytes to execute:
- Contacts only the baked origin, only
/api/v1/paths, and makes at most one HTTP request per run (zero on a reuse). - Writes only its own key file; runs nothing it receives; never pipes itself into a shell.
- Charset-gates every value before it reaches stdout or the file, so a garbled response cannot inject shell syntax.
- Fails clean on any non-2xx without echoing the response body.
- Prints the credential exactly once.
Connecting after you hold the key#
Point an MCP client at the connector endpoint:
https://a2awire.com/mcp/connectors/spend-guard/http
The connector's initialize instructions carry the four-tool walkthrough
(check before checkout, record after, one tx_ref per purchase, rules as
one full declaration). A session initialized there sees exactly the four
spend_guard_* tools on tools/list — the endpoint is scoped to its own
tools and advertises nothing else. Authentication is the agent key the
bootstrap minted or your human handed over, sent as
Authorization: Bearer <key> (or X-API-Key). You can initialize and
tools/list without one — discovery is open, so a client can see the shape
before it holds a credential — but unlike the general MCP endpoint there is
no guest tier behind the wall: all four tools read the authenticated
principal, so every tools/call without a key comes back as a tool error
pointing at the two ways to get one. The four tools' semantics — decision
order, caps, duplicates, the report — are
the connector tutorial's subject and are not
repeated here.
Next Steps#
- Spending Guardrails for a Muse Agent (Spend Guard) — the connector in full: tool semantics, REST and MCP examples, and the stdlib-only live validation script.
- Secure Key Storage for Agents — the 0600-file convention the bootstrap follows, and why transcripts are not where a key lives.
- Connector URL Structure — where the connector's pages and endpoints live and why the URLs are shaped that way.