Skip to content

Commit e3a16e5

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

5 files changed

Lines changed: 152 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: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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. 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.
27+
28+
```ts [src/index.ts]
29+
import { Agent } from 'agents'
30+
import { tempo } from 'mppx/client'
31+
import { McpClient } from 'mppx/mcp/client'
32+
import { connectWallet } from './accounts'
33+
34+
type Env = { MCP_SERVER_URL: string; ACCOUNTS_PRIVATE_KEY: string }
35+
36+
export class MyAgent extends Agent<Env> {
37+
async onStart() {
38+
const { id } = await this.mcp.connect(this.env.MCP_SERVER_URL)
39+
const { account, getClient } = await connectWallet(this.env)
40+
McpClient.wrap(this.mcp.mcpConnections[id].client, {
41+
methods: [tempo({ account, getClient })],
42+
onPaymentRequired: (challenge) => Number(challenge.request.amount) < 1_000_000, // approve based on amount/intent
43+
})
44+
}
45+
}
46+
```
47+
48+
`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.`
49+
50+
:::note
51+
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).
52+
:::
53+
54+
## Accounts access keys
55+
56+
`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:
57+
58+
```ts [src/accounts.ts]
59+
import { Expiry, Provider, Storage, secp256k1 } from 'accounts'
60+
import { createClient, custom } from 'viem'
61+
import { tempo } from 'viem/tempo/chains'
62+
63+
export async function connectWallet(env: { ACCOUNTS_PRIVATE_KEY: string }) {
64+
const provider = Provider.create({
65+
adapter: secp256k1({ privateKey: env.ACCOUNTS_PRIVATE_KEY as `0x${string}` }),
66+
authorizeAccessKey: { expiry: Expiry.days(7) },
67+
storage: Storage.memory(),
68+
})
69+
const { accounts: [{ address: account }] } = await provider.request({ method: 'wallet_connect' })
70+
return { account, getClient: () => createClient({ account, chain: tempo, transport: custom(provider) }) }
71+
}
72+
```
73+
74+
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).
75+
76+
## Local development with a viem account
77+
78+
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`):
79+
80+
```ts
81+
import { tempo } from 'mppx/client'
82+
import { McpClient } from 'mppx/mcp/client'
83+
import { privateKeyToAccount } from 'viem/accounts'
84+
85+
McpClient.wrap(mcpClient, {
86+
methods: [tempo({ account: privateKeyToAccount(env.MPP_PRIVATE_KEY as `0x${string}`) })],
87+
})
88+
```
89+
90+
## Related
91+
92+
- [Cloudflare MCP client API](https://developers.cloudflare.com/agents/model-context-protocol/apis/client-api/)
93+
- [Cloudflare MPP payment overview](https://developers.cloudflare.com/agents/tools/payments/mpp/)
94+
- [Charge for HTTP content with MPP](https://developers.cloudflare.com/agents/tools/payments/mpp-charge-for-http-content/)
95+
- [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)