Monetize any MCP server in 10 minutes — no billing code required A developer released MCP Billing Gateway, an open-source reverse proxy that adds Stripe subscriptions, per-call credits, and crypto payments to any MCP server without modifying the server code. The tool handles authentication, usage tracking, and billing logic, allowing developers to monetize their MCP servers in minutes instead of weeks. You built an MCP server. It works. AI agents call it. But you're paying for compute and API calls out of pocket. Adding billing to an MCP server usually means: Stripe integration, API key management, usage tracking, free tier logic, subscription management, webhook handling. That's 2–4 weeks of work that has nothing to do with the tool you actually built. Most operators give up and leave money on the table. MCP Billing Gateway https://github.com/sapph1re/mcp-billing-gateway-sdk solves this. It's a reverse proxy that sits in front of your MCP server and handles all billing — Stripe subscriptions, per-call credits, and x402 crypto payments. Your server stays unchanged. AI Agent caller │ ├─ Authorization: Bearer cgwcl ... │ ▼ ┌──────────────────────────┐ │ mcp-billing-gateway │ │ │ │ 1. Authenticate caller │ │ 2. Check credits/sub │ │ 3. Debit payment │ │ 4. Forward JSON-RPC │ │ 5. Record usage │ │ 6. Return response │ └──────────┬───────────────┘ │ ▼ ┌──────────────────────────┐ │ Your MCP Server │ │ unchanged, untouched │ └──────────────────────────┘ Your MCP server never sees billing logic. If a caller has no credits, they get a 402 Payment Required before your server is even contacted. Here's a minimal MCP server with two text-analysis tools. Plain Express, no frameworks: python // server.js import express from "express"; const app = express ; app.use express.json ; const tools = { word count: { description: "Count words, characters, and sentences in text", inputSchema: { type: "object", properties: { text: { type: "string" } }, required: "text" , }, handler { text } { return { words: text.split /\s+/ .filter Boolean .length, characters: text.length, sentences: text.split / . ? +/ .filter Boolean .length, }; }, }, text transform: { description: "Transform text: uppercase, lowercase, titlecase, reverse", inputSchema: { type: "object", properties: { text: { type: "string" }, mode: { type: "string", enum: "uppercase", "lowercase", "titlecase", "reverse" }, }, required: "text", "mode" , }, handler { text, mode } { const transforms = { uppercase: = text.toUpperCase , lowercase: = text.toLowerCase , titlecase: = text.replace /\w\S /g, w = w.charAt 0 .toUpperCase + w.slice 1 .toLowerCase , reverse: = text.split "" .reverse .join "" , }; return transforms mode ; }, }, }; function handleJsonRpc { id, method, params } { if method === "initialize" return { jsonrpc: "2.0", id, result: { protocolVersion: "2025-03-26", capabilities: { tools: {} }, serverInfo: { name: "word-tools", version: "1.0.0" } } }; if method === "tools/list" return { jsonrpc: "2.0", id, result: { tools: Object.entries tools .map name, t = { name, description: t.description, inputSchema: t.inputSchema } } }; if method === "tools/call" { const tool = tools params?.name ; if tool return { jsonrpc: "2.0", id, error: { code: -32601, message: Unknown tool: ${params?.name} } }; const result = tool.handler params.arguments || {} ; return { jsonrpc: "2.0", id, result: { content: { type: "text", text: typeof result === "string" ? result : JSON.stringify result } } }; } return { jsonrpc: "2.0", id, error: { code: -32601, message: Method not found: ${method} } }; } app.post "/mcp", req, res = res.json handleJsonRpc req.body ; app.listen 4000, = console.log "MCP server on http://localhost:4000/mcp" ; Test it works: curl -X POST http://localhost:4000/mcp \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"word count","arguments":{"text":"Hello world from MCP"}}}' → {"jsonrpc":"2.0","id":1,"result":{"content": {"type":"text","text":"{\"words\":4,\"characters\":20,\"sentences\":1}"} }} Anyone can call it for free. Let's fix that. curl -X POST https://mcp-billing-gateway-production.up.railway.app/api/v1/operator/register \ -H "Content-Type: application/json" \ -d '{"email": "you@example.com", "name": "Your Name"}' { "operator id": "op 01KW...", "api key": "cgwop abcd1234..." } Save the api key . Visit the stripe onboard url in the full response to connect your Stripe account for payouts — takes about 2 minutes. curl -X POST https://mcp-billing-gateway-production.up.railway.app/api/v1/operator/servers \ -H "Authorization: Bearer YOUR OPERATOR KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Word Tools", "upstream url": "https://your-server.example.com/mcp", "proxy slug": "word-tools", "auth mode": "header", "upstream auth token": "your-server-secret", "is public": true }' Your server is now proxied at: https://mcp-billing-gateway-production.up.railway.app/proxy/word-tools The upstream auth token is your server's own secret — callers never see it. The gateway injects it when forwarding requests to your upstream. Per-call pay-as-you-go : curl -X POST https://mcp-billing-gateway-production.up.railway.app/api/v1/operator/servers/SERVER ID/plans \ -H "Authorization: Bearer YOUR OPERATOR KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Pay-as-you-go", "billing model": "per call", "price per call usd micro": 10000, "free calls per month": 100, "is default": true }' $0.01 per call prices are in micro-units: 10,000 = $0.01 , with 100 free calls/month. The free tier resets monthly. Or subscription: -d '{"name": "Pro", "billing model": "subscription", "subscription price usd cents": 999, "subscription interval": "monthly", "subscription call limit": 10000}' Or tiered: -d '{"name": "Volume", "billing model": "tiered", "tiers": {"up to": 1000, "price usd micro": 0}, {"up to": 10000, "price usd micro": 5000}, {"up to": null, "price usd micro": 10000} }' First 1,000 calls free, next 9,000 at $0.005, everything above at $0.01. Callers register and get an API key: curl -X POST https://mcp-billing-gateway-production.up.railway.app/api/v1/caller/register \ -H "Content-Type: application/json" \ -d '{"email": "agent@example.com", "name": "My AI Agent"}' Then call through the proxy: curl -X POST https://mcp-billing-gateway-production.up.railway.app/proxy/word-tools \ -H "Authorization: Bearer CALLER API KEY" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"word count","arguments":{"text":"Hello world"}}}' Same JSON-RPC response — but now it's billed. After 100 free calls, each call costs $0.01 from their credit balance. For Claude Desktop or Cursor, callers add to their MCP config: { "mcpServers": { "word-tools": { "url": "https://mcp-billing-gateway-production.up.railway.app/proxy/word-tools/mcp", "headers": { "Authorization": "Bearer CALLER API KEY" } } } } For agent-to-agent transactions, the gateway also supports x402 https://www.x402.org/ — USDC on Base chain. No API key required. The caller sends a payment claim header: curl -X POST https://mcp-billing-gateway-production.up.railway.app/proxy/word-tools \ -H "X-402-Payment: