You built an MCP server. Now agents are hammering your premium tools for free and you've got no lever to pull.
The boring fix is "add auth" — OAuth tokens, API keys, a whole user management system. But that's overkill for a tool that should just cost 21 sats per call.
Here's the short fix.
What you need
Two packages:
npm install @powforge/captcha-paymcp-provider @powforge/paymcp-l402-provider paymcp
- paymcp — decorator framework that wraps MCP tools with payment gates
- @powforge/captcha-paymcp-provider — PoW-skip tier: agent solves SHA-256, no invoice needed
- @powforge/paymcp-l402-provider — Lightning tier: agent pays a BOLT11 invoice via LNBits
The 10-line integration
const { PayMCP } = require('paymcp');
const { CaptchaPowProvider } = require('@powforge/captcha-paymcp-provider');
const { LnbitsPaymentProvider } = require('@powforge/paymcp-l402-provider');
PayMCP(mcp, {
providers: [
new CaptchaPowProvider({ captchaUrl: 'https://captcha.powforge.dev' }),
new LnbitsPaymentProvider({
lnbitsUrl: process.env.LNBITS_URL,
lnbitsApiKey: process.env.LNBITS_KEY,
satsAmount: 21,
}),
],
});
Drop that right after you construct your McpServer. Tag any tool with { _meta: { price: 1 } } and it's now gated.
How it works
PoW path (free, ~5-10s of CPU):
-
createPaymentfetches a SHA-256 challenge from the captcha server. - It mines the nonce server-side — no round-trip to the client needed.
- Returns a
pow://URI encoding all params a PoW-capable MCP client SDK needs. -
getPaymentStatussubmits the nonce to/api/verifyand returns'paid'on confirm.
Lightning path (21 sats):
-
createPaymentmints a BOLT11 invoice via LNBits. - Returns the invoice in the payment URL.
-
getPaymentStatuspolls until the invoice is settled.
paymcp tries the PoW provider first. If the calling agent doesn't support pow:// URIs, it falls through to the Lightning invoice. The agent picks whichever it can satisfy.
Why both tiers
Some agents are compute-rich, sats-poor — they'd rather burn CPU cycles than need a wallet. Others are running in headless pipelines with a Lightning wallet already wired. Give them both options and you capture more traffic without managing two separate auth flows.
The pow:// URI scheme also means the payment proof travels in-band with the request — no session state, no cookies, no database lookup beyond the challenge ledger the captcha server already maintains.
Full example
'use strict';
const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
const { PayMCP } = require('paymcp');
const { CaptchaPowProvider } = require('@powforge/captcha-paymcp-provider');
const { LnbitsPaymentProvider } = require('@powforge/paymcp-l402-provider');
const { z } = require('zod');
const mcp = new McpServer({ name: 'my-mcp-server', version: '1.0.0' });
PayMCP(mcp, {
providers: [
new CaptchaPowProvider({ captchaUrl: 'https://captcha.powforge.dev' }),
new LnbitsPaymentProvider({
lnbitsUrl: process.env.LNBITS_URL,
lnbitsApiKey: process.env.LNBITS_KEY,
satsAmount: 21,
}),
],
});
mcp.tool(
'premium_lookup',
'Premium data lookup — PoW-skip (free) or Lightning (21 sats)',
{ query: z.string() },
{ _meta: { price: 1 } },
async ({ query }) => ({
content: [{ type: 'text', text: `Result for: ${query}` }],
}),
);
const transport = new StdioServerTransport();
mcp.connect(transport).then(() => {
process.stderr.write('MCP server running\n');
});
Self-hosting the captcha server
The captchaUrl above points to captcha.powforge.dev which handles challenge issuance and verification. You can self-host it too — it's @powforge/captcha running as a Node.js server. The whole thing is under 300 lines.
What it costs
- PoW path: free for the agent, a few seconds of server CPU per call, and a round-trip to your captcha endpoint.
-
Lightning path: 21 sats (or whatever
satsAmountyou set) credited to your LNBits wallet. - No external auth services, no API keys to rotate, no user database.
The PoW path is also a natural rate limiter. Solving a difficulty-14 SHA-256 challenge takes roughly 5-10 seconds on a modern CPU — plenty of friction to discourage abuse, not so much that legitimate agents bail out.
Source on npm:
Top comments (0)