acr-mcp
v0.3.3
Published
ACR Machine TCA over MCP — check whether your agent can pay, benchmark a machine purchase, grade a seller, verify a print.
Maintainers
Readme
ACR Machine TCA, over MCP
Eleven tools that let an MCP host (Claude Desktop, Claude Code, anything speaking stdio MCP) ask whether a bill should be paid, whether your agent has been overpaying, what compute actually cost a wallet, which seller to route to, what the tape says — and whether this agent can pay for a metered query at all.
npx -y acr-mcp # no clone, no path to editIf that 404s, the package is not on npm yet and the clone below is the way
in — it is two commands and it is verified, not merely written down (a fresh
checkout builds in about four seconds and its 114 tests pass). Nothing else
differs: the same dist/server.js, the same env block, the same eleven tools.
dist/ is gitignored, so a fresh clone has no binary until it builds one:
git clone https://github.com/kaustubh76/ACR---Arc-Compute-Rate.git
cd ACR---Arc-Compute-Rate/mcp && npm ci && npm run buildThen point the host at the built file — dist/server.js, never src/server.ts,
and an absolute path:
{ "command": "node", "args": ["/abs/path/to/ACR---Arc-Compute-Rate/mcp/dist/server.js"] }{
"mcpServers": {
"acr-tca": {
"command": "npx",
"args": ["-y", "acr-mcp"],
"env": {
"ACR_API": "https://acr-api-1fto.onrender.com",
"ACR_AGENT_PRIVATE_KEY": "0x<any 32-byte key: the card, not a wallet>"
}
}
}
}That is the whole setup. Ten of the eleven tools are reads, so you can paste
this with no wallet anywhere near it. ACR_ARC_CHAIN_ID is deliberately absent —
the card takes its chain from whichever gate ACR_API names, so pointing this at
a different press needs nothing else changed. See The 401 this used to be below.
Why that host — and which press to point at
All eleven tools answer on both of these. That was not true a day ago, and
the table is the shortest way to say what changed. Measured with npm run smoke
against each press, 2026-10-10 and again 2026-10-11:
| | acr-api-1fto (Arc testnet) | acr-probe-free (Arc) — the default | acr-api-mainnet (Arc) |
|---|---|---|---|
| tools answering | 12 of 12 probes | 11 of 11 probes | 5 of 11 did not |
| subgraph transport | studio-dev, cap 3000 | gateway, no cap | studio-dev, cap 3000 |
| its own query ledger | 2436 queries, 0 errors | 0 errors | 10 queries, 10 errors |
| check_spend, benchmark_price | price a real bill | price a real bill | no /par route to ask |
| get_rate print age | ~25 days | ~1.6 hours | ~1.6 hours |
| routes served | 50 | 50 | 44 |
The old default was acr-api-mainnet, and the reason it had to move is in one
cell above: ten queries, ten errors. That is not a slow day. The Graph
Studio's development URL is capped at 3,000 queries a day and counted on no
dashboard, so a press that exceeds it fails every subgraph-backed read for the
rest of the day — which is precisely why five tools used to answer "the
subgraph did not answer". The fix was to publish the subgraph to the network
and query it through the gateway with an API key; acr-probe-free runs that,
daily_cap reads null, and the cap stops applying rather than being silenced.
So: the default needs nothing set, and the quickstart above still names the
testnet press on purpose — its tape is years deeper, which is what you want
when you are trying tools out. Point ACR_API wherever you like; the card takes
its chain from whichever gate it names, so nothing else changes.
One honest caveat about the default. It is on Render's free plan, which
sleeps after ~15 idle minutes, and a cold start costs tens of seconds. It sets
ACR_SELF_URL so its own 60-second loop knocks on its public URL, and Render
counts that as traffic. Measured rather than assumed: after 17.5 minutes with
zero external requests its process_started_at was unchanged to the second
and /health answered in 1.34s — a restart would have moved that field. If you
ever do meet a cold start, the symptom is one slow first call, not an error.
| tool | what it answers | spends? |
|---|---|---|
| check_spend | should you pay this bill — the spend agent's own ten-rung ladder over any vendor's invoice, with the published prices it was judged against. No account, no key, no history with ACR | no |
| can_i_pay | can this agent pay for a metered query, and if not, which of seven rungs is in the way | no |
| pay_and_read | buys one metered query for real and returns the data plus the settlement reference | yes |
| payment_receipts | did the payment land — the settlement tape and revenue counter, narrowed to your payer | no |
| spend_report | the running total of every bill this machine has had checked: how many were over the going rate, by how much in bp, the worst vendor, and what was actually recoverable in USDC. The answer to "is my agent overpaying" for an agent that pays its own vendors | no |
| wallet_tca | a wallet's transaction-cost analysis if it bought from ACR: what it paid vs the benchmark, slippage, overpaid. A wallet ACR has never seen comes back seen: false, not zero. "me" answers the human-proof challenge and returns ONE figure across every wallet the person owns (ACR_HUMAN_AGENT_KEY) | no |
| reroute_suggestion | the seller this payer should have bought from, and the saving | no |
| seller_rating | one seller's rating, components and human depth | no |
| benchmark_price | one quote priced against what the market is actually paying | no |
| get_rate | the on-chain print for an index | no |
| query_tape | any named subgraph operation through the seller's read proxy | no |
Demo: an agent's spend, end to end
One command runs the conversation an operator would have with an agent that buys inference, through the real server over stdio exactly as a host speaks to it — and every step is also an assertion, so the demo fails (exit 1) the moment it stops demonstrating something:
npm run demo # tokens the agent bought — spends nothing
ACR_DEMO_PAY=1 ACR_PAYER_PRIVATE_KEY=0x… npm run demo # + USDC the agent spent: one $0.0001 query on Arc testnetPart 1, as it printed against the default press on 2026-10-13 (checks trimmed):
you › "My agent was billed 0.20 USDC for 1M tokens of inference. Should it pay?"
tool › check_spend {"billed_usdc":0.2,"quantity":1000,"unit":"$/1k tokens"}
acr › you paid 0.000200 USDC per 1k tokens; the going rate is 0.000400 across 5 published prices
acr › cheapest: GPT-4o mini (openai/gpt-4o-mini) at 0.000262 per 1k
acr › verdict under_par (-5,000 bp) → the spend agent would PAY
you › "Then it was billed 0.47 USDC for 23k tokens by its inference provider. Should it pay that?"
tool › check_spend {"billed_usdc":0.47,"quantity":23,"unit":"$/1k tokens"}
acr › you paid 0.0204 USDC per 1k tokens; the going rate is 0.000400 across 5 published prices
acr › verdict over_par (+500,870 bp) → the spend agent would ESCALATE
acr › 0.463963 USDC of this bill was avoidable at the cheapest published price
you › "Has my agent been overpaying for tokens?"
tool › spend_report {"days":1}
acr › 2 bills checked · 1 over the going rate · worst +500,870 bp
acr › 0.463963 USDC could have been paid insteadWhat it asserts: each bill is priced against the open market with at least two
cited quotes and a current basket; the fair bill is paid and the expensive one is
not waved through; and the report's avoidable total equals the second bill's
saving to the micro-USDC. It runs against its own throwaway spend log, so your
~/.acr/spend.jsonl is never touched.
Part 2 makes one real testnet payment and reads it back: can_i_pay green on all
seven rungs, pay_and_read settles $0.0001 (settlement 53cb9387…), and
payment_receipts finds that settlement on the press's tape. It also compares two
views of the same wallet, and that comparison is where it found something: on the
testnet press 36 receipts are held but the chain shows 1 purchase — receipts
there are not reaching the on-chain mirror, so wallet_tca under-reports what that
wallet spent. That is printed as a measured gap (◌), not a pass and not a failure,
because the fix is an operator one.
The example this replaced. /developers used to teach "billed 0.47 USDC for 23k
tokens by 0xefe0…". That address is ACR's own seller, and the press prices a seller
ACR operates only against ACR's fleet, never the market — so the answer was "pay,
unbenchmarked" on mainnet and "pay, −9,609 bp" on testnet, while the same bill with no
vendor is the +500,870 bp escalation above. check_spend now adds a hint whenever a
bill was judged against the fleet, naming the call that prices it against the market;
the demo's last step asks exactly that question and checks the hint is there.
Should I pay this bill?
check_spend is the one tool that works on a bill ACR has never seen. Give it the
invoice as the invoice is written — the amount billed, how much you bought, and
the unit — and it runs ACR's benchmark and the spend agent's own decision ladder
over it, returning the verdict that agent would reach and the rule that produced it.
check_spend(billed_usdc: 0.47, quantity: 23, unit: "$/1k tokens",
vendor: "0xefe0…dF19") # vendor optionalThree things worth knowing:
The bill goes up as billed. The press divides by the quantity itself, so you are never asked to compute the per-unit price the tool exists to check.
quantityis counted in the unit: 23 for 23,000 tokens at$/1k tokens, not 23000.vendorchanges the answer, so it is not a label. A seller ACR operates is judged against ACR's own fleet prices; anyone else against the open market. A vendor is always excluded from its own comparison set, which can collapse the benchmark to "no independent seller" — an answer, not a failure.And it can flip the verdict, which is worth seeing before it surprises you. Measured against a local press:
0.47for 23 at$/1k tokenswith no vendor isescalate, "500870 bp above the going market rate of 0.0004 across 5 published prices". The same bill naming a fleet seller ispay— because excluding that seller leaves the fleet population with nobody to compare against, so the bill becomes unbenchmarked, and an unbenchmarked bill under the 1 USDC ceiling passes. The verdict says so in its own words ("unbenchmarked but under the ceiling") andbenchmarked_againstflipsmarket→fleet, which is why this tool returns the press's payload untouched rather than reducing it to a verdict. A one-word answer would have hidden the reason the word changed.Three units, and only three —
$/1k tokens,$/GPU-sec,$/MB. The press 422s anything else and names the list back in its refusal. An earlier version of this plugin advertised$/GPU-hourand$/GB-month; neither exists, so two of the three units it offered were an instant 422.
What it does not check is named rather than implied: there is no meter, no counterparty screen, no agreement, no budget and no balance for a caller who has onboarded nothing, and the verdict leaves a note for each.
Is my agent overpaying?
check_spend answers one bill. spend_report answers the question behind it.
Every bill check_spend prices is appended to a file on your machine, and
spend_report folds them into a running view: how many were over the going
rate, by how much, which vendor is worst, and what was actually recoverable.
spend_report(days: 30) # or vendor: "0x…", to narrow it{
"bills_checked": 41, "priced": 38,
"over_rate": { "n": 11, "median_bp": 380, "worst_bp": 4120 },
"actionable": { "n": 6, "could_have_paid_usdc": 1.84,
"basis": "the cheapest reachable independent offer at the time of each check" },
"by_vendor": [ { "vendor": "0x…", "bills": 9, "over_rate_bp": 410.2 } ],
"note": "these are bills you asked ACR to price. ACR did not observe your payments."
}Why one number is in basis points and the other in dollars. par is the
median of independent observed quotes, so "above the going rate" is a statement
about one price in a market — real, and not money you can go and collect.
best is the cheapest offer somebody actually made, so the difference from it
is money that was available. Only the second becomes USDC. ACR's own index
cannot price a real invoice at all: anchors/GAP.md puts its reference levels
20x to 1250x off real market prices, which is why none of this is measured
against it.
And this is a record of checks, not of payments. ACR never sees whether you
paid any of these bills, at what price, or at all. wallet_tca is the other
half — it measures settlements ACR actually observed — and it only works for a
wallet that bought from ACR's own sellers.
Privacy
check_spend sends each bill to the press. That is how it gets priced: the
amount, the quantity, the unit and the vendor address go to whatever ACR_API
names. That was true before spend_report existed and it is worth saying
plainly — if your invoice amounts are sensitive, this is the tool that transmits
them.
spend_report sends nothing. It reads a plain JSONL file on your machine
and does the arithmetic locally. ACR does not need a copy of your vendor ledger
to tell you what a fair price is, so it does not get one.
| | |
|---|---|
| where | ACR_SPEND_LOG, default ~/.acr/spend.jsonl. The home directory, not the working directory, because an MCP server inherits its host's |
| what | one line per priced bill: vendor, unit, quantity, amount, and the press's verdict. No keys, no cards, no addresses you did not supply |
| who can read it | mode 0600, created 0700. Plain JSONL: read it, grep it, rm it |
| turning it off | ACR_SPEND_LOG=off. Then check_spend answers recorded_to: null and spend_report says there is nothing to report |
There is deliberately no tool that deletes it. A tool that erases a
business's bill history is a tool a model will eventually try; rm is the
interface.
Can my agent pay?
This is the question the plugin could not answer for its first version. The six original tools are analytics over payments that happened somewhere else: none of them touched a payment-gated endpoint, so a developer who wired the server up never even saw a 402, and the only way to find out was to leave the agent and start curling.
can_i_pay answers it as a ladder, because "no" is the useless answer. There are
seven independent reasons a payment cannot happen, they fail in a fixed order, and
the one that fired is the only thing you need:
| rung | what it checks |
|---|---|
| host | the press answers, and says which chain it is on |
| chain | that chain has a profile here, so USDC and the Gateway wallet can be located |
| card | the gate accepts your agent card — a card it refuses 401s every other tool |
| gate | the endpoint really is behind the paywall, per the gate's own gated_endpoints |
| challenge | the 402 comes back and parses: scheme, network, asset, payTo, amount |
| payer | a payer key is configured — the one thing no server can supply for you |
| funds | the money is in the Circle Gateway balance a settlement spends from |
It never answers the 402 it asks for, so nothing is spent and no key is needed to run it. With no payer key it still reports the other six rungs, which is the useful read-only state: the gate is fine, you are not configured.
That last rung is the one that surprises people. An x402 settlement on Arc spends
the payer's Gateway deposit, not the USDC in its wallet — the client signs an
EIP-3009 authorization against the GatewayWallet. A wallet holding USDC with an
empty Gateway balance cannot pay; it has to deposit first, and can_i_pay says
deposit rather than ready.
Paying for real
pay_and_read is the only tool that moves money, and it is not registered at
all unless ACR_PAYER_PRIVATE_KEY is set — a tool a host can see is a tool a
model will try.
"env": {
"ACR_API": "https://acr-api-1fto.onrender.com",
"ACR_PAYER_PRIVATE_KEY": "0x<a wallet YOU control, funded into Circle Gateway>",
"ACR_MAX_SPEND_USDC": "0.01"
}Same press as the quickstart above, which also means testnet USDC — the
faucet kind. Point it at the mainnet press and the identical call spends real
money; the chain comes from whichever gate ACR_API names, and can_i_pay
prints which one before anything is signed.
A paid query is $0.0001, which is exactly the amount that makes a looping agent
expensive without ever looking alarming. So there is a per-process cap,
ACR_MAX_SPEND_USDC, defaulting to one cent; pay_and_read prices each call from
the live 402 before authorizing anything and refuses past the cap, naming it.
Set it to 0 to keep the tool registered but inert.
Use a key you control. Never a shared or house key — ACR_PAYER_PRIVATE_KEY is a
separate variable from the repo's ACR_BUYER_PRIVATE_KEY precisely so the two
cannot be picked up by accident.
It is a carded caller
Every tool's fetch is wrapped by src/card.ts, the same EIP-712 encoder the buyer
agent uses. With ACR_AGENT_PRIVATE_KEY set, every call carries an AGENT-CARD
header, and the seller answers from the carded tier: a rate-limit budget keyed on
your key rather than the host ceiling every anonymous reader behind a proxy shares,
and — on query_tape — Google Cloud Model Armor screening the request and the reply.
Unset, the server is anonymous, which is a working state, not an error.
The 401 this used to be
The chain id lives inside the card's EIP-712 domain, so it is part of the
signature: sign for one chain, present it to a gate expecting another, and the gate
recovers a different address and answers
401 "agent card signature does not match its agent".
DEFAULT_API named the mainnet press while the chain id defaulted to Arc testnet,
and nothing — not this README, not the snippet on /developers — set the variable
that reconciled them. Measured 2026-10-08 against the config this file used to
print: four of five tools 401, the fifth failed downstream of it. All 16 tests
passed, because they fake fetch, and a fake gate cannot refuse a card.
So the chain id is not defaulted any more. GET /agent/challenge answers an
uncarded caller with its own chain_id, and that is what the card is signed for.
ACR_ARC_CHAIN_ID still overrides, for a fork or a local gate. And when the chain
cannot be established at all, the call goes out anonymous rather than carrying a
card the gate will refuse — a lower rate-limit bucket beats a 401 on every tool.
And the override no longer wins silently, which was the same bug wearing the other hat. It used to short-circuit before the gate was ever asked, so pointing it at the wrong chain reproduced the original failure exactly. Measured 2026-10-10 against both live presses:
testnet gate (wants 5042002) card signed 5042002 -> 200 tier=carded
testnet gate (wants 5042002) card signed 5042 -> 401
mainnet gate (wants 5042) card signed 5042002 -> 401
mainnet gate (wants 5042) card signed 5042 -> 200 tier=cardedThat was reachable from this repo's own .env, which pins
ACR_ARC_CHAIN_ID=5042002 while DEFAULT_API is the mainnet press — so any shell
that exports it and sets no ACR_API would 401 on every carded tool with no hint
why. (mcp/ loads no dotenv of its own, which is the only reason it had not bitten
yet.) So the gate is asked anyway, and a disagreement means no card rather than
a doomed one. An unreachable gate still honours the override: a sleeping free-tier
press must not quietly strip a correctly-set card.
| env | meaning |
|---|---|
| ACR_API | the seller to call. The default is https://acr-probe-free.onrender.com, an Arc mainnet press — the product's chain — on which all eleven tools answer. The quickstart above still names the testnet press explicitly, because its tape is deeper; see Why that host for what each one is good for. Run it bare and you get the mainnet default, whole |
| ACR_AGENT_PRIVATE_KEY | signs the card. Any 32-byte key; nothing is enrolled, nothing is spent |
| ACR_PAYER_PRIVATE_KEY | spends. The wallet pay_and_read settles from. Unset → that tool is not registered, and can_i_pay reports the payer rung as the blocker |
| ACR_MAX_SPEND_USDC | per-process spend ceiling (default 0.01). 0 means refuse every payment |
| ACR_ARC_RPC_URL | the RPC can_i_pay reads balances from (default: the chain's public one) |
| ACR_AGENT_HUMAN_CLUSTER | opt-in human claim: the cluster HumanIdMirror.clusterOf records for this key's wallet in the current 7-day window. A claim the chain cannot confirm is a 401, never a silent downgrade, so leave it unset unless you have resolved that wallet |
| ACR_ARC_CHAIN_ID | overrides the card's domain chain. Leave it unset unless you know you need it; the gate is asked instead. Set it to a chain the gate disagrees with and the plugin presents no card at all and says so once on stderr — see The 401 this used to be |
| ACR_SPEND_LOG | where check_spend records each priced bill so spend_report can total them. Default ~/.acr/spend.jsonl; off disables recording entirely. Nothing here is uploaded — see Privacy |
| ACR_ARC_PRIVATE_MAINNET | inert — it changes nothing. Kept documented so nobody sets it expecting an effect. It used to be passed to Circle's client as arcPrivateMainnet; GatewayClientConfig in the installed @circle-fin/x402-batching 3.5.0 is {chain, privateKey, rpcUrl?, headers?} and the string appears nowhere in that package's shipped code, so it was dropped on the floor at runtime (it compiled because a spread skips excess-property checks). It was unreachable anyway: both chain profiles are privateMainnet: false since the private-mainnet preview ended. Verified 2026-10-10 |
| ACR_HUMAN_AGENT_KEY | lets wallet_tca("me") answer the AgentKit gate (production): the key of a wallet registered in AgentBook. The plugin signs each challenge (CAIP-122, EIP-191) in-process; the key never leaves it. On a fixture-backed press the roster is the demo one and the key is derivable — the testnet section above has the one line |
| ACR_HUMAN_NULLIFIER | the same, for a local dev gate (ACR_HUMANID_MODE=dev): a bare nullifier. Not a spending key; anyone holding it can read that human's costs |
Running it against Arc testnet
Nothing here is testnet-aware by default: ACR_API falls back to the mainnet
press, so the only thing that puts this plugin on testnet is setting it. The
snippet on /developers cannot show you this — the terminal has a whole-project
gate forbidding the other network's values in its shipping UI
(apps/terminal/lib/mainnetOnly.test.ts, eight recorded incidents) — so the
recipe lives here.
"env": {
"ACR_API": "https://acr-api-1fto.onrender.com",
"ACR_AGENT_PRIVATE_KEY": "0x<any 32 bytes>",
"ACR_PAYER_PRIVATE_KEY": "0x<a wallet with an open Gateway deposit on Arc testnet>",
"ACR_MAX_SPEND_USDC": "0.001",
"ACR_HUMAN_AGENT_KEY": "0x<a fixture wallet's key — see below>"
}Deliberately absent: ACR_ARC_CHAIN_ID (that gate answers 5042002 for itself,
and pinning the wrong one costs you the card — see above), ACR_ARC_RPC_URL (the
profile's https://rpc.testnet.arc.io answers), and ACR_AGENT_HUMAN_CLUSTER
(testnet reports human_binding_verifiable: true, so an unresolved claim is a
hard 401 on every tool, not a downgrade).
Measured against that press on 2026-10-10, with that config:
| | |
|---|---|
| card | /agent/whoami → 200, tier carded |
| can_i_pay | all seven rungs pass — host, chain, card, gate, challenge, payer, funds |
| pay_and_read("/prints") | HTTP 200, $0.0001, eip155:5042002, settlement 6a9e4799-…, which then appeared on payment_receipts as seq 113 |
| every tool | 11 of 11 answered, and wallet_tca went from seen: false to 21 purchases / $0.105752 once that payment was on the tape |
| wallet_tca("me") | the AgentKit proof accepted — /humanid/info verified_proofs 0 → 1 — returning the human's cluster and window |
wallet_tca("me"), the one tool that needs a person
"me" unions every wallet one human owns, so it needs a human proof rather than
an address. That press verifies AgentKit proofs and its AgentBook is the
fixture roster (/humanid/info → agentbook: fixture), whose wallets are
derived from public labels — two humans, four wallets, acr-buyer-1..3 for one
person and acr-buyer-4 for another:
ACR_HUMAN_AGENT_KEY=0x$(python3 -c 'import hashlib; print(hashlib.sha256(b"acr-buyer::acr-buyer-1").hexdigest())')
# -> 0x674055533B05Ec3fD135fC21c4d91a4A2D3193d3, which is the testnet tape's main payerThese are not people, and that is the property that makes printing the
recipe safe: every one is flagged sandbox all the way onto the chain
(HumanIdMirror.clusterProvenance) and into the tape (HumanCluster.sandbox),
and none holds anything worth keeping. packages/acr_oracle_client/.../demo_humans.py
is the source. Do not expect this to work against a press whose /humanid/info
reports a real AgentBook — there the key has to be a wallet an actual person
registered.
Two windows, and they are not the same number. An address takes a 30-day
default; "me" takes the press's human rotation window (about a week),
because a person's wallet set is only resolved per window. Asking "me" for 30
days is refused — by this plugin, before it spends a nonce, naming the window to
ask for. Measured 2026-10-10: the proof is accepted (verified_proofs 0 → 1 the
first time it ran) and the answer then reports the human's cluster and
wallet_count. A wallet_count of 0 means nobody has run the weekly
resolve_humans for the current window — an operator chore, not a fault in the
plugin, and npm run smoke says so in those words.
Two things are true of this press and worth knowing before you trust a number from it:
- Its oracle has not posted since 2026-09-15. All three indices answer with
prints 25 days old, and
/healthshows why:signer: "circle"withposter_last_tx: nullwhile the keeper fires on schedule — the chore runs and nothing lands.get_ratenow reports that age rather than handing you the number bare./onchainalso flickers between a value and a 404 within minutes, because Arc answerseth_getLogswith a 429 under load; an absence there is not always permanent. - The carded tier works here and does not on mainnet.
query_tapeis screened by Model Armor, which fails closed for carded callers; the mainnet press reportscredentials_present: false, so carding it breaks that one tool (handled, withhost_side: true, and pinned bypaying.test.ts). Testnet is the press where the full carded path is exercisable.
The card names no verifyingContract on purpose — the seller, not a contract,
verifies it — so audience (acr-index-api, read from GET /agent/challenge) and
a 15-minute lifetime stand in for one. GET /agent/whoami with the header tells you
which tier you landed on and why, and can_i_pay's card rung reports exactly that.
Two things to know about the answers
benchmark_price wants a unit. With one, it goes through ACR's /par
benchmark and returns the verdict the spend agent itself would reach. Without one,
all it can do is compare against the index print — and an index level is not a
dollar price per unit. Asking it to price $2.50 per GPU-hour against a level of
0.011 used to return a confident slippage_bp: 2257157.1. It now refuses that
comparison and says why.
The default window is 30 days. It was 7, and at 7 every payer and seller on
both presses reported zero rows — including the addresses /developers printed as
its worked examples. An empty window now carries a hint saying it is a window
rather than a verdict.
Developing
npm ci && npm test # 61 hermetic tests, no network, no secrets — what CI runs
npm run typecheck
npm run build # tsc → dist/, which is what `npx acr-mcp` runs
npm run smoke # every tool against a REAL press (needs network)npm run smoke is the one that matters before publishing. The unit suite fakes
fetch, which is the right shape for a hermetic CI job and is why the 401 above
survived 16 green tests — a fake gate cannot refuse a card. The smoke probe reports
the gate's own chain id and the tier it actually granted, so it cannot pass
identically against a press it was never pointed at.
Full design: docs/AGENT-MODULE.md.
