Use the Cloudflare Agents SDK to pay MPP services. The mppx SDK handles payment retries for HTTP requests and Model Context Protocol (MCP) tool calls.
Create a Cloudflare Agents project. Fund an account for the payment method that the service accepts.
-
Install the Agents SDK,
mppx, andviem:npm i agents mppx viemyarn add agents mppx viempnpm add agents mppx viembun add agents mppx viem -
Store the payment private key as a Worker secret:
npx wrangler secret put MPP_PRIVATE_KEYyarn wrangler secret put MPP_PRIVATE_KEYpnpm wrangler secret put MPP_PRIVATE_KEY -
Create the payment method once:
src/payments.jsjs import { tempo } from "mppx/client"; import { privateKeyToAccount } from "viem/accounts"; export function createPaymentMethods(privateKey) { const account = privateKeyToAccount(privateKey); return [tempo.charge({ account })]; }src/payments.tsts import { tempo } from "mppx/client"; import { privateKeyToAccount } from "viem/accounts"; export function createPaymentMethods(privateKey: string) { const account = privateKeyToAccount(privateKey as `0x${string}`); return [tempo.charge({ account })] as const; }
Create a payment-aware client in onStart(). Restrict automatic payments to trusted origins:
import { Agent } from "agents";
import { Mppx } from "mppx/client";
import { createPaymentMethods } from "./payments";
export class BuyerAgent extends Agent {
methods;
payments;
async onStart() {
this.methods = createPaymentMethods(this.env.MPP_PRIVATE_KEY);
this.payments = Mppx.create({
acceptPaymentPolicy: { origins: ["https://api.example.com"] },
methods: this.methods,
polyfill: false,
});
}
async buyReport() {
const response = await this.payments.fetch(
"https://api.example.com/reports/latest",
);
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
return response.json();
}
}import { Agent } from "agents";
import { Mppx } from "mppx/client";
import { createPaymentMethods } from "./payments";
type PaymentEnv = Env & { MPP_PRIVATE_KEY: string };
export class BuyerAgent extends Agent<PaymentEnv> {
methods!: ReturnType<typeof createPaymentMethods>;
payments!: ReturnType<typeof Mppx.create>;
async onStart() {
this.methods = createPaymentMethods(this.env.MPP_PRIVATE_KEY);
this.payments = Mppx.create({
acceptPaymentPolicy: { origins: ["https://api.example.com"] },
methods: this.methods,
polyfill: false,
});
}
async buyReport() {
const response = await this.payments.fetch(
"https://api.example.com/reports/latest",
);
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
return response.json();
}
}Free endpoints pass through unchanged. Paid endpoints trigger the payment retry and return a Payment-Receipt header.
Connect the Agent with addMcpServer(). Wait for the connection before wrapping its client:
import { Agent } from "agents";
import { McpClient } from "mppx/mcp/client";
import { createPaymentMethods } from "./payments";
export class BuyerAgent extends Agent {
methods;
async onStart() {
this.methods = createPaymentMethods(this.env.MPP_PRIVATE_KEY);
}
async paidSearch() {
const connection = await this.addMcpServer(
"premium-search",
"https://mcp.example.com/mcp",
);
if (connection.state === "authenticating") {
return { authUrl: connection.authUrl };
}
await this.mcp.waitForConnections();
const server = this.getMcpServers().servers[connection.id];
const mcpConnection = this.mcp.mcpConnections[connection.id];
if (server?.state !== "ready" || !mcpConnection) {
throw new Error("MCP server is not ready.");
}
const client = McpClient.wrap(mcpConnection.client, {
methods: this.methods,
});
const result = await client.callTool({
name: "premium_search",
arguments: { query: "Cloudflare Agents" },
});
return { content: result.content, receipt: result.receipt };
}
}import { Agent } from "agents";
import { McpClient } from "mppx/mcp/client";
import { createPaymentMethods } from "./payments";
type PaymentEnv = Env & { MPP_PRIVATE_KEY: string };
export class BuyerAgent extends Agent<PaymentEnv> {
methods!: ReturnType<typeof createPaymentMethods>;
async onStart() {
this.methods = createPaymentMethods(this.env.MPP_PRIVATE_KEY);
}
async paidSearch() {
const connection = await this.addMcpServer(
"premium-search",
"https://mcp.example.com/mcp",
);
if (connection.state === "authenticating") {
return { authUrl: connection.authUrl };
}
await this.mcp.waitForConnections();
const server = this.getMcpServers().servers[connection.id];
const mcpConnection = this.mcp.mcpConnections[connection.id];
if (server?.state !== "ready" || !mcpConnection) {
throw new Error("MCP server is not ready.");
}
const client = McpClient.wrap(mcpConnection.client, {
methods: this.methods,
});
const result = await client.callTool({
name: "premium_search",
arguments: { query: "Cloudflare Agents" },
});
return { content: result.content, receipt: result.receipt };
}
}If paidSearch() returns an authUrl, send the user to that URL and retry after authorization.
The wrapper retries a paid tool call with an MPP Credential. The result includes the MPP Receipt as result.receipt.
By default, both clients pay compatible Challenges automatically. Use onChallenge for HTTP or onPaymentRequired for MCP when a payment needs approval. Challenge amounts are integer base units, not decimal display values.
The mppx HTTP client also recognizes x402 Challenges. Configure an x402-compatible EVM method next to the MPP method. The service does not need changes. For configuration, refer to Use MPP with x402 ↗.
To accept payments, refer to Accept payments with MPP. For MCP connection options, refer to the MCP client API.