Building a production MCP server on Cloudflare Workers (with auth + Stripe)

Most "build an MCP server" tutorials in May 2026 show you npx create-mcp and a Hello, world tool. That works for a demo. It does not work for anything you'd expose to the public internet, because the moment your server can do anything useful โ€” call an LLM, query a database, hit a paid API โ€” random people will drain your bill.

A production MCP server in 2026 needs five things the templates skip: authentication, per-user quotas, billing, observability, and edge deployment. This is the architecture we use for ask-meridian.uk โ€” a live MCP server in the official MCP Registry โ€” and it costs $0/month in hosting until you cross 100k requests/day.

The architecture in one diagram

   Claude Code / Cursor / Cline / Windsurf
        โ”‚ stdio JSON-RPC
        โ–ผ
   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
   โ”‚ npm-installable shim                  โ”‚  ~5 KB JavaScript on the user's
   โ”‚ (forwards every call over HTTPS)      โ”‚  laptop, fetched via npx -y
   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                โ”‚ HTTPS POST + Authorization: Bearer โ€ฆ
                โ–ผ
   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
   โ”‚ Cloudflare Worker / Pages Function    โ”‚  validates bearer, checks
   โ”‚                                       โ”‚  quota, calls upstream LLM
   โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
        โ–ผ                  โ–ผ
   Cloudflare KV      Stripe (Checkout +
   key:HASH           Customer Portal +
   monthly:HASH:YM    webhooks โ†’ /api/stripe/webhook)
   free:IP:DAY

Three boxes, all free or near-free. Cloudflare Workers free tier covers 100k requests/day. KV covers 100k reads/day + 1k writes/day. Stripe is 2.9% + 30ยข per successful charge โ€” no fixed monthly cost, no per-API-call cost.

Why a fat backend + thin shim

The pattern that wins in 2026 is what we call fat backend + thin shim. The user installs a small npm package locally; that package speaks MCP JSON-RPC on stdio (because that's what their client wants) and forwards every tools/call to your real backend over HTTPS.

Three reasons this beats running everything locally on the user's machine:

The auth flow

Stripe โ†’ Webhook โ†’ KV. When a user pays, Stripe's webhook hits /api/stripe/webhook. We generate a 32-byte random key (mrd_live_โ€ฆ), store its SHA-256 hash in KV under key:HASH, and stash the plain-text key under session:STRIPE_SESSION_ID with a 30-minute TTL.

The user lands on /api/stripe/claim?session_id=โ€ฆ after checkout. We pull the plain-text key once, delete the session entry, and show the key in an HTML page. They save it (we tell them they only see it once). From then on, every request carries Authorization: Bearer mrd_live_โ€ฆ and we look up the SHA-256 in KV.

Why hash? Because if your KV ever gets dumped โ€” insider threat, misconfiguration, SDK bug โ€” the leaked data shouldn't include working credentials. You only ever store the SHA-256.

Per-IP rate limiting on the free tier

For anonymous traffic (no bearer header), we cap at 5-10 requests/day per IP. The pattern is one read + one write to KV per request:

const ip   = request.headers.get('cf-connecting-ip') || 'unknown'
const dkey = `free:${ip}:${new Date().toISOString().slice(0, 10)}`
const used = parseInt((await env.MCP_KV.get(dkey)) || '0', 10)
if (used >= LIMIT) return json({ error: 'free tier exhausted' }, { status: 429 })
await env.MCP_KV.put(dkey, String(used + 1), { expirationTtl: 90000 })

KV doesn't support atomic increments. At 5/day per IP that doesn't matter โ€” racing under-counts by 1 occasionally is fine. At 5/sec it would matter; for that you'd use Durable Objects.

One non-obvious gotcha: residential IPv6. ISPs hand out /64 prefixes; the host bits change every few hours. Per-IPv6-address counting under-counts. The fix is to compute the /64 prefix and use that as the rate-limit key. We do this in _ip.js โ€” about 30 lines.

The cost-of-goods problem

You're charging $29/mo. Your real cost is the LLM API call you make for the user. If a user can run up $50 of LLM cost on their $29 plan, you lose money. The whole game is keeping COGS < revenue per user.

Three levers worth pulling, in order:

  1. Cache aggressively. Same task โ†’ same response, 99% of the time. Hash the input + parameters, cache the LLM output for 24 hours. Repeat queries cost you ~$0.0001 instead of $0.005.
  2. Use Cloudflare AI Gateway. Free observability + caching layer in front of any LLM provider. Set cache_ttl=86400 and identical upstream requests dedupe at the gateway, even when your KV cache misses.
  3. Route to the cheapest provider that meets the quality bar. Workers AI for the bulk (free tier covers a lot), Groq for "needs to be fast" (~$0.0005/call), Anthropic Sonnet only when the task actually needs it (~$0.012/call).

SSE streaming to make 30-second waits feel like 5

An LLM call to Anthropic Sonnet on a complex task can take 20-30 seconds. Users stare at a spinner and abort at 8 seconds. The fix is Server-Sent Events: as the LLM streams tokens to your Worker, you stream progress events back to the user.

The wire format is simple: event: progress\ndata: {...}\n\n. The hard parts are (1) parsing OpenAI-format SSE chunks from the upstream, (2) throttling progress events so you don't flood the client, (3) gracefully handling cancellation when the user closes their tab. About 80 lines of TypeScript total.

Want every line of code, working, in your hands today?

The full step-by-step guide ships the complete Worker (~400 lines), the npm-publishable stdio shim (~150 lines), the Stripe webhook handler with signature verification, the SSE streaming pipeline, the AI Gateway wiring, and a copy-paste MCP Registry submission โ€” plus the exact production gotchas in chapter 9 that you'd otherwise hit live.

Get the guide โ€” $29 โ†’

What we got wrong (so you don't)

Three things that bit us building Meridian, all of which the guide covers:

The end state

A live MCP server that takes real money via Stripe, has per-user quotas, runs on Cloudflare's free tier, and is listed in the official MCP Registry. Total infrastructure cost at ~50 paying users: $0/month. Revenue: $1,450/month.

Meridian itself is the proof โ€” try the live demo at ask-meridian.uk, browse the source at github.com/LuuOW/meridian-mcp. The patterns above are exactly what's in the public repo.

Ship yours in 30 minutes โ†’

Working CF Worker template + stdio shim + 60-page guide for $29. MIT-licensed code, commercial use OK.

Build Your Own MCP Server โ€” $29 โ†’