{
  "markdown": "# @powforge/mcp-l402-gate\n\n[![npm](https://img.shields.io/npm/v/@powforge/mcp-l402-gate.svg)](https://www.npmjs.com/package/@powforge/mcp-l402-gate)\n[![license](https://img.shields.io/badge/license-MIT-blue.svg)](#license)\n[![node](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg)](#requirements)\n\nIdentity-scored Lightning paywall for MCP server operators.\n\nL402 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.\n\nThis 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.\n\n## See it in action\n\nA clone-and-run example server lives at [github.com/zekebuilds-lab/mcp-l402-gate-example](https://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.\n\n## The Gap\n\nSats4AI's own documentation states the limitation plainly:\n\n> \"autonomous agents cannot build reputation or receive preferential treatment across sessions.\"\n\n`@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.\n\n## Why not just L402\n\nL402 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.\"\n\nPowForge 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.\n\n## Requirements\n\n- Node >= 18\n- An LNBits wallet (URL + invoice/read API key) for the Lightning side\n- An accessible PowForge oracle URL (default: `https://identity.powforge.dev`)\n\n## 5-line integration (Express)\n\n```js\nconst express = require('express');\nconst { mcpL402Middleware } = require('@powforge/mcp-l402-gate');\n\nconst app = express();\napp.use('/tools/expensive', mcpL402Middleware({\n  secret: process.env.GATE_HMAC_SECRET,\n  lnbitsUrl: process.env.LNBITS_URL,\n  lnbitsApiKey: process.env.LNBITS_INVOICE_KEY,\n  satsAmount: 10,\n  minScore: 10, // composite >= 10 means \"emerging\" tier on the oracle\n}));\n\napp.post('/tools/expensive', (req, res) => {\n  // Reached only when L402 paid AND req.doiScore >= 10\n  res.json({ ok: true, doiScore: req.doiScore, l402: req.l402Token });\n});\n```\n\nThe 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.\n\n## MCP tool wrapping\n\n```js\nconst { mcpL402Tool } = require('@powforge/mcp-l402-gate');\n\nconst expensiveTool = mcpL402Tool({\n  secret: process.env.GATE_HMAC_SECRET,\n  lnbitsUrl: process.env.LNBITS_URL,\n  lnbitsApiKey: process.env.LNBITS_INVOICE_KEY,\n  satsAmount: 10,\n  minScore: 10,\n}, {\n  name: 'image_render',\n  description: 'Render an image. 10 sats. Requires DoI score >= 10.',\n  inputSchema: {\n    type: 'object',\n    properties: {\n      prompt: { type: 'string' },\n      pubkey: { type: 'string' },\n      auth: { type: 'object', properties: { macaroon: { type: 'string' }, preimage: { type: 'string' } } },\n    },\n    required: ['prompt', 'pubkey'],\n  },\n}, async (args, ctx) => {\n  // Runs only when paid AND ctx.doiScore >= 10\n  return { image_url: `https://example/r/${args.prompt}`, billed_to: ctx.doiScore };\n});\n\n// Register expensiveTool with your MCP server. On first call without args.auth,\n// the tool returns { paid: false, challenge: { macaroon, invoice, ... } }.\n// The MCP client pays the invoice, then re-calls with args.auth set.\n```\n\n## Config reference\n\n| Field | Default | Notes |\n|-------|---------|-------|\n| `secret` | required | HMAC key for macaroon signing. Rotate periodically. |\n| `lnbitsUrl` | required | LNBits base URL. |\n| `lnbitsApiKey` | required | LNBits invoice/read key. NEVER pass admin key. |\n| `satsAmount` | 10 | Invoice amount per call. |\n| `minScore` | 10 | Reject paid callers below this composite score. |\n| `failClosed` | true | If oracle errors, reject the call. Set false to fall through with `req.doiScoreError`. |\n| `oracleUrl` | https://identity.powforge.dev | Override for self-hosted oracles. |\n| `scope` | mcp-l402-gate:call | L402 macaroon scope. |\n| `ttlSeconds` | 600 | Macaroon validity. |\n| `scoreField` | composite | Which envelope field to compare to minScore. |\n| `callerPubkeyHeader` | x-caller-pubkey | HTTP header carrying the caller's asserted pubkey. |\n| `oracleAuth` | optional | `{macaroon, preimage}` if your oracle is itself L402-paywalled. |\n| `createInvoiceFn` | optional | Test seam. Async `(memo) => {payment_hash, bolt11}`. |\n| `checkPaidFn` | optional | Test seam. Async `(payment_hash) => boolean`. |\n| `lookupScoreFn` | optional | Test seam. Async `(pubkey) => {composite, rank, depth}`. |\n\n## Score thresholds (composite)\n\nSame buckets the oracle reports as `rank`:\n\n| Threshold | Rank | Use it when |\n|-----------|------|-------------|\n| 0 | unknown | You only want pay-to-call. Skip this package and use L402 directly. |\n| 10 | emerging | First-call abuse hurts. Default for most public MCP tools. |\n| 40 | active | The tool burns real GPU or has expensive side effects. |\n| 100 | established | Compliance-sensitive or single-tenant SaaS-style endpoints. |\n| 200 | trusted | High-trust admin tooling. |\n\n## Failure modes\n\n| Status | Body | Meaning |\n|--------|------|---------|\n| 402 | `{error: \"payment required\", macaroon, invoice, payment_hash}` | First call. Pay the invoice, retry with `Authorization: L402 <macaroon>:<preimage>`. |\n| 401 | `{error: \"invalid macaroon\", reason}` | Macaroon malformed, expired, wrong scope, or wrong signature. |\n| 401 | `{error: \"preimage does not match payment hash\"}` | Preimage failed sha256 check against the macaroon's payment hash. |\n| 409 | `{error: \"macaroon already redeemed\"}` | Replay guard fired. Mint a fresh macaroon. |\n| 400 | `{error: \"caller_pubkey_required\"}` | No `X-Caller-Pubkey` header or `?pubkey=` query. |\n| 403 | `{error: \"score_too_low\", score, min, rank}` | Caller paid but DoI score is below threshold. |\n| 503 | `{error: \"oracle_unavailable\", mode: \"fail_closed\"}` | Oracle error and `failClosed` is on (the default). |\n| 502 | `{error: \"invoice provider unavailable\"}` | LNBits unreachable on first-call mint. |\n\n## Why this is a separate package\n\nThe 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.\n\n## Tests\n\n```\nnpm test\n```\n\n16 unit tests, no real network. The macaroon HMAC is real; LNBits and oracle are stubbed.\n\n## License\n\nMIT.\n\n## Links\n\n- PowForge oracle (live): https://identity.powforge.dev\n- Identity SDK: [@powforge/identity](https://www.npmjs.com/package/@powforge/identity) on npm\n- MCP identity tools: [@powforge/mcp-identity](https://www.npmjs.com/package/@powforge/mcp-identity) on npm\n",
  "bytes": 8175,
  "sha": "384bee90c1f5c5346ffee3b1e666d9e481f2b13555a5c721d4d2c7253c6a20ca",
  "repo_slug": "zekebuilds-lab/mcp-l402-gate",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_zekebuilds_lab_mcp_l402_gate_47e7c318/readme"
}