Skip to content

Pay from the Agents SDK

Last updated View as MarkdownAgent setup

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.

Prerequisites

Create a Cloudflare Agents project. Fund an account for the payment method that the service accepts.

Configure payments

  1. Install the Agents SDK, mppx, and viem:

    npm i agents mppx viem
  2. Store the payment private key as a Worker secret:

    npx wrangler secret put MPP_PRIVATE_KEY
  3. 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;
    }

Pay an HTTP service

Create a payment-aware client in onStart(). Restrict automatic payments to trusted origins:

src/agent.jsjs
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();
	}
}
src/agent.tsts
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.

Pay an MCP tool

Connect the Agent with addMcpServer(). Wait for the connection before wrapping its client:

src/agent.jsjs
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 };
	}
}
src/agent.tsts
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.

Pay x402 services

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.

Was this helpful?