A drop-in monetization layer for MCP servers. Keep your own endpoint — wrap it, get API keys, per-call metering, and a usage/billing dashboard in minutes.
There are 20,000+ MCP servers in the wild. Fewer than 5% have ever earned a dollar, and ~40% ship with no auth at all — not because nobody wants to charge, but because building an MCP tool takes a weekend; monetizing it takes weeks of billing plumbing: keys, hashing, rate limits, metering, quota enforcement, a dashboard, a payment rail.
ToolMint is the thin dev-first layer that collapses that to a config change. You point it at your MCP/HTTP endpoint (UPSTREAM_URL); it fronts your traffic and adds the parts you didn't want to build. It runs on the same primitives a working paid MCP already needs — a Cloudflare Worker, D1, and the x402 micropayment rail — just productized so you don't re-derive them.
- Reverse proxy — a Worker that faithfully forwards every request (method, path, query, body, headers; response streamed back, so SSE/JSON-RPC pass through) to your configured upstream.
- API-key auth — issue keys (
POST /admin/keys), validate them on every call viaX-API-KeyorAuthorization: Bearer. Unauthed →401. Only a SHA-256 hash is stored, never the raw key. - Per-call metering — every authed call logs
{key_id, path, ts, status, units}to D1. A free-tier monthly quota is enforced per key →402when exceeded (the hook for paid upgrades). - Usage dashboard — a minimal HTML page (
/dashboard) showing calls per key, this-month usage vs quota, and totals — read straight from D1. - Billing hook — a documented seam (
/billing/upgrade) where a settled x402 (or Stripe) payment raises a key's quota. Manual admin top-ups work today; the x402 settlement layer is pre-wired and flips on with one config change.
cd toolmint
npm install
# 1. point ToolMint at YOUR MCP server (edit wrangler.jsonc -> vars.UPSTREAM_URL)
# e.g. "UPSTREAM_URL": "https://my-mcp-server.example.com"
# (leave the default to demo against the built-in echo upstream)
# 2. set a local admin secret
cp .dev.vars.example .dev.vars # then edit ADMIN_SECRET
# 3. create the metering tables in a LOCAL D1
npx wrangler d1 execute toolmint --local --file=./schema.sql
# 4. run it
npx wrangler devThen, in another terminal:
# issue a key (returns api_key ONCE — store it)
curl -s -X POST http://127.0.0.1:8787/admin/keys \
-H "Authorization: Bearer dev-admin-secret-change-me" \
-H "content-type: application/json" \
-d '{"name":"my-first-key"}'
# call your API through ToolMint with that key — metered + quota-gated
curl -s http://127.0.0.1:8787/anything?foo=bar -H "X-API-Key: tm_live_..."
# see the metered call(s)
open http://127.0.0.1:8787/dashboard?key=dev-admin-secret-change-meA call with no key returns 401. Once a key passes its monthly quota, calls return 402 pointing at the upgrade endpoint.
npm run db:create # creates the D1, prints a database_id
# paste that id into wrangler.jsonc -> d1_databases[0].database_id
npm run db:apply:remote # apply schema to the production D1
npx wrangler secret put ADMIN_SECRET # set the real admin secret
npm run deploy| Var | Meaning |
|---|---|
UPSTREAM_URL |
The MCP/HTTP endpoint ToolMint fronts. Set this to your server. |
FREE_MONTHLY_QUOTA |
Calls per key per month before 402. Default 1000. |
HOP_HEADERS |
Comma-separated headers stripped before forwarding. |
SERVICE_NAME |
Display name on the dashboard / manifest. |
X402_ENABLED |
"true" turns on the x402 paywall for quota upgrades. |
X402_NETWORK |
base-sepolia (testnet, free facilitator) or base (mainnet). |
PAY_TO |
Wallet that receives upgrade payments. |
PRICE_PER_UPGRADE |
Price of one quota upgrade, e.g. "$5.00". |
UPGRADE_QUOTA_GRANT |
Calls added to a key's quota per settled upgrade. |
Secrets (never in code — wrangler secret put / .dev.vars): ADMIN_SECRET (required), and on mainnet CDP_API_KEY_ID + CDP_API_KEY_SECRET.
- Free tier:
FREE_MONTHLY_QUOTAcalls/key/month. Enough to integrate and demo. Exceed it →402. - Paid quota: a settled payment raises a key's
monthly_quotabyUPGRADE_QUOTA_GRANT(e.g. $5 → +100k calls). Stack upgrades for more. - This is the standard freemium-for-agents shape: zero-friction trial, pay only when volume justifies it, no signup wall — an agent can settle the upgrade itself over x402.
| Method / path | Auth | Purpose |
|---|---|---|
ANY /* |
API key | Proxied to upstream — metered + quota-gated |
POST /admin/keys |
admin | Issue a key (api_key shown once) |
GET /admin/keys |
admin | List keys + usage (JSON) |
POST /admin/keys/:id/revoke |
admin | Revoke a key |
POST /billing/upgrade |
admin / x402 | Raise a key's quota |
GET /dashboard |
admin | HTML usage dashboard |
GET /health |
free | Health check |
GET /mcp |
free | ToolMint's own MCP manifest |
ANY /__origin |
free | Built-in echo upstream (demo only) |
The grant logic (src/billing.ts → grantUpgrade) is payment-rail agnostic and fully implemented. Two ways to drive it:
Manual / comped (works today, no rail):
curl -X POST http://127.0.0.1:8787/billing/upgrade \
-H "Authorization: Bearer <ADMIN_SECRET>" \
-H "content-type: application/json" \
-d '{"key_id":"<KEY_ID>","quota_add":100000}'x402 (pre-wired stub — flip on):
- In
wrangler.jsonc: setX402_ENABLED: "true",X402_NETWORK, andPAY_TO(your wallet). npm install @coinbase/x402 x402-hono(the dynamic import insrc/billing.tsexpects them; same versions as the x402-starter repo).- On mainnet, set
CDP_API_KEY_ID/CDP_API_KEY_SECRETsecrets (testnet uses the free public facilitator — no keys). - Now an agent can
GET /billing/upgrade?key_id=..., receive the402challenge, payPRICE_PER_UPGRADEin USDC, and the middleware re-runs the handler — which grantsUPGRADE_QUOTA_GRANTcalls and writes abilling_eventsrow. (Same settle-then-mint pattern as agentflow's/v1/credits/buy.)
Stripe (v2, not wired): add POST /billing/stripe/webhook, verify Stripe-Signature against STRIPE_WEBHOOK_SECRET, and on checkout.session.completed call the same grantUpgrade(env, keyId, grant, {source:"stripe", ...}).
- Full billing: only the x402 settlement layer is gated behind
X402_ENABLED; Stripe is a documented seam, not code. No invoices, proration, or auto-renew (x402 is per-call, so "subscriptions" are time-boxed passes — see agentflow for that pattern). npx create-toolmintpackaging: today you clone this repo. v2 = a scaffolder that writeswrangler.jsonc+ secrets from a couple of prompts.- Hosted multi-tenant: this is single-tenant (one
UPSTREAM_URLper deploy). v2 = a control plane where many owners register many upstreams under one ToolMint. - Rate limiting / burst control: only monthly quota today; no per-second limits.
- Usage-based (token) billing: metering already records
units(honors an upstreamX-Units/X-Tokens-Usedheader), but quota is enforced on call count, not units.
schema.sql — api_keys (id, key_hash, key_prefix, name, monthly_quota, revoked, timestamps), usage_events (key_id, ts, month, path, method, status, units, duration_ms), billing_events (audit of upgrades). A mirror lives in migrations/0001_init.sql for the wrangler d1 migrations apply workflow.
toolmint/
├── src/
│ ├── index.ts # Worker: proxy + auth + metering + quota + admin + billing routes
│ ├── db.ts # D1: key issuance/validation, metering, dashboard reads
│ ├── billing.ts # the billing seam — grantUpgrade + x402 paywall stub
│ ├── dashboard.ts # minimal HTML usage dashboard
│ └── types.ts # Env bindings
├── schema.sql # D1 tables (apply with wrangler d1 execute)
├── migrations/0001_init.sql
├── wrangler.jsonc
├── package.json
├── .dev.vars.example # copy to .dev.vars for local secrets
└── README.md
Built on the same primitives as x402-starter (the drop-in paywall) and agentflow (live x402 + D1 metering) — ToolMint productizes that stack for any MCP server.