{"slug": "monetize-any-mcp-server-in-10-minutes-no-billing-code-required", "title": "Monetize any MCP server in 10 minutes — no billing code required", "summary": "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.", "body_md": "You built an MCP server. It works. AI agents call it. But you're paying for compute and API calls out of pocket.\n\nAdding 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.\n\n[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.\n\n```\nAI Agent (caller)\n    │\n    ├─ Authorization: Bearer cgwcl_...\n    │\n    ▼\n┌──────────────────────────┐\n│   mcp-billing-gateway    │\n│                          │\n│  1. Authenticate caller  │\n│  2. Check credits/sub    │\n│  3. Debit payment        │\n│  4. Forward JSON-RPC     │\n│  5. Record usage         │\n│  6. Return response      │\n└──────────┬───────────────┘\n           │\n           ▼\n┌──────────────────────────┐\n│  Your MCP Server         │\n│  (unchanged, untouched)  │\n└──────────────────────────┘\n```\n\nYour MCP server never sees billing logic. If a caller has no credits, they get a `402 Payment Required`\n\nbefore your server is even contacted.\n\nHere's a minimal MCP server with two text-analysis tools. Plain Express, no frameworks:\n\n``` python\n// server.js\nimport express from \"express\";\n\nconst app = express();\napp.use(express.json());\n\nconst tools = {\n  word_count: {\n    description: \"Count words, characters, and sentences in text\",\n    inputSchema: {\n      type: \"object\",\n      properties: { text: { type: \"string\" } },\n      required: [\"text\"],\n    },\n    handler({ text }) {\n      return {\n        words: text.split(/\\s+/).filter(Boolean).length,\n        characters: text.length,\n        sentences: text.split(/[.!?]+/).filter(Boolean).length,\n      };\n    },\n  },\n  text_transform: {\n    description: \"Transform text: uppercase, lowercase, titlecase, reverse\",\n    inputSchema: {\n      type: \"object\",\n      properties: {\n        text: { type: \"string\" },\n        mode: { type: \"string\", enum: [\"uppercase\", \"lowercase\", \"titlecase\", \"reverse\"] },\n      },\n      required: [\"text\", \"mode\"],\n    },\n    handler({ text, mode }) {\n      const transforms = {\n        uppercase: () => text.toUpperCase(),\n        lowercase: () => text.toLowerCase(),\n        titlecase: () => text.replace(/\\w\\S*/g, w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()),\n        reverse: () => text.split(\"\").reverse().join(\"\"),\n      };\n      return transforms[mode]();\n    },\n  },\n};\n\nfunction handleJsonRpc({ id, method, params }) {\n  if (method === \"initialize\")\n    return { jsonrpc: \"2.0\", id, result: { protocolVersion: \"2025-03-26\", capabilities: { tools: {} }, serverInfo: { name: \"word-tools\", version: \"1.0.0\" } } };\n  if (method === \"tools/list\")\n    return { jsonrpc: \"2.0\", id, result: { tools: Object.entries(tools).map(([name, t]) => ({ name, description: t.description, inputSchema: t.inputSchema })) } };\n  if (method === \"tools/call\") {\n    const tool = tools[params?.name];\n    if (!tool) return { jsonrpc: \"2.0\", id, error: { code: -32601, message: `Unknown tool: ${params?.name}` } };\n    const result = tool.handler(params.arguments || {});\n    return { jsonrpc: \"2.0\", id, result: { content: [{ type: \"text\", text: typeof result === \"string\" ? result : JSON.stringify(result) }] } };\n  }\n  return { jsonrpc: \"2.0\", id, error: { code: -32601, message: `Method not found: ${method}` } };\n}\n\napp.post(\"/mcp\", (req, res) => res.json(handleJsonRpc(req.body)));\napp.listen(4000, () => console.log(\"MCP server on http://localhost:4000/mcp\"));\n```\n\nTest it works:\n\n```\ncurl -X POST http://localhost:4000/mcp \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"word_count\",\"arguments\":{\"text\":\"Hello world from MCP\"}}}'\n# → {\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"{\\\"words\\\":4,\\\"characters\\\":20,\\\"sentences\\\":1}\"}]}}\n```\n\nAnyone can call it for free. Let's fix that.\n\n```\ncurl -X POST https://mcp-billing-gateway-production.up.railway.app/api/v1/operator/register \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\": \"you@example.com\", \"name\": \"Your Name\"}'\n{\n  \"operator_id\": \"op_01KW...\",\n  \"api_key\": \"cgwop_abcd1234...\"\n}\n```\n\nSave the `api_key`\n\n. Visit the `stripe_onboard_url`\n\nin the full response to connect your Stripe account for payouts — takes about 2 minutes.\n\n```\ncurl -X POST https://mcp-billing-gateway-production.up.railway.app/api/v1/operator/servers \\\n  -H \"Authorization: Bearer YOUR_OPERATOR_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\": \"Word Tools\",\n    \"upstream_url\": \"https://your-server.example.com/mcp\",\n    \"proxy_slug\": \"word-tools\",\n    \"auth_mode\": \"header\",\n    \"upstream_auth_token\": \"your-server-secret\",\n    \"is_public\": true\n  }'\n```\n\nYour server is now proxied at:\n\n```\nhttps://mcp-billing-gateway-production.up.railway.app/proxy/word-tools\n```\n\nThe `upstream_auth_token`\n\nis your server's own secret — callers never see it. The gateway injects it when forwarding requests to your upstream.\n\n**Per-call (pay-as-you-go):**\n\n```\ncurl -X POST https://mcp-billing-gateway-production.up.railway.app/api/v1/operator/servers/SERVER_ID/plans \\\n  -H \"Authorization: Bearer YOUR_OPERATOR_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\": \"Pay-as-you-go\",\n    \"billing_model\": \"per_call\",\n    \"price_per_call_usd_micro\": 10000,\n    \"free_calls_per_month\": 100,\n    \"is_default\": true\n  }'\n```\n\n$0.01 per call (prices are in micro-units: 10,000 = $0.01), with 100 free calls/month. The free tier resets monthly.\n\n**Or subscription:**\n\n```\n-d '{\"name\": \"Pro\", \"billing_model\": \"subscription\", \"subscription_price_usd_cents\": 999, \"subscription_interval\": \"monthly\", \"subscription_call_limit\": 10000}'\n```\n\n**Or tiered:**\n\n```\n-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}]}'\n```\n\nFirst 1,000 calls free, next 9,000 at $0.005, everything above at $0.01.\n\nCallers register and get an API key:\n\n```\ncurl -X POST https://mcp-billing-gateway-production.up.railway.app/api/v1/caller/register \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\": \"agent@example.com\", \"name\": \"My AI Agent\"}'\n```\n\nThen call through the proxy:\n\n```\ncurl -X POST https://mcp-billing-gateway-production.up.railway.app/proxy/word-tools \\\n  -H \"Authorization: Bearer CALLER_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"word_count\",\"arguments\":{\"text\":\"Hello world\"}}}'\n```\n\nSame JSON-RPC response — but now it's billed. After 100 free calls, each call costs $0.01 from their credit balance.\n\nFor Claude Desktop or Cursor, callers add to their MCP config:\n\n```\n{\n  \"mcpServers\": {\n    \"word-tools\": {\n      \"url\": \"https://mcp-billing-gateway-production.up.railway.app/proxy/word-tools/mcp\",\n      \"headers\": { \"Authorization\": \"Bearer CALLER_API_KEY\" }\n    }\n  }\n}\n```\n\nFor 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:\n\n```\ncurl -X POST https://mcp-billing-gateway-production.up.railway.app/proxy/word-tools \\\n  -H \"X-402-Payment: <base64-encoded-payment-claim>\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"word_count\",\"arguments\":{\"text\":\"Hello\"}}}'\n```\n\nIf a caller sends a request without valid payment, they get a structured 402 response with pricing info. This means the same server accepts both Stripe credits and USDC payments — whatever the caller supports.\n\n```\ncurl https://mcp-billing-gateway-production.up.railway.app/api/v1/operator/servers/SERVER_ID/usage \\\n  -H \"Authorization: Bearer YOUR_OPERATOR_KEY\"\n{\n  \"total_calls\": 47,\n  \"total_revenue_usd_cents\": 37,\n  \"by_tool\": [\n    {\"tool_name\": \"word_count\", \"calls\": 30, \"revenue_usd_cents\": 20},\n    {\"tool_name\": \"text_transform\", \"calls\": 17, \"revenue_usd_cents\": 17}\n  ]\n}\n```\n\nDefault revenue split: 85/15 — you keep 85%. Request a payout when you're ready:\n\n```\ncurl -X POST https://mcp-billing-gateway-production.up.railway.app/api/v1/operator/payouts/request \\\n  -H \"Authorization: Bearer YOUR_OPERATOR_KEY\"\n```\n\nFunds hit your Stripe account in 1–2 business days. Minimum: $1.00.\n\n| Concern | Without gateway | With gateway |\n|---|---|---|\n| API key management | Build auth system | Built-in (hashed, scoped, revocable) |\n| Stripe integration | Stripe SDK + webhooks | Pre-integrated via Connect |\n| Usage metering | Build tracking | Per-call metering, per-tool breakdown |\n| Subscription management | Handle webhook lifecycle | Automatic period tracking + cancellation |\n| Credit system | Build ledger + balance | Prepaid credits with auto-refund on errors |\n| x402 crypto payments | Implement verification | Built-in USDC validation + anti-replay |\n| Revenue analytics | Build dashboard | Usage by day, tool, billing rail, caller |\n| Payouts | Manual transfers | One API call, automatic splits |\n\nClone and run the full interactive demo:\n\n```\ngit clone https://github.com/sapph1re/mcp-billing-demo.git\ncd mcp-billing-demo\nnpm install && npm start  # Terminal 1: starts the MCP server\nnpm run demo              # Terminal 2: runs all 6 steps against live gateway\n```\n\nThe demo walks through every step with real API calls.\n\n**Live gateway**: [mcp-billing-gateway-production.up.railway.app](https://mcp-billing-gateway-production.up.railway.app)\n\n**SDK docs**: [sapph1re/mcp-billing-gateway-sdk](https://github.com/sapph1re/mcp-billing-gateway-sdk)\n\n**Demo repo**: [sapph1re/mcp-billing-demo](https://github.com/sapph1re/mcp-billing-demo)", "url": "https://wpnews.pro/news/monetize-any-mcp-server-in-10-minutes-no-billing-code-required", "canonical_source": "https://dev.to/sapph1re/monetize-any-mcp-server-in-10-minutes-no-billing-code-required-54hh", "published_at": "2026-07-10 03:14:34+00:00", "updated_at": "2026-07-10 03:36:00.113099+00:00", "lang": "en", "topics": ["developer-tools", "ai-infrastructure", "ai-agents"], "entities": ["MCP Billing Gateway", "Stripe", "GitHub", "sapph1re"], "alternates": {"html": "https://wpnews.pro/news/monetize-any-mcp-server-in-10-minutes-no-billing-code-required", "markdown": "https://wpnews.pro/news/monetize-any-mcp-server-in-10-minutes-no-billing-code-required.md", "text": "https://wpnews.pro/news/monetize-any-mcp-server-in-10-minutes-no-billing-code-required.txt", "jsonld": "https://wpnews.pro/news/monetize-any-mcp-server-in-10-minutes-no-billing-code-required.jsonld"}}