NEW0.6 — an AI agent can spend a permission you signed on your phone →
FOR MCP SERVERS THAT SELL

Let AI agents shop your store.CredentAgent makes them ask first.

CredentAgent is an open-source Node library for MCP servers. Put any tool — checkout, a 21+ item, a member price — behind proof from the shopper's phone wallet. The agent does the shopping; the human approves what matters, and your server enforces it.

npm i @openmobilehub/credentagent-gate

Apache-2.0 · Express + any MCP client (Claude, ChatGPT, Goose) · OpenID4VP · pre-1.0, honest status

your-mcp-server.js — the checkout tool's policy
// completes only after the shopper proves: policy: [ required(age.over(21)), required(payment.in("usd")), ]
How it works

Your tool returns a link, not a yes. The shopper's wallet decides.

When an agent calls a tool you've gated, the gate answers with what's required and an approve link. The shopper proves it on their phone, your server verifies it, and only then does the order complete.

  1. The shopper asks the agent: “Buy the Oak Whiskey.”
  2. The agent calls your checkout tool.
  3. Your server answers: needs age_over_21 + payment, and here's the approve link.
  4. The agent hands the link (or a QR) to the shopper.
  5. The shopper opens it and approves in their wallet.
  6. The wallet presents the credentials to your server (OpenID4VP).
  7. Your server verifies the proof and re-prices the order itself, then completes it.
  8. The agent sees the order settled and carries on.
The agent never holds a credentialIt carries a link. The proof travels wallet → your server.
Enforced on your serverAmount and policy are re-derived server-side; there's no button the agent can click past.
Works in any MCP clientClaude, ChatGPT, Goose — the tool just returns text and a link. No client plugin.
Try it live

See the gate fire before you write a line.

An agent shops on a real MCP storefront built with CredentAgent — and stops at the gate until you prove your age.

Get started

Gate your first tool in three steps.

For an MCP server you already run. Starting from nothing? The storefront gives you a whole gated store instead.

1

Install the gate

One package, types included. It has no MCP SDK dependency, so it fits whichever MCP server you already use.

npm i @openmobilehub/credentagent-gate
2

Wire the checkout once, at startup

orders.serve(app) adds the approve page and the wallet proof rails to your Express app.

