DEV Community

Zeke
Zeke

Posted on

Three ways to gate an MCP server: OAuth, L402, and proof-of-work

Somebody at Sentry filed a bug last month: Cursor Automations started hitting rate-limit errors almost immediately after authenticating. The bucket was sized for humans — 60 requests per 60 seconds — and an agent tore through it in seconds.

That's the MCP auth problem in miniature. You've got a server exposing tools. Agents call those tools. You want to slow down abuse, charge per call, or just make sure you don't blow up your LLM budget on some runaway loop. How do you do that without wiring up a full OAuth stack that breaks the first time an agent doesn't have a browser to open?

Three real options exist right now. Here's how they compare.

Option 1: OAuth 2.1 (the spec says so)

The MCP spec mandates OAuth 2.1 for authorization. If you're building a production server for enterprise customers — actual humans with accounts — this is the right call. You get scoped access, token revocation, audit trails. SSO works. Compliance teams stop emailing you.

The problem is agents. OAuth 2.1 has an authorization code flow that requires a redirect URI. An agent running headless doesn't have a browser. DPoP (Demonstrating Proof of Possession) and Workload Identity Federation are on the MCP roadmap but not shipped yet. If you need auth today and your callers are mostly agents, OAuth puts you in a hole.

Good fit: enterprise SaaS, human-driven clients, compliance-heavy contexts.
Bad fit: anonymous agents, public APIs, pay-per-call services.

Option 2: L402 Lightning payments

L402 is an HTTP extension — originally from Lightning Labs — where a server responds to an unauthorized request with 402 Payment Required and an invoice in the WWW-Authenticate header. The client pays it over Lightning and retries with the preimage as a credential.

Two npm packages ship L402 for MCP right now: lightning-wallet-mcp and l402-kit-mcp. The model is clean: each tool call costs a fixed number of sats. No accounts, no sessions, no user. An agent with a Lightning wallet (Alby, Phoenixd, NWC) can handle the whole flow programmatically.

# First call — server responds with 402
curl -X POST https://your-mcp-server/tools/call \
  -H "Content-Type: application/json" \
  -d '{"name": "expensive_tool", "arguments": {}}'
# → 402 Payment Required
# → WWW-Authenticate: L402 invoice="lnbc...", macaroon="..."

# Pay the invoice, get the preimage
# Retry with credentials
curl -X POST https://your-mcp-server/tools/call \
  -H "Authorization: L402 <macaroon>:<preimage>" \
  -H "Content-Type: application/json" \
  -d '{"name": "expensive_tool", "arguments": {}}'
# → 200 OK
Enter fullscreen mode Exit fullscreen mode

Good fit: agents with Lightning wallets, micropayment-per-call APIs, services that want Bitcoin-native monetization.
Bad fit: agents without wallets, free tiers, contexts where a payment friction point would kill adoption.

Option 3: Proof-of-work

PoW is the weird one. Instead of paying money, the caller burns CPU to solve a hashcash-style puzzle. The server sets a difficulty. The caller grinds until they find a nonce. No wallet required.

# Get a challenge
curl https://your-mcp-server/api/challenge
# → {"challenge": "abc123", "difficulty": 4, "algorithm": "sha256"}

# Grind the nonce (this is what @powforge/captcha does)
npx @powforge/captcha solve --challenge abc123 --difficulty 4
# → {"nonce": "7f3a...", "solution": "0000..."}

# Submit with the solution
curl -X POST https://your-mcp-server/tools/call \
  -H "X-PoW-Solution: challenge=abc123,nonce=7f3a..." \
  -H "Content-Type: application/json" \
  -d '{"name": "tool", "arguments": {}}'
Enter fullscreen mode Exit fullscreen mode

The cost scales with difficulty. A legitimate caller solving once is fine. An abuser trying to call 10,000 times hits a wall because each call requires fresh compute. No central authority. No wallet. No redirect.

@powforge/captcha-mcp ships this as a drop-in middleware for existing MCP servers. You wrap your server, set a difficulty, and callers need to solve before tool calls go through. It also supports L402 as an escape valve — pay sats instead of grinding if you'd rather.

This is also a 429 fix. Instead of returning 429 Too Many Requests with no recovery path, the server hands the agent a puzzle — a machine-readable backoff signal an autonomous agent can satisfy without a human, an account, or a Retry-After header that means nothing to code running in a loop.

Good fit: anonymous agents, public APIs, abuse prevention without monetization, PoW-or-pay dual rail.
Bad fit: high-throughput legitimate callers (they'll hate the CPU cost), real-time tools where latency matters.

Putting it together

OAuth 2.1 L402 Lightning Proof-of-Work
Requires user Yes No No
Requires wallet No Yes No
Works headless Partial Yes Yes
Revenue model Subscription/SaaS Per-call sats N/A (cost-of-abuse)
Stateless No Yes Yes
Ship today Yes Yes Yes

An 88% stat from an Astrix Security study this year: that many MCP servers require credentials of some kind but document almost none of it. The OpenClaw scan found 42,000+ publicly exposed MCP instances with no auth at all. Most of those aren't enterprise installs — they're side projects and weekend experiments.

For that audience, OAuth is overkill. L402 is elegant but needs a wallet. PoW gives you friction-without-friction — callers can always get through, they just can't do it for free at scale.

The practical decision tree

  • Building for enterprise with human users? OAuth 2.1. The spec points here and compliance needs it.
  • Monetizing per tool call, callers have Lightning wallets? L402. Clean, stateless, Bitcoin-native.
  • Public API, no accounts, agents unknown? PoW — or PoW with L402 escape valve. @powforge/captcha-mcp ships both.
  • Genuinely unsure? Start with PoW for the free tier and L402 for a paid tier. You can add OAuth later when you have enterprise customers asking for it.

The Sentry rate-limit bug is fixable with any of these. But if you build it for agents first, OAuth is the last tool you reach for — not the first.


@powforge/captcha-mcp: https://www.npmjs.com/package/@powforge/captcha-mcp
@powforge/captcha (standalone): https://www.npmjs.com/package/@powforge/captcha

Top comments (0)