From e3a16e59f8016cf3481dd57c1153f510077fbd79 Mon Sep 17 00:00:00 2001 From: Parv Ahuja Date: Thu, 11 Jun 2026 15:36:50 -0700 Subject: [PATCH] docs: add Cloudflare Agents integration guide --- src/pages.gen.ts | 1 + src/pages/partner-sdks/cloudflare-agents.mdx | 95 +++++++++++++++++++ src/pages/quickstart/agent.mdx | 8 ++ .../sdk/typescript/client/McpClient.wrap.mdx | 47 +++++++-- vocs.config.ts | 9 ++ 5 files changed, 152 insertions(+), 8 deletions(-) create mode 100644 src/pages/partner-sdks/cloudflare-agents.mdx diff --git a/src/pages.gen.ts b/src/pages.gen.ts index 4a998981..1ee49c0e 100644 --- a/src/pages.gen.ts +++ b/src/pages.gen.ts @@ -37,6 +37,7 @@ type Page = | { path: '/intents/subscription'; render: 'static' } | { path: '/mpp-vs-x402'; render: 'static' } | { path: '/overview'; render: 'static' } + | { path: '/partner-sdks/cloudflare-agents'; render: 'static' } | { path: '/payment-methods/card/charge'; render: 'static' } | { path: '/payment-methods/card'; render: 'static' } | { path: '/payment-methods/custom'; render: 'static' } diff --git a/src/pages/partner-sdks/cloudflare-agents.mdx b/src/pages/partner-sdks/cloudflare-agents.mdx new file mode 100644 index 00000000..dd3f5078 --- /dev/null +++ b/src/pages/partner-sdks/cloudflare-agents.mdx @@ -0,0 +1,95 @@ +--- +description: "Use mppx to let a Cloudflare Agent pay for MPP-enabled MCP tools." +imageDescription: "Connect Cloudflare Agents to paid MPP MCP tools" +--- + +# Cloudflare Agents [Pay for MCP tools from your agent] + +Use [`McpClient.wrap`](/sdk/typescript/client/McpClient.wrap) to let a [Cloudflare Agent](https://developers.cloudflare.com/agents/) call free and paid MCP tools through the same MCP client. Free tools pass through untouched. When a paid tool returns an MPP Challenge, the wrapped client creates a Credential, retries the call, and returns the result—the Agent never needs to know which tools are paid. + +## Install + +:::code-group +```bash [npm] +$ npm install agents mppx accounts +``` +```bash [pnpm] +$ pnpm add agents mppx accounts +``` +```bash [bun] +$ bun add agents mppx accounts +``` +::: + +## Wrap the MCP client + +Connect an MCP server in `onStart`, then inject payment handling into the connected client. Payments are authorized inside your wallet through the `wallet_authorizeChallenge` RPC using your [Tempo Accounts](https://accounts.tempo.xyz/docs) access keys—mppx discovers wallet support via `wallet_getCapabilities` and falls back to local signing for accounts that aren't wallet-backed. + +```ts [src/index.ts] +import { Agent } from 'agents' +import { tempo } from 'mppx/client' +import { McpClient } from 'mppx/mcp/client' +import { connectWallet } from './accounts' + +type Env = { MCP_SERVER_URL: string; ACCOUNTS_PRIVATE_KEY: string } + +export class MyAgent extends Agent { + async onStart() { + const { id } = await this.mcp.connect(this.env.MCP_SERVER_URL) + const { account, getClient } = await connectWallet(this.env) + McpClient.wrap(this.mcp.mcpConnections[id].client, { + methods: [tempo({ account, getClient })], + onPaymentRequired: (challenge) => Number(challenge.request.amount) < 1_000_000, // approve based on amount/intent + }) + } +} +``` + +`McpClient.wrap` mutates the client in place, so there is nothing to store: every surface built on the connection—including `this.mcp.getAITools(...)` and `this.mcp.callTool(...)`—stays payment-aware. Paid calls expose the MPP Receipt on `result.receipt`. `onPaymentRequired` is the spend-policy / human-in-the-loop hook: it runs before each Credential is created, and returning (or resolving) `false` declines the payment—`callTool` rejects with `Payment declined.` + +:::note +If you also use Cloudflare's `withX402Client`, apply `McpClient.wrap` first—each wrapper then handles only its own protocol's challenges (the reverse order breaks tool calls). +::: + +## Accounts access keys + +`connectWallet` runs a headless [Tempo Accounts](https://accounts.tempo.xyz/docs) wallet next to the Agent: the `secp256k1` adapter pins a server-side key, `wallet_connect` authorizes an access key, and wrapping the provider in a viem client gives `tempo(...)` a `json-rpc` account whose challenges route to the wallet: + +```ts [src/accounts.ts] +import { Expiry, Provider, Storage, secp256k1 } from 'accounts' +import { createClient, custom } from 'viem' +import { tempo } from 'viem/tempo/chains' + +export async function connectWallet(env: { ACCOUNTS_PRIVATE_KEY: string }) { + const provider = Provider.create({ + adapter: secp256k1({ privateKey: env.ACCOUNTS_PRIVATE_KEY as `0x${string}` }), + authorizeAccessKey: { expiry: Expiry.days(7) }, + storage: Storage.memory(), + }) + const { accounts: [{ address: account }] } = await provider.request({ method: 'wallet_connect' }) + return { account, getClient: () => createClient({ account, chain: tempo, transport: custom(provider) }) } +} +``` + +Access keys are chain-scoped—the authorized chain must match the chain ID in the MPP Challenge. `Storage.memory()` re-authorizes a key on each cold start; wrap KV or a Durable Object with `Storage.from(...)` to persist keys. The provider also exposes pre-wired method clients at `provider.mpp` for direct use. For provisioning accounts and access-key policies (spend limits, scopes, expiry), see the [Accounts docs](https://accounts.tempo.xyz/docs). + +## Local development with a viem account + +For local development or a simple server wallet, skip Accounts and pass a viem account directly (store the key with `npx wrangler secret put MPP_PRIVATE_KEY`): + +```ts +import { tempo } from 'mppx/client' +import { McpClient } from 'mppx/mcp/client' +import { privateKeyToAccount } from 'viem/accounts' + +McpClient.wrap(mcpClient, { + methods: [tempo({ account: privateKeyToAccount(env.MPP_PRIVATE_KEY as `0x${string}`) })], +}) +``` + +## Related + +- [Cloudflare MCP client API](https://developers.cloudflare.com/agents/model-context-protocol/apis/client-api/) +- [Cloudflare MPP payment overview](https://developers.cloudflare.com/agents/tools/payments/mpp/) +- [Charge for HTTP content with MPP](https://developers.cloudflare.com/agents/tools/payments/mpp-charge-for-http-content/) +- [Use with agents](/quickstart/agent) diff --git a/src/pages/quickstart/agent.mdx b/src/pages/quickstart/agent.mdx index b82dd650..6c2893cc 100644 --- a/src/pages/quickstart/agent.mdx +++ b/src/pages/quickstart/agent.mdx @@ -9,6 +9,8 @@ import { Badge, Card, Cards, Tab, Tabs } from 'vocs' Agents can automatically interact with MPP-enabled services, paying for API calls without human intervention. Learn more about [agentic payments](/use-cases/agentic-payments) or get started below. +If you're building an agent with a partner SDK, see [Cloudflare Agents](/partner-sdks/cloudflare-agents) for a framework-specific integration. + | Tool | Best for | Setup | |------|----------|-------| | [Tempo Wallet](#tempo-wallet) | MPP services with spend controls and service discovery | `tempo wallet login` | @@ -223,4 +225,10 @@ $ mppx https://mpp.dev/api/ping/paid title="Wallets" to="/tools/wallet" /> + diff --git a/src/pages/sdk/typescript/client/McpClient.wrap.mdx b/src/pages/sdk/typescript/client/McpClient.wrap.mdx index afc0b384..0b9f7161 100644 --- a/src/pages/sdk/typescript/client/McpClient.wrap.mdx +++ b/src/pages/sdk/typescript/client/McpClient.wrap.mdx @@ -1,12 +1,15 @@ # `McpClient.wrap` [Payment-aware MCP client] -Wraps an MCP SDK client with automatic payment handling. When a tool call returns a `-32042` payment required error, the wrapper creates a Credential and retries the call. +Adds automatic payment handling to an MCP SDK client in place. When a tool call returns a `-32042` payment required error—or a tool result carrying payment-required metadata—the client creates a Credential and retries the call. + +`McpClient.wrap` mutates the provided client and returns the same reference, so there is nothing to store: surfaces built on the original client stay payment-aware. This includes setups where another SDK owns the MCP client reference, for example [Cloudflare Agents](/partner-sdks/cloudflare-agents). Calling `wrap` on the same client again replaces its payment configuration. ## Usage ```ts import { Client } from '@modelcontextprotocol/sdk/client' -import { McpClient, tempo } from 'mppx/mcp-sdk/client' +import { tempo } from 'mppx/client' +import { McpClient } from 'mppx/mcp/client' import { privateKeyToAccount } from 'viem/accounts' const client = new Client({ name: 'my-client', version: '1.0.0' }) @@ -20,20 +23,29 @@ const result = await mcp.callTool({ name: 'premium_tool', arguments: {} }) // @log: { content: [...], receipt: { ... } } ``` +`mcp` is the same object as `client`, retyped with the payment-aware `callTool`. + ### With call options -Pass `context` and `timeout` through the second argument to `callTool`. +`callTool` keeps the MCP SDK's `(params, resultSchema?, options?)` signature—pass `context`, a per-call `onPaymentRequired` approval hook, and a request `timeout` through the third argument. `context` and `onPaymentRequired` are stripped before the remaining request options are forwarded to the SDK. ```ts const result = await mcp.callTool( { name: 'premium_tool', arguments: { query: 'hello' } }, - { context: { foo: 'bar' }, timeout: 30_000 }, + undefined, + { + context: { foo: 'bar' }, + onPaymentRequired: (challenge) => Number(challenge.request.amount) < 1_000_000, + timeout: 30_000, + }, ) ``` +A per-call `onPaymentRequired` overrides the configured hook; pass `null` to bypass a configured hook for one call. + ## Return type -`McpClient.wrap` returns an object that spreads the original client and overrides `callTool` with a payment-aware version. +`McpClient.wrap` returns the same client reference, with `callTool` retyped to the payment-aware version. The MCP SDK's three-argument `callTool` signature is preserved, so surfaces built on it—such as Cloudflare's `MCPClientManager.callTool(params, resultSchema, options)`—keep working. ```ts type McpClient = Omit & { @@ -43,6 +55,7 @@ type McpClient = Omit & { name: string _meta?: Record }, + resultSchema?: CallToolResultSchema, options?: CallToolOptions, ) => Promise } @@ -62,10 +75,28 @@ type CallToolResult = Awaited> & { - **Type:** `Pick` -The MCP SDK client instance to wrap. Must have a `callTool` method—typically an instance of `Client` from `@modelcontextprotocol/sdk/client`. +The MCP SDK client instance to mutate. Must have a `callTool` method—typically an instance of `Client` from `@modelcontextprotocol/sdk/client`. ### config.methods -- **Type:** `readonly Method.AnyClient[]` +- **Type:** `readonly (Method.AnyClient | readonly Method.AnyClient[])[]` + +Array of payment methods to use when handling payment Challenges. Accepts individual method clients or tuples (e.g. from `tempo()`). The client matches Challenges from the server against installed methods by name and intent. + +### config.onPaymentRequired + +- **Type:** `(challenge: Challenge.Challenge) => boolean | Promise` + +Optional approval hook called before creating a payment credential. Receives the selected Challenge; return `false` to decline—`callTool` then rejects with `Payment declined.` + +### config.orderChallenges + +- **Type:** `(candidates: ChallengeCandidate[]) => ChallengeCandidate[] | Promise` + +Optional. Filters and sorts the supported Challenges before a Credential is created; the first candidate is used. + +### config.paymentPreferences + +- **Type:** `AcceptPayment.Config` -Array of payment methods to use when handling payment Challenges. The wrapper matches Challenges from the server against installed methods by name and intent. +Optional. Client-declared supported payment methods, keyed by typed `method/intent` strings. diff --git a/vocs.config.ts b/vocs.config.ts index 03e702d8..6aefaccc 100644 --- a/vocs.config.ts +++ b/vocs.config.ts @@ -289,6 +289,15 @@ export default defineConfig({ { text: "Use with your app", link: "/quickstart/client" }, ], }, + { + text: "Use with partner SDKs", + items: [ + { + text: "Cloudflare Agents", + link: "/partner-sdks/cloudflare-agents", + }, + ], + }, { text: "Guides", items: [