import { CredentAgent, age, payment, required, } from "@openmobilehub/credentagent-gate"; const credentagent = new CredentAgent({ walletOrigin: "https://shop.example", // your public origin }); credentagent.orders.serve(app); // approve page + proof rails // fires once the shopper approves credentagent.on("order.settled", ({ id }) => fulfill(id));
3

Return an approve link from your checkout tool

Create the order with its policy and hand approveUrl back to the agent. A status tool reads orders.retrieve(id) — MCP has no push, so the agent polls it.

// inside your MCP checkout tool const { id, approveUrl } = await credentagent.orders.create({ order, // { id, total, currency, lines }, priced by your server policy: [required(age.over(21)), required(payment.in("usd"))], }); return { content: [{ type: "text", text: `Approve here: ${approveUrl}` }], }; // inside your order-status tool await credentagent.orders.retrieve(id); // → { ok: true, completion } or { ok: false, pending: true, approveUrl }
Have your own MCP server?

@openmobilehub/credentagent-gate

Gate the tools you already have — the three steps above. Bring your own catalog and order store with defineHost().

npm →
Starting fresh, or want a reference store?

@openmobilehub/credentagent-storefront

A ready-to-run MCP store: nine shopping tools, a product widget and a gated checkout. Install it with the gate at the same version — npm i @openmobilehub/credentagent-gate@0.6.0 @openmobilehub/credentagent-storefront@0.6.0.

npm →
Use cases

One policy line per rule your merchant needs.

Age, membership and payment are built in. Anything else a wallet can hold, you define.

Age-restricted goods

The agent can't complete a 21+ order until the shopper proves their age.

required(age.over(21))

Member pricing

The member price applies when the shopper proves membership; checkout still works without it.

optional(membership.discount(10))

Payment approval

The shopper approves this exact amount and payee in their wallet.

required(payment.in("usd"))

Any credential

A prescription, a license, employee status — define the credential and gate the tool.

defineCredential({ id: "prescription", … })

Today these wallet checks run at ⚠ presence-only-demo — real wire crypto, no issuer trust anchor yet. What that means →

Go further

Let the agent buy on its own — within limits the shopper signed.

When the checkout works, these are the next pieces merchants ask for: unattended purchases, your own catalog and order store, and a signal to fulfillment.

Approve once — with a signature. The human sets a bounded budget (how much, per purchase, and what), and the grant only becomes spendable after their phone wallet signs those exact limits. The agent then buys against it unattended, every rule re-checked server-side on every spend. Age is never delegated to the agent: a 21+ item needs the human's own proof — from their wallet while approving the grant, or live at checkout. Without it, the spend refuses.

credentagent.grants.serve(app); // serves each grant's signing page const grant = await credentagent.grants.create({ merchant: "utopia", budget: 200, perSpend: 130, allow: { categories: ["Beverages"] }, // bound WHAT may be bought, not just how much }); // signing defaults to "device" sendToUser(grant.approveUrl); // → the wallet signs the grant's exact bounds // later, human away const g = await credentagent.grants.retrieve(grant.id); g.trustLevel; // "device-signed" — real signature, demo trust anchor const s = await g.spend({ idempotencyKey: "order-1", items: [{ sku: "coffee" }] }); // → { ok: true, … } or { ok: false, code: "budget-exceeded" | "step-up" | … } await g.revoke(); // kill-switch — the very next spend refuses
Status · honest by design

Pre-1.0. Here's exactly what's real, and what production still needs.

The site follows the SDK's own trust_level — it never claims more than the library enforces.

✓ Real today

  • Passkey approvals — real WebAuthn, origin-bound, replay-protected
  • Signed order.settled webhooks (HMAC-SHA256)
  • Wallet device signatures over a spending grant's exact limits
  • Server-side enforcement: amount and policy re-derived on completion

⚠ Demo-grade today

  • Wallet credentials (age, membership, payment) are presence-only-demo: the wire crypto is real, but with no issuer trust anchor a self-crafted credential would pass
  • Wallet-signed grants: real signature, demo trust anchor
  • Click-to-approve grants — for demos and CI only

→ Before production

  • Issuer-verified trust — mdoc issuer/device signatures (Multipaz): the next milestone
  • Or relay a real external verifier — the interface ships; no adapter yet
  • Your deployment: a real GATE_SECRET, public walletOrigin, shared stores — doctor() checks them
The full trust table, rail by rail
passkey — real WebAuthn, origin/RP-ID-bound, replay-protected
✓ real crypto
webhooks — HMAC-SHA256-signed order.settled, replay-protected
✓ real crypto
credential — age, membership, any defineCredential(): OpenID4VP JWE + nonce + HPKE + mdoc parse, explicit positive claim
⚠ presence-only-demo
dc-payment — the same, plus the wallet's device-signed hash re-checked against this exact amount & payee
⚠ presence-only-demo
wallet-signed spending grants (default) — the gate verifies the wallet's device signature over the grant's exact limits; the key isn't anchored to a trusted issuer yet, so a self-made key would still pass
device-signed · demo
click-to-approve grants (opt-in signing: "page") — the server takes the approver's word for it; for demos and CI with no phone
⚠ server-issued-demo
external verifier — the gate can relay an issuer-verified verdict from a real verifier, while re-checking amount & policy itself. No real adapter ships yet.
interface only
issuer-verified trust, built in — mdoc issuer/device signatures (Multipaz) + a key-bound AP2 mandate
roadmap — next milestone

The wire crypto is real; the issuer trust anchor is not yet — a self-crafted credential would still pass. Wallet-signed grants go one step further (the signature is real), but without an anchor they are still a demo. A presence-only gate is never sold as a real safety control. Branding can restyle the consent pages, but never the honesty footer.

Don't take our word for it — check a credential yourself

What the wallet sends isn't a CredentAgent token. It's a standard ISO 18013-5 mdoc: issuer-signed claims, a signed list of their digests, and a signature from the phone's own key. Independent tools from the OpenWallet Foundation's Multipaz project open it.

1 · Look inside

Paste any mdoc DeviceResponse into Multipaz Tools' viewer: document type, every disclosed claim, validity dates, per-claim digests, the device key. It decodes in your browser — nothing is uploaded.

Coming in the next gate release: inspectPresentations: true puts an Inspect this presentation link on the consent page (PR #223).

2 · Check the signatures

Multipaz Tools' verifier asks your wallet for a credential itself, then runs the Multipaz SDK's checks: the issuer's signature, each claim's digest, the device signature, and the validity window.

3 · See who signed it

Every credential carries the certificate that signed it — the payment page's Issuer certificate › link opens it in the X.509 viewer. Multipaz Wallet's built-in test credentials are signed by OWF Multipaz TEST IACA; the CredentAgent demo passes by Utopia Demo IACA (certificate). Both are test authorities, not trusted issuers.

What this shows: the credentials are real, standard, and signed. What it doesn't: that the issuer deserves trust — the demo CA is ours, and the gate itself doesn't check the issuer signature yet (presence-only-demo, above).

Built on open standards
OpenID4VPWebAuthnISO mdocAP2MCPMultipaz

An OpenMobileHub project with the OpenWallet Foundation and the AAIF Foundation. Apache-2.0.