# paywall-mcp **Paywall ANY stdio MCP server with Lightning, without modifying it.** `paywall-mcp` is a generic sidecar: configure it with an upstream MCP server command and a per-tool price map, and it transparently: - Forwards `tools/list` from the upstream to the LLM client, with prices appended to each tool's description. - Intercepts `tools/call`: free tools pass through; priced tools require a paid Lightning invoice (via Nostr Wallet Connect / NIP-47) before the call is forwarded. No code changes to the upstream server. Works with Anthropic's reference MCP servers, your own, or any third-party MCP server that speaks stdio. > **v0.1 — proxy + payment gate complete.** Spawns a stdio upstream as a child process; per-tool pricing via env; in-memory invoice cache + replay protection; audit log; read-only mode. Persistent cache + HTTP/SSE upstream transport deferred to v0.2. --- ## Why this exists Modern paid-API patterns (Lightning paywall, L402, micropayments) exist for HTTP but the MCP ecosystem has no standard for paid tool calls. Building it into each individual server is repetitive and error-prone. `paywall-mcp` is the missing sidecar: write your tools as a normal MCP server, then wrap it with `paywall-mcp` to charge sats per call. ## How the dual-call pattern works For any priced tool: 1. **First call** — LLM calls `priced_tool({...args})` *without* `payment_hash`. paywall-mcp issues a bolt11 invoice through your NWC wallet and returns: ```json { "error": "payment_required", "invoice": "lnbc...", "payment_hash": "abc123...", "amount_sats": 21, "expires_in_seconds": 600, "next_step": "Pay this bolt11 ..." } ``` 2. **Payment** — the LLM (or its operator) pays the invoice. Easiest path: use [`nwc-mcp`](https://npmjs.com/package/nwc-mcp) — the same LLM can call `nwc_pay_invoice` to settle. 3. **Second call** — LLM calls `priced_tool({...args, payment_hash: "abc123..."})`. paywall-mcp verifies settlement via NWC `lookup_invoice`, strips `payment_hash` from the args, forwards the original call to the upstream, returns the upstream's result. Replay protection: the same `payment_hash` cannot be redeemed twice. Buyers pay a fresh invoice for each call. --- ## What you can build with it - **Charge sats for premium tools** in an MCP server you already have, by adding one wrapper process. - **Per-tool pricing tiers** — `free_lookup: 0`, `premium_analysis: 100`, `rare_alpha_signal: 5000`. Buyers see prices in tool descriptions. - **Bundle-and-resell third-party MCP servers** — wrap someone else's open-source MCP server with your paywall and offer it as a managed paid service. - **A/B test pricing** — adjust `PAYWALL_PRICE_MAP` in env, restart, you're at the new price. - **Time-limited promotional pricing** — start at 21 sats, raise to 100 sats once usage proves the value. --- ## Requirements - Node 20+ - An existing stdio MCP server to wrap (paywall-mcp doesn't host tools itself; it gates an upstream's tools). - A NIP-47 NWC connection string for the **seller's** receive wallet. `make_invoice` + `lookup_invoice` are the only permissions paywall-mcp needs. **A receive-only NWC connection is perfectly fine and recommended** — paywall-mcp never spends. ## Install ```bash # From npm npx -y paywall-mcp # From source git clone cd paywall-mcp corepack enable pnpm pnpm install pnpm build ``` ## Configure ```bash cp .env.example .env # edit .env: set PAYWALL_UPSTREAM_COMMAND/ARGS, NWC_CONNECTION_STRING, prices ``` The server auto-loads `.env` from its own directory (next to `dist/`) — deliberately NOT from cwd, to avoid env collisions when running multiple MCP servers in the same Claude Code session. ### Required | Var | Purpose | |---|---| | `PAYWALL_UPSTREAM_COMMAND` | Executable to spawn as the upstream MCP server (e.g., `node`). | | `PAYWALL_UPSTREAM_ARGS` | JSON array of args passed to the upstream command (e.g., `["/path/to/upstream/dist/index.js"]`). | ### Required when any tool has a non-zero price | Var | Purpose | |---|---| | `NWC_CONNECTION_STRING` | NIP-47 NWC URI for the seller's RECEIVE wallet. `make_invoice` + `lookup_invoice` permissions sufficient. paywall-mcp never spends. | ### Pricing | Var | Default | Purpose | |---|---|---| | `PAYWALL_DEFAULT_PRICE_SATS` | `0` | Default price for any tool not in the price map. `0` = free passthrough. | | `PAYWALL_PRICE_MAP` | `{}` | JSON object mapping tool names to sat prices. Per-tool 0 = free; missing = use default. Example: `{"premium_compliment":21,"rare_alpha_signal":5000}`. | ### Optional | Var | Default | Purpose | |---|---|---| | `PAYWALL_UPSTREAM_CWD` | (parent cwd) | Working directory for the upstream child process. | | `PAYWALL_UPSTREAM_ENV` | (inherits) | JSON object of env-var overrides for the upstream. | | `PAYWALL_READ_ONLY` | `false` | Disables all paid tool calls (`tools/list` still works). Useful for maintenance. | | `PAYWALL_INVOICE_TTL_SECONDS` | `600` | Invoice TTL. Past this, `payment_hash` is forgotten from cache and the buyer must re-issue. | | `PAYWALL_PRICE_LABEL_TEMPLATE` | `"(💰 {price} sats)"` | Label appended to each priced tool's description in `tools/list`. `{price}` is substituted with the sat amount. | | `PAYWALL_LOG_PATH` | `./paywall-mcp.log` | Server log. | | `PAYWALL_AUDIT_PATH` | `./paywall-mcp-audit.log` | NDJSON audit log (one line per call). | --- ## End-to-end example: paywall the bundled `paywall-mcp-test` server The companion `paywall-mcp-test` package exposes a single tool — `premium_compliment`. It already implements its own paywall pattern internally, but it's also a convenient stand-in for "any upstream MCP server" to demonstrate paywall-mcp itself. `.env`: ``` PAYWALL_UPSTREAM_COMMAND=node PAYWALL_UPSTREAM_ARGS=["/abs/path/to/paywall-mcp-test/dist/index.js"] NWC_CONNECTION_STRING=nostr+walletconnect://... PAYWALL_DEFAULT_PRICE_SATS=21 PAYWALL_PRICE_MAP={"premium_compliment":21} ``` Wire `paywall-mcp` (not the upstream directly) into your MCP client: ```json { "mcpServers": { "paywall": { "command": "npx", "args": ["-y", "paywall-mcp"], "env": {} } } } ``` Now from your agent: ``` 1. tools/list → premium_compliment ... (💰 21 sats) This tool requires ... 2. premium_compliment({}) → returns invoice + payment_hash 3. nwc_pay_invoice(invoice) → buyer pays 4. premium_compliment({ payment_hash: "..." }) → upstream's result returned ``` --- ## Safety model ``` tools/list → upstream.listTools() → augment descriptions with prices → return tools/call: if price == 0 → upstream.callTool(args) (passthrough) elif PAYWALL_READ_ONLY → refuse with paywall_read_only (block) elif no payment_hash → gate.issue() → return bolt11 + hash (issue) elif bad hash format → refuse with invalid_payment_hash (block) else (have hash): gate.verify() ──┬─ unknown_payment_hash → block ├─ payment_hash_already_redeemed → block (replay) ├─ payment_hash_tool_mismatch → block ├─ payment_not_settled → block └─ ok → strip hash → upstream.callTool() (paid passthrough) ``` Audit log entries (NDJSON, one per request): - **`outcome: "ok"`** — invoice issued, free passthrough, or paid passthrough completed - **`outcome: "blocked"`** — read-only refusal, invalid hash, replay, mismatch, not-settled - **`outcome: "error"`** — upstream call failed, NWC `lookup_invoice` failed, etc. Tail the audit log for ground truth — independent of whatever the LLM tells you. ```bash tail -f paywall-mcp-audit.log | jq . ``` --- ## Wire into Claude Desktop / Claude Code / Cursor ```json { "mcpServers": { "paywall": { "command": "npx", "args": ["-y", "paywall-mcp"], "env": {} } } } ``` Same `.env`-via-binary-dir pattern as the rest of the substrate — leave `env` empty in the client config; secrets stay in `paywall-mcp/.env`. --- ## Testing ```bash pnpm typecheck pnpm test # 13 vitest cases (config resolution + payment-gate state machine) pnpm build # ~18 KB ESM bundle ``` --- ## Companion servers - [`nwc-mcp`](https://npmjs.com/package/nwc-mcp) — Lightning wallet for the **buyer**. Lets the agent pay the invoices paywall-mcp issues. The matching half of the agent-pays-a-paid-tool loop. - [`nostr-ops-mcp`](https://npmjs.com/package/nostr-ops-mcp) — NOSTR identity, publishing, encrypted DMs. - [`marketplace-mcp`](https://npmjs.com/package/marketplace-mcp) — Run a NIP-15 / Shopstr storefront from an agent. - [`albyhub-admin-mcp`](https://npmjs.com/package/albyhub-admin-mcp) — Alby Hub node-admin via HTTP API. --- ## License MIT — see [`LICENSE`](./LICENSE). ## Contact / Issues Built by **LLMOps.Pro**. - **NOSTR:** [`npub1hdg932jvwc3jdvkqywgqv0ue4nn60exrf92asy8mtazt3hjg7d2s2yw0nw`](https://njump.me/npub1hdg932jvwc3jdvkqywgqv0ue4nn60exrf92asy8mtazt3hjg7d2s2yw0nw) — follow, DM, zap. - **Lightning Address:** `sovereigncitizens@getalby.com` — for support zaps and "this was useful" tips. - **Bug reports / feature requests:** open a GitHub issue (link forthcoming). - **Security issues:** please disclose privately via NOSTR DM before opening a public issue.