mpp-mcp-gateway
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mpp-mcp-gatewaycall get_weather for San Francisco"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mpp-mcp-gateway
Monetize MCP (Model Context Protocol) tools with stablecoin micropayments via the Machine Payments Protocol (MPP) on Tempo.
What this is
A TypeScript library that lets you:
Build MCP servers whose tools charge stablecoins per call (e.g. $0.001 per API hit)
Build AI agents that pay for those tools automatically — no API keys, no signup, no billing accounts
Both sides use native MCP transports (stdio, SSE, HTTP). Payments settle in <1 second on Tempo's payments-first L1.
┌─────────────┐ MCP tool call ┌──────────────────┐
│ AI Agent │ ─────────────────▶ │ Paid MCP Server │
│ (wallet) │ ◀──────────────────│ (your tools) │
└─────────────┘ 402 challenge └──────────────────┘
│ ▲
│ sign tx + retry │ verify on-chain
▼ │
┌──────────────────────────────────────────┐
│ Tempo Blockchain │
│ (pathUSD / AlphaUSD micropayments) │
└──────────────────────────────────────────┘Why
MCP servers are free today. Anyone can call any tool, so there's no sustainable way to run premium services.
MPP solves machine payments. It's the HTTP 402-based protocol by Stripe × Tempo for agent-to-API payments.
This bridges them. Charge for MCP tool calls in sub-cent stablecoin payments using the HMAC-bound challenge/receipt flow, with automatic on-chain verification.
Install
npm install mpp-mcp-gatewayQuick start
Server — paid weather tool
// server.ts
import { createPaidMcpServer } from 'mpp-mcp-gateway/server'
import { z } from 'zod'
const server = createPaidMcpServer({
name: 'weather',
version: '1.0.0',
recipient: '0xYourWallet',
secretKey: process.env.PAYMENT_SECRET_KEY!,
network: 'testnet',
tools: [
{
name: 'get_weather',
description: 'Get current weather for a city.',
inputSchema: { city: z.string() },
pricing: { type: 'per-call', amount: '0.001' },
handler: async ({ city }) => ({
content: [{ type: 'text', text: `Weather in ${city}: 72°F, clear` }],
}),
},
{
name: 'ping',
description: 'Free liveness check.',
inputSchema: {},
// no pricing → free
handler: async () => ({ content: [{ type: 'text', text: 'pong' }] }),
},
],
})
await server.startStdio()Client — AI agent that pays
// agent.ts
import { createPaidMcpClient } from 'mpp-mcp-gateway/client'
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
const client = createPaidMcpClient({
name: 'my-agent',
version: '1.0.0',
privateKey: process.env.AGENT_PRIVATE_KEY! as `0x${string}`,
maxPerCall: '0.10',
maxTotal: '10.00',
})
await client.connect(new StdioClientTransport({
command: 'node',
args: ['server.js'],
}))
const result = await client.callTool('get_weather', { city: 'San Francisco' })
console.log(result.content[0].text)
console.log('paid:', result.paid)
console.log('tx: ', result.receipt?.reference)Running the bundled examples
1. Typecheck & build
cd mpp-mcp-gateway
npm install
npm run build2. In-memory demo (no subprocess, no testnet funds if server recipient = sender)
npm run example:demoThis spins the server and client up in the same process via InMemoryTransport. It proves the 402 challenge → sign → verify → receipt flow end-to-end.
3. Stdio demo (real subprocess)
npm run example:clientThe client spawns the server as a subprocess, lists tools, calls a free tool, and pays for two paid tools. Each paid call produces a real transaction hash on Tempo testnet.
4. Streamable HTTP demo (modern network transport)
# Terminal 1
npm run example:http:server
# Terminal 2
npm run example:http:clientSingle /mcp endpoint, stateful sessions via the Mcp-Session-Id header. See examples/paid-weather-http/ for curl examples.
5. SSE demo (legacy network transport)
# Terminal 1
npm run example:sse:server
# Terminal 2
npm run example:sse:clientTwo-endpoint shape (GET /sse for the stream, POST /messages?sessionId=… for client messages). Useful when integrating with clients that haven't migrated to Streamable HTTP. See examples/paid-weather-sse/.
6. Session pricing demo (channel-based, sub-100ms per call)
# Terminal 1
npm run example:streaming:server
# Terminal 2
npm run example:streaming:clientThe agent opens an on-chain escrow channel once (~1s), then signs incremental off-chain vouchers per call (~50ms each). Best for streaming or high-frequency tools. See examples/paid-streaming-mcp/.
7. Access-key (subscription) pricing demo
# Terminal 1
npm run example:subscription:server
# Terminal 2
npm run example:subscription:clientPay once, get a key. Subsequent calls present the key instead of paying. The key expires by time (validFor) or by call count (maxCalls) or both. Best for "buy a day pass" or "buy 1000 calls" UX. See examples/paid-subscription-mcp/.
8. Live dashboard
# One-time: build the dashboard UI
cd dashboard && npm install && npm run build && cd ..
# Then start the combined server
npm run example:dashboard:serverBrowse to http://localhost:3010/ for live revenue counters, per-tool breakdowns, and a streaming call log. The same Express app hosts POST/GET /mcp for tool calls and GET /api/{stats,tools,calls} for the dashboard backend. See examples/paid-weather-dashboard/.
Funding the agent wallet
The default demo key (0xac0974...) is the standard Anvil test key #0. Fund any address on Tempo testnet with:
cast rpc tempo_fundAddress 0xYourAddress --rpc-url https://rpc.moderato.tempo.xyzThis airdrops 1M of each testnet stablecoin (pathUSD, AlphaUSD, BetaUSD, ThetaUSD).
API
createPaidMcpServer(config)
Option | Type | Default | Description |
| string | required | Server name advertised to clients |
| string | required | Server version |
|
| required | Wallet receiving payments |
| string | required | HMAC key binding challenges. Keep private. |
|
| pathUSD | TIP-20 token accepted |
|
|
| Which chain to settle on |
|
| required | The paid / free tools to expose |
|
| — | Private key for server-side fee sponsorship |
|
| in-memory | Persistent store for access-key records |
|
| in-memory | Persistent store for session channel state |
| number |
| Ring buffer capacity (set 0 to disable) |
|
| console+redaction | Structured logger instance |
| number |
| Graceful shutdown drain window |
|
| — | Hook fired when |
| object |
| Rate limiting config (see docs) |
|
| — | OpenTelemetry tracer (opt-in) |
|
| — | Webhook endpoint + secret + events |
PaidToolDefinition
{
name: string
description: string
inputSchema: Record<string, z.ZodTypeAny> // Zod shape
pricing?: { type: 'per-call'; amount: '0.01' } // or 'tiered'; omit for free
handler: (args) => Promise<{ content: [...], data?: any }>
}Pricing models
// Flat per-call — one Tempo tx per call (~1s settlement)
{ type: 'per-call', amount: '0.01' }
// Tiered by call count — first N calls cheaper, then ramp
{ type: 'tiered', tiers: [
{ upTo: 100, amount: '0.01' },
{ upTo: 1000, amount: '0.005' },
{ upTo: 'unlimited', amount: '0.001' },
]}
// Session — open one channel, sign vouchers per call (~50ms each)
// Best for streaming or high-frequency tools.
{
type: 'session',
amount: '0.0001', // per-unit price
unitType: 'request', // free-form unit label ('second', 'token', ...)
suggestedDeposit: '0.05', // hint to client about channel funding
minVoucherDelta: '0.0001', // optional anti-dust floor
}
// Access key — pay once, call until the key expires or runs out.
// Best for "day pass" / "1000-call pack" / "subscription" patterns.
{
type: 'access-key',
amount: '1.00', // upfront payment
validFor: '7d', // optional: '60s'/'15m'/'4h'/'7d'/'30d'
maxCalls: 1000, // optional: per-key call cap
// At least one of validFor or maxCalls is required.
}
|
|
| |
First-call latency | ~1s | ~1s (channel open) | ~1s (charge + key issuance) |
Subsequent-call latency | ~1s each | ~50ms each (off-chain voucher) | sub-50ms (no MPP at all) |
On-chain txs per N calls | N | 2 (open + close) | 1 (the upfront charge) |
Best for | Discrete API hits | Streaming, chat-style tools | Subscription / day-pass UX |
Practical floor | ~$0.001 | ~$0.0001 | flat upfront |
You can mix all four pricing modes on the same server. Free tools have no pricing field.
createPaidMcpClient(config)
Option | Type | Default | Description |
| string | required | Client name advertised to servers |
| string | required | Client version |
|
| required | Agent wallet key |
| string |
| Reject any tool call more expensive than this |
| string |
| Cumulative spend cap across all calls |
| string |
| Max channel deposit the client will sign on session open |
|
|
| Which chain to sign against |
Caps are enforced before any transaction is signed. A challenge that exceeds maxPerCall or maxTotal throws SpendingCapExceededError. A session challenge whose suggestedDeposit exceeds maxSessionDeposit throws SessionDepositCapExceededError. Neither error consumes any on-chain gas.
How it works
Agent calls
get_weather. Server sees no payment credential, issues aMcpError(-32042)with an HMAC-bound MPP challenge inerror.data.challenges.Client catches the error, finds a matching method intent (
tempo.charge), signs a TempotransferWithMemotransaction for the exact amount.Client retries the same tool call with the serialized transaction in
_meta["org.paymentauth/credential"].Server decodes the credential, submits the signed tx to Tempo RPC, waits for on-chain confirmation.
Server returns the tool result with a
Receiptin_meta["org.paymentauth/receipt"](method, tx hash, timestamp).Client surfaces the content + receipt.
Deterministic finality on Tempo (~0.6s blocks, no reorgs) means the agent round-trip stays sub-second even with on-chain settlement.
Network constants
// Testnet (Moderato)
TEMPO_TESTNET = {
chainId: 42431,
rpcUrl: 'https://rpc.moderato.tempo.xyz',
explorerUrl: 'https://explore.testnet.tempo.xyz',
}
// Mainnet
TEMPO_MAINNET = {
chainId: 4217,
rpcUrl: 'https://rpc.tempo.xyz',
explorerUrl: 'https://explore.tempo.xyz',
}
TESTNET_TOKENS = {
pathUSD: '0x20c0000000000000000000000000000000000000',
alphaUSD: '0x20c0000000000000000000000000000000000001',
betaUSD: '0x20c0000000000000000000000000000000000002',
thetaUSD: '0x20c0000000000000000000000000000000000003',
}Built on
MPP — Machine Payments Protocol (Stripe × Tempo)
mppx — TypeScript SDK for MPP with MCP integration
@modelcontextprotocol/sdk — MCP client/server
Tempo — Payments-first L1 blockchain
viem — Ethereum / Tempo transaction library
Transports
The library is transport-agnostic — PaidMcpServer.server exposes the underlying McpServer so you can connect any transport from @modelcontextprotocol/sdk. PaidMcpClient.connect(transport) accepts any client transport.
Transport | Use case | Example |
stdio | Local subprocesses, Claude Desktop integrations | |
Streamable HTTP | Modern network transport — single endpoint, sessions over | |
SSE | Legacy network transport — | |
In-memory | Tests and same-process demos |
The 402 → sign → retry → receipt flow is identical across all transports — only the wire format changes.
Dashboard
The library ships an optional dashboard for live revenue and call-log visibility:
import express from 'express'
import { createPaidMcpServer, mountDashboard } from 'mpp-mcp-gateway'
const server = createPaidMcpServer({ ... })
const app = express()
mountDashboard(server, app) // adds GET /api/{stats,tools,calls}
app.use(express.static('dashboard/dist')) // optional UIEndpoints (all read-only, no auth by default — pin them behind your own middleware in production):
Endpoint | Returns |
|
|
|
|
|
|
The bundled UI under dashboard/ is a small Vite + React app that polls these endpoints every 2 seconds. See dashboard/README.md for build/dev workflow, or examples/paid-weather-dashboard/ for a one-process server-plus-UI setup.
Discovery
Public registries like mpp.land crawl OpenAPI documents annotated with the x-payment-info extension defined in draft-payment-discovery-00. The library's mountDiscovery does this for you:
import { mountDiscovery } from 'mpp-mcp-gateway'
mountDiscovery(server, app, {
baseUrl: 'https://api.example.com',
categories: ['data', 'search'],
docs: { homepage: 'https://example.com/docs' },
})
// GET /openapi.json now serves a discoverable spec.The generated document includes:
Top-level
x-service-info(categories, doc links)One operation per registered tool, with full Zod-derived input schemas
x-payment-info.offers[]per-operation matching the IETF discovery draft402 Payment Requireddeclared on paid operationsCache-Control: max-age=300per spec recommendation
Once your server is publicly reachable over HTTPS, mpp.land and similar registries will pick it up automatically — no submission flow required. The 402 challenge remains authoritative; discovery is purely advisory.
Stores
The library needs persistent state for access keys and session channels. Three adapters ship out of the box:
import { Store } from 'mpp-mcp-gateway/stores'
// In-memory (default) — single process, lost on restart
const store = Store.memory()
// Upstash Redis — atomic CAS, multi-instance safe, edge-compatible
import { Redis } from '@upstash/redis'
const redis = new Redis({ url: '...', token: '...' })
const store = Store.upstash(redis, { keyPrefix: 'mppmcp:' })
// Cloudflare KV — eventually consistent, best for access keys on Workers
const store = Store.cloudflareKv(env.MY_KV, { keyPrefix: 'ak:' })Pass stores via accessKeyStore and sessionStore in server config.
Auth, Metrics, and Rate Limiting
import express from 'express'
import {
createPaidMcpServer,
mountDashboard,
mountDiscovery,
mountMetrics,
auth,
} from 'mpp-mcp-gateway'
const server = createPaidMcpServer({ ... })
const app = express()
// Dashboard — auth-gated
mountDashboard(server, app, {
middleware: auth.bearerToken(process.env.DASHBOARD_TOKEN!),
})
// Metrics — Prometheus text format, auth-gated
mountMetrics(server, app, {
middleware: auth.bearerToken(process.env.METRICS_TOKEN!),
})
// Discovery — public with CORS for registry crawlers
mountDiscovery(server, app, {
middleware: auth.publicCors(),
baseUrl: 'https://api.example.com',
})Rate limiting is on by default (60 req/min/tool). For multi-instance deployments:
import { upstashTokenBucketLimiter } from 'mpp-mcp-gateway/rate-limit'
const server = createPaidMcpServer({
// ...
rateLimit: {
limiter: upstashTokenBucketLimiter(redis, {
keyPrefix: 'rl:',
refillPerMinute: 120,
capacity: 30,
}),
},
})Webhooks
Push events to your own endpoint when payments settle, sessions open/close, or calls fail:
const server = createPaidMcpServer({
// ...
webhooks: {
url: 'https://example.com/mppmcp/webhook',
secret: process.env.WEBHOOK_SECRET!,
events: ['payment.received', 'session.closed', 'call.failed'],
},
})Events are HMAC-signed (X-MppMcp-Signature), fire-and-forget with retry (1s → 4s → 16s).
CLI
Inspect and manage deployed gateways from the command line:
npx mpp-mcp inspect https://my-gateway.fly.dev --token=secret
npx mpp-mcp stats https://api.example.com --token=admin
npx mpp-mcp tools https://api.example.com --token=admin
npx mpp-mcp calls https://api.example.com --token=admin --limit=50
npx mpp-mcp keys list https://api.example.com --token=adminDocumentation
Document | Description |
Go-live tick-list | |
Edge deployment guide | |
Serverless deployment guide | |
Persistent process deployment | |
Internals reference (diagrams, data flow) | |
Upgrade guide | |
Export stability classifications |
Roadmap
Shipped ✅
Per-call, tiered, session, and access-key pricing
Cooperative session close —
client.closeSession()settles on-chainAccess keys — subscription-style recurring access
Dashboard UI — live revenue tracking, per-tool analytics
Discovery — OpenAPI +
x-payment-infofor registry crawlingHTTP / SSE / stdio transport examples
Multi-currency offers — tools can advertise acceptance of multiple stablecoins
Persistent stores — Upstash Redis (atomic CAS), Cloudflare KV, in-memory
Rate limiting — in-memory token bucket + Upstash Redis (multi-instance)
Auth middleware — bearer token, API key, basic auth, signed query, public CORS
Prometheus
/metricsendpointOpenTelemetry tracing — opt-in span tree per paid call
Structured logging with secret redaction
Graceful shutdown with drain timeout
Webhooks — 6 event types, HMAC-signed, retry with backoff
Error taxonomy — 9 typed error classes with stable codes
BigInt-exact revenue tracking (no float drift)
O(1) ring-buffer call log
Edge runtime compatibility (Workers, Vercel Edge, Deno, Bun)
Operator CLI —
npx mpp-mcp inspect/stats/tools/calls/keysAPI stability freeze with
docs/api-stability.mdPerformance benchmarks
TypeDoc API reference
Type-level test suite (tsd)
Deployment guides (Cloudflare Workers, Vercel, Fly.io)
Production checklist + architecture reference
Next
Published to npm
CI pipeline (GitHub Actions: test, release, integration)
TypeDoc site deployed to GitHub Pages
CHANGELOG automation (changesets or similar)
License
MIT
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/aspiring-100x/mpp-mcp-gateway'
If you have feedback or need assistance with the MCP directory API, please join our Discord server