Use Cloudflare Workers to accept Machine Payments Protocol (MPP) payments. Choose an integration based on the service you want to protect:
mpp-proxy↗ — Charge for HTTP content without changing your origin code. Refer to Charge for HTTP content.- Worker route — Add
mppxpayment middleware to a Worker application. - MCP tool — Require payment before an MCP tool returns its result.
Create a Cloudflare account. You also need a payment recipient and an MPP secret key.
The examples use a stablecoin payment method on testnet. For other methods, refer to MPP payment methods ↗.
Add mppx middleware when you control the Worker application:
-
Install Hono and
mppxin the Worker project:npm i hono mppxyarn add hono mppxpnpm add hono mppxbun add hono mppx -
Store the MPP signing key as a Worker secret:
npx wrangler secret put MPP_SECRET_KEYyarn wrangler secret put MPP_SECRET_KEYpnpm wrangler secret put MPP_SECRET_KEY -
Add the payment middleware before the paid route handler:
src/index.jsjs import { env } from "cloudflare:workers"; import { Hono } from "hono"; import { Mppx, tempo } from "mppx/hono"; const workerEnv = env; const app = new Hono(); const mppx = Mppx.create({ methods: [tempo.charge({ testnet: true })], secretKey: workerEnv.MPP_SECRET_KEY, }); app.get( "/premium", mppx.charge({ amount: "0.01", currency: "0x20c0000000000000000000000000000000000000", description: "Premium API access", recipient: "<YOUR_WALLET_ADDRESS>", }), (c) => c.json({ access: "paid" }), ); export default app;src/index.tsts import { env } from "cloudflare:workers"; import { Hono } from "hono"; import { Mppx, tempo } from "mppx/hono"; const workerEnv = env as Env & { MPP_SECRET_KEY: string }; const app = new Hono(); const mppx = Mppx.create({ methods: [tempo.charge({ testnet: true })], secretKey: workerEnv.MPP_SECRET_KEY, }); app.get( "/premium", mppx.charge({ amount: "0.01", currency: "0x20c0000000000000000000000000000000000000", description: "Premium API access", recipient: "<YOUR_WALLET_ADDRESS>", }), (c) => c.json({ access: "paid" }), ); export default app; -
Deploy the Worker:
npx wrangler deployyarn wrangler deploypnpm wrangler deploy -
Request the paid route without a payment:
curl -i https://<YOUR_WORKER>.workers.dev/premiumThe Worker returns
402 Payment Requiredand aWWW-Authenticate: Paymentheader. A paid retry reaches the handler and returns aPayment-Receiptheader.
Add the MPP transport to an McpAgent:
-
Install the required packages in an Agents project:
npm i agents mppx @modelcontextprotocol/sdk zodyarn add agents mppx @modelcontextprotocol/sdk zodpnpm add agents mppx @modelcontextprotocol/sdk zodbun add agents mppx @modelcontextprotocol/sdk zod -
Bind the
McpAgentDurable Object in the Wrangler configuration:{ "$schema": "./node_modules/wrangler/config-schema.json", "name": "mpp-server", "main": "src/index.ts", // Set this to today's date "compatibility_date": "2026-08-13", "compatibility_flags": [ "nodejs_compat" ], "durable_objects": { "bindings": [ { "name": "MCP_OBJECT", "class_name": "PaidMCP" } ] }, "migrations": [ { "tag": "v1", "new_sqlite_classes": [ "PaidMCP" ] } ] }name = "mpp-server" main = "src/index.ts" # Set this to today's date compatibility_date = "2026-08-13" compatibility_flags = ["nodejs_compat"] [[durable_objects.bindings]] name = "MCP_OBJECT" class_name = "PaidMCP" [[migrations]] tag = "v1" new_sqlite_classes = ["PaidMCP"] -
Store
MPP_SECRET_KEYas a Worker secret:npx wrangler secret put MPP_SECRET_KEYyarn wrangler secret put MPP_SECRET_KEYpnpm wrangler secret put MPP_SECRET_KEY -
Check payment before returning the tool result:
src/index.jsjs import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { McpAgent } from "agents/mcp"; import { Mppx, tempo, Transport } from "mppx/server"; import { z } from "zod"; export class PaidMCP extends McpAgent { server = new McpServer({ name: "paid-search", version: "1.0.0" }); async init() { const mppx = Mppx.create({ methods: [tempo.charge({ testnet: true })], secretKey: this.env.MPP_SECRET_KEY, transport: Transport.mcpSdk(), }); this.server.tool( "premium_search", "Search premium content", { query: z.string() }, async ({ query }, extra) => { const payment = await mppx.charge({ amount: "0.01", currency: "0x20c0000000000000000000000000000000000000", description: "Premium search", recipient: "<YOUR_WALLET_ADDRESS>", })(extra); if (payment.status === 402) throw payment.challenge; return payment.withReceipt({ content: [{ type: "text", text: `Results for: ${query}` }], }); }, ); } } export default PaidMCP.serve("/mcp");src/index.tsts import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { McpAgent } from "agents/mcp"; import { Mppx, tempo, Transport } from "mppx/server"; import { z } from "zod"; type PaymentEnv = Env & { MPP_SECRET_KEY: string }; export class PaidMCP extends McpAgent<PaymentEnv> { server = new McpServer({ name: "paid-search", version: "1.0.0" }); async init() { const mppx = Mppx.create({ methods: [tempo.charge({ testnet: true })], secretKey: this.env.MPP_SECRET_KEY, transport: Transport.mcpSdk(), }); this.server.tool( "premium_search", "Search premium content", { query: z.string() }, async ({ query }, extra) => { const payment = await mppx.charge({ amount: "0.01", currency: "0x20c0000000000000000000000000000000000000", description: "Premium search", recipient: "<YOUR_WALLET_ADDRESS>", })(extra); if (payment.status === 402) throw payment.challenge; return payment.withReceipt({ content: [{ type: "text", text: `Results for: ${query}` }], }); }, ); } } export default PaidMCP.serve("/mcp"); -
Deploy the Worker:
npx wrangler deployyarn wrangler deploypnpm wrangler deployAn unpaid
premium_searchcall returns an MPP Challenge. A paid retry returns the tool result and an MPP Receipt in_meta.
To test both payment flows from a Cloudflare Agent, refer to Pay from the Agents SDK. For production billing patterns, refer to MPP payment intents ↗.