paywall-mcp
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/listfrom 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.
Related MCP server: protect-mcp
How the dual-call pattern works
For any priced tool:
First call — LLM calls
priced_tool({...args})withoutpayment_hash. paywall-mcp issues a bolt11 invoice through your NWC wallet and returns:{ "error": "payment_required", "invoice": "lnbc...", "payment_hash": "abc123...", "amount_sats": 21, "expires_in_seconds": 600, "next_step": "Pay this bolt11 ..." }Payment — the LLM (or its operator) pays the invoice. Easiest path: use
nwc-mcp— the same LLM can callnwc_pay_invoiceto settle.Second call — LLM calls
priced_tool({...args, payment_hash: "abc123..."}). paywall-mcp verifies settlement via NWClookup_invoice, stripspayment_hashfrom 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_MAPin 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_invoiceare the only permissions paywall-mcp needs. A receive-only NWC connection is perfectly fine and recommended — paywall-mcp never spends.
Install
# From npm
npx -y paywall-mcp
# From source
git clone <repo>
cd paywall-mcp
corepack enable pnpm
pnpm install
pnpm buildConfigure
cp .env.example .env
# edit .env: set PAYWALL_UPSTREAM_COMMAND/ARGS, NWC_CONNECTION_STRING, pricesThe 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 |
| Executable to spawn as the upstream MCP server (e.g., |
| JSON array of args passed to the upstream command (e.g., |
Required when any tool has a non-zero price
Var | Purpose |
| NIP-47 NWC URI for the seller's RECEIVE wallet. |
Pricing
Var | Default | Purpose |
|
| Default price for any tool not in the price map. |
|
| JSON object mapping tool names to sat prices. Per-tool 0 = free; missing = use default. Example: |
Optional
Var | Default | Purpose |
| (parent cwd) | Working directory for the upstream child process. |
| (inherits) | JSON object of env-var overrides for the upstream. |
|
| Disables all paid tool calls ( |
|
| Invoice TTL. Past this, |
|
| Label appended to each priced tool's description in |
|
| Server 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:
{
"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 returnedSafety 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 completedoutcome: "blocked"— read-only refusal, invalid hash, replay, mismatch, not-settledoutcome: "error"— upstream call failed, NWClookup_invoicefailed, etc.
Tail the audit log for ground truth — independent of whatever the LLM tells you.
tail -f paywall-mcp-audit.log | jq .Wire into Claude Desktop / Claude Code / Cursor
{
"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
pnpm typecheck
pnpm test # 13 vitest cases (config resolution + payment-gate state machine)
pnpm build # ~18 KB ESM bundleCompanion servers
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— NOSTR identity, publishing, encrypted DMs.marketplace-mcp— Run a NIP-15 / Shopstr storefront from an agent.albyhub-admin-mcp— Alby Hub node-admin via HTTP API.
License
MIT — see LICENSE.
Contact / Issues
Built by LLMOps.Pro.
NOSTR:
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.
This server cannot be installed
Maintenance
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/llmops-pro/paywall-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server