npm @powforge/mcp-l402-gatestdioupdated 4mo ago
Identity-scored Lightning paywall for MCP server operators.
What can you do with mcp l402 gate?
@powforge/mcp-l402-gate
Identity-scored Lightning paywall for MCP server operators.
L402 alone proves the caller paid 10 sats. It does not prove the caller has a reputation, has been around for more than 10 minutes, or that pricing one tool call shifts their economics at all. A fresh wallet pays the same 10 sats as a real user.
This package adds a Depth-of-Identity check on top of the L402 invoice. Drop it in front of an MCP tool and a caller has to (a) settle a Lightning invoice and (b) carry a DoI score above your threshold before the tool body runs. Cheap sybils still pay the toll, but the toll plus the per-pubkey reputation requirement is harder to grind than either piece on its own.
See it in action
A clone-and-run example server lives at github.com/zekebuilds-lab/mcp-l402-gate-example. It exposes one tool, bitcoin_data, that fetches the BTC/USD price plus mempool fees from mempool.space, gated by L402 + DoI. Clone it, fill in your LNBits creds, npm start, and you have a Lightning-gated MCP server running locally.
The Gap
Sats4AI's own documentation states the limitation plainly:
"autonomous agents cannot build reputation or receive preferential treatment across sessions."
@powforge/mcp-l402-gate closes that gap by composing L402 payment gating with the DoI oracle's composite identity score. A paying caller is also a known caller, with a per-pubkey reputation that survives across sessions and that costs irreversible work to fake.
Why not just L402
L402 is great wire format, weak abuse control. Recent MCP billing tools (sats4ai-mcp, invinoveritas, l402-kit, 402-mcp, coinopai-mcp) all ship the same 402 -> macaroon -> paid -> tool body flow, and an attacker can replay the flow from a fresh node every minute. coinopai-mcp's own author put it: "x402 is payment transport only. It doesn't handle agent identity, rate negotiation, multi-agent splits, or reputation."
PowForge has been shipping the missing piece. The DoI oracle at https://identity.powforge.dev returns a Schnorr-signed score for any Nostr pubkey, computed from observable irreversible work across four dimensions (social, access, vouch, economic). This package wires that score into the L402 path so a paying caller is also a costly-to-fake caller.
Requirements
- Node >= 18
- An LNBits wallet (URL + invoice/read API key) for the Lightning side
- An accessible PowForge oracle URL (default:
https://identity.powforge.dev)
5-line integration (Express)
const express = require('express');
const { mcpL402Middleware } = require('@powforge/mcp-l402-gate');
const app = express();
app.use('/tools/expensive', mcpL402Middleware({
secret: process.env.GATE_HMAC_SECRET,
lnbitsUrl: process.env.LNBITS_URL,
lnbitsApiKey: process.env.LNBITS_INVOICE_KEY,
satsAmount: 10,
minScore: 10, // composite >= 10 means "emerging" tier on the oracle
}));
app.post('/tools/expensive', (req, res) => {
// Reached only when L402 paid AND req.doiScore >= 10
res.json({ ok: true, doiScore: req.doiScore, l402: req.l402Token });
});
The caller passes their pubkey via the X-Caller-Pubkey header or ?pubkey= query string. v0.1.0 treats this as caller-asserted; v0.2.0 will bind it cryptographically via NIP-98.
MCP tool wrapping
const { mcpL402Tool } = require('@powforge/mcp-l402-gate');
const expensiveTool = mcpL402Tool({
secret: process.env.GATE_HMAC_SECRET,
lnbitsUrl: process.env.LNBITS_URL,
lnbitsApiKey: process.env.LNBITS_INVOICE_KEY,
satsAmount: 10,
minScore: 10,
}, {
name: 'image_render',
description: 'Render an image. 10 sats. Requires DoI score >= 10.',
inputSchema: {
type: 'object',
properties: {
prompt: { type: 'string' },
pubkey: { type: 'string' },
auth: { type: 'object', properties: { macaroon: { type: 'string' }, preimage: { type: 'string' } } },
},
required: ['prompt', 'pubkey'],
},
}, async (args, ctx) => {
// Runs only when paid AND ctx.doiScore >= 10
return { image_url: `https://example/r/${args.prompt}`, billed_to: ctx.doiScore };
});
// Register expensiveTool with your MCP server. On first call without args.auth,
// the tool returns { paid: false, challenge: { macaroon, invoice, ... } }.
// The MCP client pays the invoice, then re-calls with args.auth set.
Config reference
| Field | Default | Notes |
|---|---|---|
secret |
required | HMAC key for macaroon signing. Rotate periodically. |
lnbitsUrl |
required | LNBits base URL. |
lnbitsApiKey |
required | LNBits invoice/read key. NEVER pass admin key. |
satsAmount |
10 | Invoice amount per call. |
minScore |
10 | Reject paid callers below this composite score. |
failClosed |
true | If oracle errors, reject the call. Set false to fall through with req.doiScoreError. |
oracleUrl |
https://identity.powforge.dev | Override for self-hosted oracles. |
scope |
mcp-l402-gate:call | L402 macaroon scope. |
ttlSeconds |
600 | Macaroon validity. |
scoreField |
composite | Which envelope field to compare to minScore. |
callerPubkeyHeader |
x-caller-pubkey | HTTP header carrying the caller's asserted pubkey. |
oracleAuth |
optional | {macaroon, preimage} if your oracle is itself L402-paywalled. |
createInvoiceFn |
optional | Test seam. Async (memo) => {payment_hash, bolt11}. |
checkPaidFn |
optional | Test seam. Async (payment_hash) => boolean. |
lookupScoreFn |
optional | Test seam. Async (pubkey) => {composite, rank, depth}. |
Score thresholds (composite)
Same buckets the oracle reports as rank:
| Threshold | Rank | Use it when |
|---|---|---|
| 0 | unknown | You only want pay-to-call. Skip this package and use L402 directly. |
| 10 | emerging | First-call abuse hurts. Default for most public MCP tools. |
| 40 | active | The tool burns real GPU or has expensive side effects. |
| 100 | established | Compliance-sensitive or single-tenant SaaS-style endpoints. |
| 200 | trusted | High-trust admin tooling. |
Failure modes
| Status | Body | Meaning |
|---|---|---|
| 402 | {error: "payment required", macaroon, invoice, payment_hash} |
First call. Pay the invoice, retry with Authorization: L402 <macaroon>:<preimage>. |
| 401 | {error: "invalid macaroon", reason} |
Macaroon malformed, expired, wrong scope, or wrong signature. |
| 401 | {error: "preimage does not match payment hash"} |
Preimage failed sha256 check against the macaroon's payment hash. |
| 409 | {error: "macaroon already redeemed"} |
Replay guard fired. Mint a fresh macaroon. |
| 400 | {error: "caller_pubkey_required"} |
No X-Caller-Pubkey header or ?pubkey= query. |
| 403 | {error: "score_too_low", score, min, rank} |
Caller paid but DoI score is below threshold. |
| 503 | {error: "oracle_unavailable", mode: "fail_closed"} |
Oracle error and failClosed is on (the default). |
| 502 | {error: "invoice provider unavailable"} |
LNBits unreachable on first-call mint. |
Why this is a separate package
The L402 macaroon mint and verify code, the LNBits client, and the oracle client are all already shipping inside other PowForge packages. The point of @powforge/mcp-l402-gate is to make the composition trivial: one factory, one config object, one middleware OR one tool wrapper. Operators do not have to assemble three packages by hand to get a defended endpoint.
Tests
npm test
16 unit tests, no real network. The macaroon HMAC is real; LNBits and oracle are stubbed.
License
MIT.
Links
- PowForge oracle (live): https://identity.powforge.dev
- Identity SDK: @powforge/identity on npm
- MCP identity tools: @powforge/mcp-identity on npm
Install
Add mcp l402 gate to your client. Pick the one you use.
claude mcp add mcp-l402-gate -- npx -y @powforge/mcp-l402-gatecodex mcp add mcp-l402-gate -- npx -y @powforge/mcp-l402-gateamp mcp add mcp-l402-gate -- npx -y @powforge/mcp-l402-gate{
"mcpServers": {
"mcp-l402-gate": {
"command": "npx",
"args": [
"-y",
"@powforge/mcp-l402-gate"
]
}
}
}Add to `claude_desktop_config.json`, then restart Claude Desktop.
{
"mcpServers": {
"mcp-l402-gate": {
"command": "npx",
"args": [
"-y",
"@powforge/mcp-l402-gate"
]
}
}
}Add to `~/.cursor/mcp.json`, or `.cursor/mcp.json` for a single project.
code --add-mcp '{"name":"mcp-l402-gate","command":"npx","args":["-y","@powforge/mcp-l402-gate"]}'Or add the block manually to `.vscode/mcp.json` under `servers`.
{
"mcpServers": {
"mcp-l402-gate": {
"command": "npx",
"args": [
"-y",
"@powforge/mcp-l402-gate"
]
}
}
}Add to `~/.codeium/windsurf/mcp_config.json`.
{
"mcpServers": {
"mcp-l402-gate": {
"command": "npx",
"args": [
"-y",
"@powforge/mcp-l402-gate"
]
}
}
}Add to `cline_mcp_settings.json` via the MCP Servers panel.
{
"mcpServers": {
"mcp-l402-gate": {
"command": "npx",
"args": [
"-y",
"@powforge/mcp-l402-gate"
]
}
}
}Add to `~/.gemini/settings.json`.
{
"mcpServers": {
"mcp-l402-gate": {
"type": "local",
"command": "npx",
"args": [
"-y",
"@powforge/mcp-l402-gate"
],
"tools": [
"*"
]
}
}
}Add to `~/.copilot/mcp-config.json`, or run `/mcp add` inside the CLI.
{
"context_servers": {
"mcp-l402-gate": {
"command": {
"path": "npx",
"args": [
"-y",
"@powforge/mcp-l402-gate"
]
}
}
}
}Add to your Zed `settings.json`.
npx -y @powforge/mcp-l402-gateRun `goose configure`, choose **Add Extension → Command-line Extension**, and paste this command.
Score
39 / 100
Incomplete
- Documentation25/25
- Maintenance13/25
- Trust6/20
- Capability0/15
- Install experience12/15
- Documents what it does and how to connect
- Has a resolvable package or endpoint
- Exposes at least one tool, prompt or resource
- README has substantive content
- Includes a code example
- Documents its configuration
- Mentions credentials or security posture
- Last commit 114 days ago
- Has a release history
- Repository is not archived
- No licence detected
- Namespace verified in the official MCP registry
- Claimed by its owner
- Published under an organisation
- 0 tool(s) documented
- Provides prompt templates
- Provides resources
- 12 documented install method(s)
- Published to a package registry
- Offers a hosted endpoint — no local install
Version history
| Versions | Published |
|---|---|
| 0.1.4Latest | May 10, 2026 |