Repository navigation
Releases: openmobilehub/credentagent
Release list
v0.6.0 — an AI agent can spend a permission you signed on your phone
In plain terms
Until now, a person had to be at the checkout to approve each purchase. With 0.6.0, a person can sign a spending permission once on their phone, for example "up to $50 at this store". An AI agent then spends it with a key of its own, and the store checks each purchase against the limits in that permission.
This is the version the README has been describing. If you followed the README and got Package subpath './agent' is not defined by "exports", upgrading fixes it.
It also fixes a security bug present in 0.2.0 through 0.5.0. A crafted checkout link could run a script on the gate's consent pages. Upgrading is recommended even if you don't use the new features. See "Security fixes" below.
Demo trust only. This is not a real safety control.
- Nothing checks yet who issued the wallet's credential, so a credential someone made themselves would pass. The built-in pages also accept a typed-in claim with no wallet at all, and that can't be turned off yet (#352).
- For agent purchases, that means an agent can create its own "permission" with any limits it likes, and the permission still verifies. The store enforces the limits, but nothing proves a person set them.
- The cryptography on the wire is real. Results carry a label that says how far to trust them:
presence-only-demoon the wallet rails, anddevice-signedorserver-issued-demoon spending permissions. These labels aren't consistent yet (#267).- Signing on a real phone needs a wallet that understands AP2's
delegaterequest, and no released wallet does yet (see "An AI agent can spend…" below).- Use this to build and to demo.
Security fixes
- A crafted checkout link could run a script on the consent pages (#332, fixing #321 and #330). It affected the passkey, credential and phone-wallet payment pages, and the storefront's checkout page. The
cartvalue in a link is now encoded, and every value the pages write into a script goes through one escaping helper. This bug is in every published version from 0.2.0 to 0.5.0 (storefront 0.2.1 too). - An agent could lower an order's total by adding a line with a negative quantity, paying $12.50 for a $22 coffee (#346, fixing #286). A line with a negative quantity is new since 0.5.0, so no published version has that. The same fix refuses a fractional quantity (half a coffee at half price) in
grant.spend, which 0.3.0 to 0.5.0 accept if your code passes the agent's quantity through unchecked.
Breaking changes
0.6.0 renames some fields, changes the phone-signing format, and removes one entry point that never worked. You're affected if you use spending grants, because phone signing (signing: "device") is the default in both 0.5.0 and 0.6.0. You're also affected if you call the intent-sign building blocks directly, or import @openmobilehub/credentagent-storefront/firestore. Stores that only use the checkout gates are not, apart from the doctor() boot check described under Upgrading.
| 0.5.0 | 0.6.0 |
|---|---|
grant.mandate.credentialDoctype, GrantMandateEvidence.credentialDoctype |
credentialType (an SD-JWT VC type, not an mdoc doctype) |
IntentVerifyResult.credentialDoctype, IntentTrustVerdict.docType |
credentialType |
IntentVerifyBackend receives { deviceResponseB64url, sessionTranscript } |
receives { sdjwt, audience, nonce }, and must return delegatePayload (the key-binding JWT's delegate_payload, verbatim). The type marks it optional, but without it every verify fails with "mandate mismatch". |
buildIntentSignRequest(…), verifyIntentPresentation(…) |
also require merchantUrl (the store's public URL, path included), delegate, mandateExp and allowedSkus. Without merchantUrl, buildIntentSignRequest throws a TypeError and verifyIntentPresentation refuses. |
SimulateOptions.overrideDocType |
overrideVct |
The phone signs with the mdoc payment credential org.multipaz.payment.sca.1 (payment.mpzpass) |
The phone signs with an SD-JWT payment credential (vct urn:emvco:dpc:card:1 or com.emvco.dpc). A wallet set up for 0.5.0 can't sign a grant until it holds one; tools/demo-pki/mint/mint-dpc-sdjwt.mjs --mpzpass mints a demo one. No released wallet supports AP2's delegate request yet, so test with devSimulateWalletSignature. |
@openmobilehub/credentagent-storefront/firestore (firestoreCatalog), and firebase-admin as an optional peer |
Removed (#314). It was never built, so importing it from npm always failed. Pass a CatalogSource over your own database instead; the storefront README shows one. |
Upgrading
npm i @openmobilehub/credentagent-gate@^0.6.0 @openmobilehub/credentagent-storefront@^0.6.0- Change your version range by hand. Below 1.0, npm treats the middle number as breaking, so
^0.5.0won't pick up 0.6.0 on its own. - If you call
doctor()and stop the server when it isn't ok (the boot check the README shows), your deployment won't start until you passnew CredentAgent({ mandateSigningKey }), a stable private P-256 key from your secret manager. This applies even if you never use AP2. Only mandates your own code mints withcredentagent.ap2are signed with that key, and those are what would stop verifying after a restart. Whether this should stay an error is open in #361.doctor()also warns aboutcheckoutSigningKey: without it each instance generates its own, so an agent purchase whose cart was quoted before a restart, or by another instance, is refused. Local development is unaffected.
What's new
An AI agent can spend a permission the person signed (gate)
- A new entry point for the agent,
@openmobilehub/credentagent-gate/agent.AgentKeyholds the agent's key, andDelegatedIntentturns the signed permission into a purchase proof. It's kept apart from the package root, so when the agent holds its own key (the host passesagentKeywhen it creates the grant), that key never enters the store's process. WithoutagentKey, the gate generates and holds the delegate key itself. - The store checks that proof with
verifyDelegatedPurchase, imported from the package root. A purchase over the limits, paying someone other than the store, or at a price the catalog doesn't charge is refused with a typed reason. - The phone signs the permission with the wallet's own key. The permission is an AP2 mandate in SD-JWT format; AP2 is the Agent Payments Protocol, a standard shape for "this agent may spend this much".
- Signing on a real phone needs a wallet that understands AP2's
delegaterequest, and no released wallet does yet. For Multipaz it exists only in a patched build; the upstream change (openwallet-foundation/multipaz#2011) was closed without merging, and a released Multipaz wallet rejects the request. To try the whole flow without a phone, usedevSimulateWalletSignature(examples/device-signed-grants.mjs). - The store's own mandates are signed with its key.
credentagent.ap2mints them with the store'smandateSigningKey, and anyone holding the public key checks one withverifyMandate. A phone-signed permission is checked when the person signs it, and again at each purchase byverifyDelegatedPurchasewhen the agent holds its key. The gate publishes the public key so others can check them. A store at the site root publishes it at/.well-known/did.json; a store under a path publishes it at<walletOrigin>/did.json, where did:web looks for it (#347). - A permission names its store, path included. A permission for
shop.example/coffeeis refused atshop.example/wine(#334). - A normal checkout still records the older-style payment mandate, which is not key-signed. The passkey rail's carries a dev integrity hash (
MOCK-DEV-SIGNER), and the phone-wallet payment's carries none. - AP2 mandates and spending grants count money in whole cents, with helpers such as
toMinorUnits,toMajorUnitsandformatAmount. The checkout itself still adds dollar amounts as floats, which can refuse a valid payment (#360, in Known issues).
An outside verifier can check who someone is, with nothing to pay (gate)
- The
verifierseam now works for identity-only checks (#160), for example releasing a record only to someone who proves they're 21 or older. The approve page no longer talks about a payment that won't happen, follows yourreturnUrl, doesn't show "$0.00" totals, and reports "Credential checked" with the verifier's trust level rather than calling it verified.examples/delegated-verifier-identity/shows it end to end.
Seeing what the wallet sent (gate + storefront)
inspectPresentations: trueadds an "Inspect this presentation" link to the consent page. It opens what the wallet sent in Multipaz Tools, only in the browser that just presented it. The answer is never stored: not on the order, not in any status read, and not in the webhook (#327). It's off by default, because an answer from a real ID can hold personal data.- **Each completed order keeps a record of...
v0.5.0 — works with claude.ai's new MCP version, wallet-signed spending grants, and a cart per conversation
CredentAgent lets an AI agent act for someone — buy something, pass an age check — only after that person proves it with a credential from their phone wallet. This release makes the reference store work with the newest version of MCP (the protocol AI apps like claude.ai use to talk to tools), lets a phone wallet sign spending permissions, and fixes a shared-cart leak in multi-server deployments.
Upgrading from 0.4 — three changes to know about
- The storefront now uses the MCP SDK v2 packages (
@modelcontextprotocol/server,/node,/express, and/ext-apps2) instead of@modelcontextprotocol/sdk. If you run the store withcreateStorefront()andstore.listen(), there is nothing to do. If you connectstore.mcpServer()to a transport yourself, it is now a v2McpServer— use the v2 transports. Requires Node.js 20+ andzod4.2+. - Spending grants now need a wallet signature by default. A grant's approve link opens a signing step on the phone, and the grant only becomes spendable once the wallet signs its exact limits. To keep the old click-to-approve page, pass
signing: "page"when you create the grant. - Carts on MCP 2026-07-28, and in
statelessMcpmode, are keyed by a cart id. Those connections have no session, so the store hands out acartIdwith the first cart-related result, and every later cart call passes it back. Claude and the built-in product picker do this for you; a custom client should passcartIdalong too.
What's new
- Works with MCP's July 2026 version (2026-07-28) — the version claude.ai now uses — on the same
/mcpendpoint as before; apps on the older version keep working unchanged. A spending grant's follow-up questions use the new protocol's built-in "I need more information" answer when the app supports it. - Each conversation gets its own cart in
statelessMcpmode. Previously, a store runningstatelessMcp: true(the multi-server mode for hosts like Vercel) gave every shopper the same cart. If you deploy withstatelessMcp, upgrade. With a sharedstorage, a conversation's cart now follows it from server to server. - Wallet-signed spending grants. The phone wallet signs the grant's limits (budget, per-purchase cap, what it may buy) before the agent can spend anything.
- Grants pinned to one product. Tell the store what you want in your own words ("black court sneakers, size 10"); it asks follow-up questions until the product is exact, and the grant can buy that product and nothing else.
- Grants that say what they can't buy. Before you approve, the page shows what the grant can't buy yet and lets you fix it — for example, prove your age so age-restricted items become buyable, or show a loyalty card for a discount on every purchase.
- Grant cards. A spending grant now appears as a visual card in the conversation instead of a sentence.
list-productsand batch lookups. Agents can read the catalog as plain data (filter, search, page through it) and look up several products in one call.redisStorage.fromEnv()reads the standard Vercel KV or Upstash environment variables, so one server file runs in memory locally and on shared storage when deployed.- The gate can identify itself to the wallet (
readerIdentity), so the wallet no longer warns that the website asking for the data is unknown.
Fixes
- Placing an order the server can't find is now refused, instead of reporting "✓ Order placed" for a purchase that never happened.
- When an order is refused at payment, the page tells the buyer why and what to do, instead of showing a row of green checkmarks.
- The payment rail uses the fingerprint (hash) method the wallet actually used when it checks that the wallet approved the exact amount, and explains a failed check in plain words.
What this release does not change
The credential checks still don't verify who issued a credential. Age and loyalty checks report trust_level: "presence-only-demo": they enforce that the right claim is disclosed and bound to this request, but a self-made credential would pass. Wallet-signed grants report "device-signed": the signature is real, but the wallet's key isn't anchored to a trusted issuer. Treat both as demos, not safety controls. No real money moves on the demo payment rails.
Full list of changes: v0.4.0...v0.5.0
v0.4.0 — configuration preflight, bring-your-own-host, and a stricter money path
CredentAgent is the consent layer for AI agents: before an agent completes something
consequential — a payment, an age check, releasing a record — a person has to prove a
credential from their phone wallet. This release adds a configuration check you can run in
one call, a supported way to plug the gate into your own application instead of the bundled
storefront, branding for the consent pages, and a safer ordering on the money path.
⚠️ Breaking change — only if you wrote your own external checker
An external checker is a plug-in that does the verifying and the charging on
CredentAgent's behalf, instead of using the built-in flows. If you wrote one against 0.3.1,
this release changes the contract you built to.
Your checker used to verify and charge in a single step. It now verifies in one step and
charges in a separate one, and CredentAgent decides when the charge happens.
The reason: CredentAgent's own rules can be stricter than your checker's. Your checker might
be satisfied by an 18-and-over proof where the shop requires 21-and-over. Under the old
arrangement the card was charged before CredentAgent got to say no — so a 20-year-old's
alcohol order would be charged and then refused. Charging is now something CredentAgent
authorizes, never the checker.
Three specific changes:
- Move your charging code out of
consume()and into the newsettle().consume()must
now verify only.settle()is optional — a checker that only proves identity, with no
payment involved, is never called and can leave it out. DelegatedVerdict.settlementwas removed. If you returned your receipt there, it is now
ignored. Return it fromsettle()instead. This one produces no error message — worth
searching your code for.binding.payeeis now required rather than optional. This one will fail to compile, which
is the intent.
Full migration guide with before/after code:
#161
If you use the built-in consent flows or the bundled storefront, none of this affects you.
New
credentagent.doctor() — check your setup in one call. Configuration mistakes used to
surface as a confusing failure halfway through a checkout. doctor() inspects your
deployment and hands back a plain list of findings, each with the problem and how to fix it.
Run it at startup or in a health check.
defineHost() — use the gate with your own application. Previously, wiring the gate into
something other than the bundled storefront meant assembling several pieces by hand and
knowing which internal slots to fill. Now you hand defineHost() your catalog, your order
storage, and your completed-order storage, and it builds the rest and wires it up. Three
lines instead of a page of plumbing.
Branded consent pages. new CredentAgent({ branding }) lets the pages your customers see
during a checkout carry your own look instead of the default. The honesty notice on each page
is fixed and cannot be removed or restyled through branding — see the note at the bottom.
Payment credentials work on iPhone. The gate now presents a stable identity to iOS
wallets on the ISO-mdoc path, which is what those wallets require to show a consistent
requester.
Easier credential testing. The tester page now offers QR codes and a raw-certificate
fallback for getting a demo credential onto a phone.
Fixed
Custom credential checks could be skipped, could deadlock, or could silently do nothing.
Three separate gaps in how custom credential rules were enforced. The most serious failed
open — an order could complete without the check it was supposed to require. If you defined
your own credential type, this is the reason to upgrade.
Two different credentials could collide and confuse the wallet. Credential identifiers
were derived from the last part of a document type name. Because these names are conventionally
version-numbered, a payment credential and a loyalty credential could both end up called
"1" — so a request containing both was ambiguous. Identifiers now derive from the full name.
Note this changes the identifier sent to wallets; verification is unaffected because responses
are matched on the credential's own field paths, not the identifier.
Spending grants could be overspent under concurrent use. Two purchases arriving at the
same moment against one grant could both be approved against the same remaining balance.
Spends against a grant are now serialized. Grant amounts are also held as whole cents
throughout, removing the rounding drift that fractional arithmetic introduced.
Checkout receipts now say how payment actually settled instead of implying a settlement
path that may not have run.
The storefront fails immediately when its display bundle is missing, with a message
naming the problem, rather than starting up and failing later at request time.
The Checkout button is reachable on a phone. It could sit off-screen on narrow displays.
Upgrading
npm install @openmobilehub/credentagent-gate@0.4.0 @openmobilehub/credentagent-storefront@0.4.0
Both packages version together — install them as a pair. If you only use the built-in flows,
no code changes are needed.
About trust — please read before deploying
The cryptography on the wire is real: WebAuthn on the passkey flow, and genuine encryption,
nonce binding, and ISO-mdoc parsing on the wallet flows.
But there is still no issuer or device-signature trust anchor. Nothing yet verifies that a
credential was issued by an authority you trust, and the mandate is signed with a development
key. A self-crafted credential would pass. This is why the wallet flows report
trust_level: "presence-only-demo".
A presence-only gate enforces disclosure and binding — that something was presented, and
that it was bound to this exact amount and this exact server. It does not enforce trust.
Do not deploy it as a real safety control, and do not rely on it for an age restriction
that carries legal weight. Issuer-verified trust is the next line of work, not something in
this release.
v0.3.1 — orders, webhooks, grants
What this release is
The npm packages @openmobilehub/credentagent-gate@0.3.1 and @openmobilehub/credentagent-storefront@0.3.1, published 2026-07-26.
This release also backfills the GitHub record: 0.3.0 (published to npm 2026-07-25) and 0.3.1 both shipped without a tag or release. The tags v0.3.0 and v0.3.1 now mark the exact commits each was published from, and these notes cover everything since v0.2.0.
Highlights
credentagent.orders.serve(app)— the human-present checkout (order token, ceremony pages, completion) wired into an Express app in one call.credentagent.webhooks— a real, signed HTTPorder.settledevent, withconstructEvent-style signature verification on the receiving side.- Grants — approve a spending limit once, and the agent can buy against it later while you're away; grant tools are exposed on the MCP server so an agent can use them directly.
defineCredential— the generic credential rail: define a custom credential type and the proof completes on the phone wallet, same as the built-in age and membership gates.- Fixes:
browse-productsoutput is now legible to headless agents (the 0.3.1 patch); the age request accepts an mDL or an EU personal ID instead of demanding both; checkout stepper/ceremony sync; serverless deploy fixes.
Honesty note (read before relying on a gate)
trust_level remains presence-only-demo on the OpenID4VP rails. The wire cryptography is real — WebAuthn on the passkey rail, OpenID4VP response decryption with nonce binding, ISO-mdoc parsing — but there is no issuer or device-signature trust anchor yet, and the AP2 mandate is dev-signed. A presence-only gate enforces disclosure and binding, not trust: a self-crafted credential would pass. Do not present it as a real safety control. Issuer-verified trust is the next milestone.
v0.2.0 — CredentAgent (rename)
v0.2.0 — AttestoMCP (all-caps class rename)
Breaking: the public class is renamed AttestoMcp → AttestoMCP (all-caps MCP brand token; #26/#30). Republishes @openmobilehub/attestomcp-gate@0.2.0 + @openmobilehub/attestomcp-storefront@0.2.0. Package names, /attestomcp/* routes, and the lowercase attestomcp instance var are unchanged. Downstream consumers update new AttestoMcp() → new AttestoMCP() and bump to ^0.2.0.
v0.1.0 — attestomcp (renamed from attesto)
Republish under the new name: @openmobilehub/attestomcp-gate@0.1.0 and @openmobilehub/attestomcp-storefront@0.1.0 (renamed from @openmobilehub/attesto-*, which are being deprecated → these). Same code, new package names + API (AttestoMcp) + routes (/attestomcp/*).
v0.1.0
What's Changed
- Add Claude Code GitHub Workflow by @dzuluaga in #5
- feat(attesto-gate): signed Cart Mandate (ap2.CartMandate) — core + enforcement by @dzuluaga in #1
- test(attesto-gate): standalone completion.test.ts (audit-flagged seam gap) by @dzuluaga in #3
- example: gate-any-action — identity-first, non-commerce gating (no checkout) by @dzuluaga in #4
- docs+ci: ROADMAP, LICENSE, deployment guide, gated Claude review workflow by @dzuluaga in #2
- feat(attesto-gate): reconcile Cart Mandate ↔ Payment Mandate (amount binding across paths) by @dzuluaga in #6
New Contributors
Full Changelog: https://github.com/openmobilehub/attesto/commits/v0.1.0