Skip to content

Commit 8b75546

Browse files
committed
docs: add Cloudflare Agents integration guide
1 parent 6a6dfdd commit 8b75546

5 files changed

Lines changed: 150 additions & 8 deletions

File tree

‎src/pages.gen.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ type Page =
3737
| { path: '/intents/subscription'; render: 'static' }
3838
| { path: '/mpp-vs-x402'; render: 'static' }
3939
| { path: '/overview'; render: 'static' }
40+
| { path: '/partner-sdks/cloudflare-agents'; render: 'static' }
4041
| { path: '/payment-methods/card/charge'; render: 'static' }
4142
| { path: '/payment-methods/card'; render: 'static' }
4243
| { path: '/payment-methods/custom'; render: 'static' }
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
---
2+
description: "Use mppx to let a Cloudflare Agent pay for MPP-enabled MCP tools."
3+
imageDescription: "Connect Cloudflare Agents to paid MPP MCP tools"
4+
---
5+
6+
# Cloudflare Agents [Pay for MCP tools from your agent]
7+
8+
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.
9+
10+
## Install
11+
12+
:::code-group
13+
```bash [npm]
14+
$ npm install agents mppx accounts
15+
```
16+
```bash [pnpm]
17+
$ pnpm add agents mppx accounts
18+
```
19+
```bash [bun]
20+
$ bun add agents mppx accounts
21+
```
22+
:::
23+
24+
## Wrap the MCP client
25+
26+
Connect an MCP server in `onStart`, then inject payment handling into the connected client.
27+
28+
```ts [src/index.ts]
29+
import { Agent } from 'agents'
30+
import { McpClient } from 'mppx/mcp/client'
31+
import { createAccountsMpp } from './accounts'
32+
33+
type Env = { MCP_SERVER_URL: string; ACCOUNTS_PRIVATE_KEY: string }
34+
35+
export class MyAgent extends Agent<Env> {
36+
async onStart() {
37+
const { id } = await this.mcp.connect(this.env.MCP_SERVER_URL)
38+
const mpp = await createAccountsMpp(this.env)
39+
McpClient.wrap(this.mcp.mcpConnections[id].client, {
40+
methods: mpp.methods,
41+
onPaymentRequired: (challenge) => Number(challenge.request.amount) < 1_000_000, // approve based on amount/intent
42+
})
43+
}
44+
}
45+
```
46+
47+
`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.`
48+
49+
:::note
50+
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).
51+
:::
52+
53+
## Accounts access keys
54+
55+
`mpp.methods` are pre-wired payment method clients from the [Tempo Accounts SDK](https://accounts.tempo.xyz/docs)—payments route through your Accounts access keys. When the connected Accounts provider supports the `wallet_authorizeChallenge` RPC, payment authorization happens inside the wallet via your access keys automatically (mppx probes `wallet_getCapabilities` and falls back to local signing otherwise). The smallest Workers-compatible setup pins a server-side key with the `secp256k1` adapter:
56+
57+
```ts [src/accounts.ts]
58+
import { Expiry, Provider, Storage, secp256k1 } from 'accounts'
59+
60+
export async function createAccountsMpp(env: { ACCOUNTS_PRIVATE_KEY: string }) {
61+
const provider = Provider.create({
62+
adapter: secp256k1({ privateKey: env.ACCOUNTS_PRIVATE_KEY as `0x${string}` }),
63+
authorizeAccessKey: { expiry: Expiry.days(7) },
64+
storage: Storage.memory(),
65+
})
66+
await provider.request({ method: 'wallet_connect' })
67+
if (!provider.mpp) throw new Error('MPP is disabled on this provider.')
68+
return provider.mpp
69+
}
70+
```
71+
72+
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. For provisioning accounts and access-key policies (spend limits, scopes, expiry), see the [Accounts docs](https://accounts.tempo.xyz/docs).
73+
74+
## Local development with a viem account
75+
76+
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`):
77+
78+
```ts
79+
import { tempo } from 'mppx/client'
80+
import { McpClient } from 'mppx/mcp/client'
81+
import { privateKeyToAccount } from 'viem/accounts'
82+
83+
McpClient.wrap(mcpClient, {
84+
methods: [tempo({ account: privateKeyToAccount(env.MPP_PRIVATE_KEY as `0x${string}`) })],
85+
})
86+
```
87+
88+
## Related
89+
90+
- [Cloudflare MCP client API](https://developers.cloudflare.com/agents/model-context-protocol/apis/client-api/)
91+
- [Cloudflare MPP payment overview](https://developers.cloudflare.com/agents/tools/payments/mpp/)
92+
- [Charge for HTTP content with MPP](https://developers.cloudflare.com/agents/tools/payments/mpp-charge-for-http-content/)
93+
- [Use with agents](/quickstart/agent)

‎src/pages/quickstart/agent.mdx‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { Badge, Card, Cards, Tab, Tabs } from 'vocs'
99

1010
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.
1111

12+
If you're building an agent with a partner SDK, see [Cloudflare Agents](/partner-sdks/cloudflare-agents) for a framework-specific integration.
13+
1214
| Tool | Best for | Setup |
1315
|------|----------|-------|
1416
| [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
223225
title="Wallets"
224226
to="/tools/wallet"
225227
/>
228+
<Card
229+
description="Pay for MPP-enabled services from a Cloudflare Agent"
230+
icon="lucide:cloud"
231+
title="Cloudflare Agents"
232+
to="/partner-sdks/cloudflare-agents"
233+
/>
226234
</Cards>

‎src/pages/sdk/typescript/client/McpClient.wrap.mdx‎

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
# `McpClient.wrap` [Payment-aware MCP client]
22

3-
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.
3+
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.
4+
5+
`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.
46

57
## Usage
68

79
```ts
810
import { Client } from '@modelcontextprotocol/sdk/client'
9-
import { McpClient, tempo } from 'mppx/mcp-sdk/client'
11+
import { tempo } from 'mppx/client'
12+
import { McpClient } from 'mppx/mcp/client'
1013
import { privateKeyToAccount } from 'viem/accounts'
1114

1215
const client = new Client({ name: 'my-client', version: '1.0.0' })
@@ -20,20 +23,29 @@ const result = await mcp.callTool({ name: 'premium_tool', arguments: {} })
2023
// @log: { content: [...], receipt: { ... } }
2124
```
2225

26+
`mcp` is the same object as `client`, retyped with the payment-aware `callTool`.
27+
2328
### With call options
2429

25-
Pass `context` and `timeout` through the second argument to `callTool`.
30+
`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.
2631

2732
```ts
2833
const result = await mcp.callTool(
2934
{ name: 'premium_tool', arguments: { query: 'hello' } },
30-
{ context: { foo: 'bar' }, timeout: 30_000 },
35+
undefined,
36+
{
37+
context: { foo: 'bar' },
38+
onPaymentRequired: (challenge) => Number(challenge.request.amount) < 1_000_000,
39+
timeout: 30_000,
40+
},
3141
)
3242
```
3343

44+
A per-call `onPaymentRequired` overrides the configured hook; pass `null` to bypass a configured hook for one call.
45+
3446
## Return type
3547

36-
`McpClient.wrap` returns an object that spreads the original client and overrides `callTool` with a payment-aware version.
48+
`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.
3749

3850
```ts
3951
type McpClient<client, methods> = Omit<client, 'callTool'> & {
@@ -43,6 +55,7 @@ type McpClient<client, methods> = Omit<client, 'callTool'> & {
4355
name: string
4456
_meta?: Record<string, unknown>
4557
},
58+
resultSchema?: CallToolResultSchema,
4659
options?: CallToolOptions<methods>,
4760
) => Promise<CallToolResult>
4861
}
@@ -62,10 +75,28 @@ type CallToolResult = Awaited<ReturnType<Client['callTool']>> & {
6275
6376
- **Type:** `Pick<Client, 'callTool'>`
6477
65-
The MCP SDK client instance to wrap. Must have a `callTool` method—typically an instance of `Client` from `@modelcontextprotocol/sdk/client`.
78+
The MCP SDK client instance to mutate. Must have a `callTool` method—typically an instance of `Client` from `@modelcontextprotocol/sdk/client`.
6679
6780
### config.methods
6881
69-
- **Type:** `readonly Method.AnyClient[]`
82+
- **Type:** `readonly (Method.AnyClient | readonly Method.AnyClient[])[]`
83+
84+
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.
85+
86+
### config.onPaymentRequired
87+
88+
- **Type:** `(challenge: Challenge.Challenge) => boolean | Promise<boolean>`
89+
90+
Optional approval hook called before creating a payment credential. Receives the selected Challenge; return `false` to decline—`callTool` then rejects with `Payment declined.`
91+
92+
### config.orderChallenges
93+
94+
- **Type:** `(candidates: ChallengeCandidate[]) => ChallengeCandidate[] | Promise<ChallengeCandidate[]>`
95+
96+
Optional. Filters and sorts the supported Challenges before a Credential is created; the first candidate is used.
97+
98+
### config.paymentPreferences
99+
100+
- **Type:** `AcceptPayment.Config`
70101
71-
Array of payment methods to use when handling payment Challenges. The wrapper matches Challenges from the server against installed methods by name and intent.
102+
Optional. Client-declared supported payment methods, keyed by typed `method/intent` strings.

‎vocs.config.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,15 @@ export default defineConfig({
289289
{ text: "Use with your app", link: "/quickstart/client" },
290290
],
291291
},
292+
{
293+
text: "Use with partner SDKs",
294+
items: [
295+
{
296+
text: "Cloudflare Agents",
297+
link: "/partner-sdks/cloudflare-agents",
298+
},
299+
],
300+
},
292301
{
293302
text: "Guides",
294303
items: [

0 commit comments

Comments
 (0)