npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ask-only-pay-mcp

v0.1.0

Published

MCP server that lets an AI agent ask for a payment or an account signup, but never do it. A separate approver shows a person its own recap over mTLS; the agent only sees an id and a state.

Readme

ask-only-pay-mcp

CI

An MCP server that lets an AI agent ask for a payment or an account signup, but never do it.

A person approves each ask on a recap that a separate process, the approver, renders itself. The agent gets back only an id and a state: never a card, a password, or the recap.

 AI agent ──MCP──▶ ask-only-pay-mcp ──HTTPS + mTLS──▶ approver ──▶ person at a terminal
   "pay 19.00 EUR       mints the id,                   stores the ask,     reads the recap,
    to Example Shop"    checks every field              shows the recap     types approve / deny
        ◀── {approval_id, state} ────────◀── {approval_id, state, ref} ──┘

Why

Agents that browse the web reach checkouts and signup forms. Letting one type a card number or a password is how money and accounts get lost, and a prompt-injected page can talk an agent into almost anything. The usual fix is a rule in the prompt: "ask before you pay". A rule can be argued with.

This project makes the agent's side powerless by construction instead:

| property | how it is enforced | |---|---| | It can only ask. | The client has three methods: ask to pay, ask to create an account, read a status. There is no approve, cancel or execute method, and the approver has no HTTP endpoint for one. Approval happens only on the approver's own console. | | Nothing secret comes back. | Every answer from the approver is parsed against a strict schema. An extra field (a recap, a card, a token) fails the parse, and the agent gets an error code instead of the data. The two short codes it may pass on (ref, error_code) are at most 64 letters, digits, - and _, and never a card number. | | What the person approves is what is recorded. | The approver renders the recap from the ask it stored. URLs are stored as the browser resolves them, so the URL shown is the URL passed on. An ask cannot be changed after it was made, and a decision is final. | | It cannot replay an ask. | The server mints the request id, never the agent. The approver refuses an id it has seen. | | Errors are codes. | A failure returns {"error": "CODE"}: never a message, URL or response body that could carry something the agent should not see. | | No secrets in arguments. | A card is a handle (card-2), an identity a handle (work). A field that holds a card number (13–19 digits passing Luhn, even inside a longer number or split by spaces or dots) is refused. | | No silent fallback. | mTLS is required, and there is no switch to turn it off. Missing, unreadable or invalid certificates stop the server before it serves anything. |

The recap also defends the person against text the agent wrote:

  • No fake recaps. The approver cuts every line to 76 terminal columns itself (counting any character that is not plain ASCII as two columns, as wide as CJK or emoji get) and marks each further line with >, so a fake [ask …] block hidden in a long reason can never begin a line. A decision is answered with what was decided (approved: PAY 19.00 EUR at shop.example), from the stored ask.
  • No hidden characters. Control, bidi, zero-width, tag and other invisible characters, and stacks of combining marks, are shown as \u{…} escapes, and runs of spaces become one.
  • No look-alike sites. The site is shown in punycode, so shop.exаmple with a Cyrillic а shows as xn--…. A host too long for one line is shown by its end (....evil.com), which names who owns it.

Quick start (from a clone)

Needs Node.js 22.12 or later, and openssl for the certificates.

git clone https://github.com/Spundu/ask-only-pay-mcp.git
cd ask-only-pay-mcp
npm ci                      # also builds dist/
npm run certs -- certs      # two separate CAs and two certificates, in ./certs (git-ignored)

1. Start the approver in a terminal of its own:

ASK_ONLY_PAY_APPROVER_CERT=certs/approver.pem \
ASK_ONLY_PAY_APPROVER_KEY=certs/approver.key \
ASK_ONLY_PAY_APPROVER_CA=certs/client-ca.pem \
node dist/approver/main.js

2. Add the MCP server to Claude Code (from the clone's folder):

claude mcp add --transport stdio ask-only-pay \
  -e ASK_ONLY_PAY_URL=https://localhost:8443 \
  -e ASK_ONLY_PAY_CLIENT_CERT="$PWD/certs/client.pem" \
  -e ASK_ONLY_PAY_CLIENT_KEY="$PWD/certs/client.key" \
  -e ASK_ONLY_PAY_CA="$PWD/certs/approver-ca.pem" \
  -- node "$PWD/dist/index.js"

3. Ask Claude to buy something. The approver's terminal shows:

[ask K7Q2M4XZ3A] PAY 19.00 EUR
  to:        Example Shop
  card:      card-2
  site:      shop.example
  checkout:  https://shop.example/checkout/123
  reason:    (from the agent, unchecked) renew the domain
  expires:   in 10 min
  approving: PAY 19.00 EUR at shop.example
  approve K7Q2M4XZ3A   or   deny K7Q2M4XZ3A

Type approve K7Q2M4XZ3A or deny K7Q2M4XZ3A. The approver answers with what you decided:

K7Q2M4XZ3A: approved: PAY 19.00 EUR at shop.example
  demo-3e40a688: no money moved (reference approver)

list shows what is pending. Claude reads the decision with the status verb.

The tool

One tool, request, with three verbs:

| verb | the agent gives | it gets back | |---|---|---| | pay | merchant, amount (a decimal string such as "19.00"), currency (EUR), card_handle, checkout_url, optional reason | {approval_id, state: "pending", expires_at} | | account_create | provider_url, optional identity_handle and reason | the same | | status | approval_id | {approval_id, state, ref?}: pending, approved, denied or expired |

The tool description tells the agent that "pending" and "denied" are normal answers, not obstacles to work around. Every field rule, error code and limit is in SPEC.md.

What the reference approver is, and is not

The approver here is a reference. On approval it records a reference starting demo- and moves no money. A real deployment keeps the same three endpoints and puts a real payment or signup step behind the approval.

Limits worth knowing:

  • Where the approver runs matters. Its console is the approval channel. Run it where the agent cannot type: another user account, another machine, or at least a terminal the agent has no access to. If the agent has a shell as the same user on the same machine, it can reach anything that user can, including holding the approver's connection places so asks fail (the safe failure).
  • Check the site and approving lines. The merchant name and the reason are the agent's words. The site and the closing approving: line are the approver's own, from the stored URL.
  • A recap needs a terminal at least 76 columns wide. On a narrower one the approver shows only the ids of new asks and asks you to widen it, since wrapped lines could let agent text start a row.
  • The client key only lets you ask. An agent that reads client.key can make more asks, which the person sees and denies. It cannot approve one. It can, though, fill the 50 pending places with junk and block real asks until they expire (10 minutes). Keep the key readable only by the MCP server's user.
  • The card check is a net, not a wall. It catches a card number pasted by mistake. An agent set on leaking one could spell the digits out. The real defence is that the agent never sees a card.
  • Memory only. The approver keeps asks in memory. A restart forgets them, which denies nothing that was not yet approved.

Development

npm ci
npm run typecheck
npm run lint
npm test            # unit, real mTLS, and end to end with the built programs

The end-to-end tests start the built approver and the built MCP server as real processes, connect an MCP client, and type approve and deny on the approver's console, as a person would. The mTLS tests use fresh certificates from scripts/certs.mjs and check that the approver refuses a client with no certificate, one from another CA, and its own certificate used as a client.

Related

  • money-guard-mcp: an MCP server that checks how a project handles money (float columns, unbalanced ledgers, tampered audit logs). This one gates the money an agent wants to spend.
  • agent-shell-gate: a permission gate for an agent's shell commands.

Licence

MIT. See LICENSE.