Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/pages.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,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' }
Expand Down
117 changes: 117 additions & 0 deletions src/pages/partner-sdks/cloudflare-agents.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
---
description: "Use mppx to let a Cloudflare Agent pay for MPP-enabled MCP tools and HTTP requests."
imageDescription: "Connect Cloudflare Agents to paid MPP MCP tools and HTTP APIs"
---

# Cloudflare Agents [Pay for MCP tools and HTTP requests from your agent]

Use [`McpClient.wrap`](/sdk/typescript/client/McpClient.wrap) and [`Mppx.create`](/sdk/typescript/client/Mppx.create) to let a [Cloudflare Agent](https://developers.cloudflare.com/agents/) pay for MCP tools and HTTP requests through MPP. Free calls pass through untouched; when a paid tool or a 402-protected request returns an MPP Challenge, the wrapped client creates a Credential, retries the call, and returns the result. A [Tempo Accounts](https://accounts.tempo.xyz/docs) wallet settles each payment with a scoped access key.

## Install

:::code-group
```bash [npm]
$ npm install agents mppx accounts
```
```bash [pnpm]
$ pnpm add agents mppx accounts
```
```bash [bun]
$ bun add agents mppx accounts
```
:::

## Connect a wallet

Both payment paths share one wallet. `connectWallet` runs a headless [Tempo Accounts](https://accounts.tempo.xyz/docs) wallet beside the Agent and returns the `account` and `getClient` you pass to `tempo(...)`:

```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}` }),
accessKey: { authorize: { 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) }) }
}
```

`Storage.memory()` re-authorizes a key on each cold start; wrap KV or a Durable Object with `Storage.from(...)` to persist keys across restarts.

## Pay for MCP tools

Connect an MPP-enabled MCP server in `onStart`, then wrap the connected client. `McpClient.wrap` mutates it in place and returns the same client, retyped so `this.mcp.getAITools(...)` and `this.mcp.callTool(...)` become payment-aware—paid calls carry the MPP Receipt on `result.receipt`.

```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<Env> {
async onStart() {
const { id } = await this.mcp.connect(this.env.MCP_SERVER_URL)
const { account, getClient } = await connectWallet(this.env)
const client = McpClient.wrap(this.mcp.mcpConnections[id].client, {
methods: [tempo({ account, getClient })],
onPaymentRequired: (challenge) => Number(challenge.request.amount) < 1_000_000, // per-call spend policy
})

// Every tool call is now payment-aware; paid calls carry an MPP Receipt.
const result = await client.callTool({ name: 'premium_search', arguments: { query: 'tempo' } })
console.log(result.receipt)
}
}
```

`onPaymentRequired` runs before each payment; return `false` to decline (the call rejects with `Payment declined.`).

## Pay for HTTP requests

The same wallet pays for plain HTTP requests. `Mppx.create` returns a payment-aware `fetch` that automatically pays an MPP 402 and retries. Call it from a request context (Workers forbid I/O at module top level):

```ts [src/pay.ts]
import { Mppx, tempo } from 'mppx/client'
import { connectWallet } from './accounts'

export async function payFetch(env: { ACCOUNTS_PRIVATE_KEY: string }, url: string) {
const { account, getClient } = await connectWallet(env)
const mppx = Mppx.create({
methods: [tempo({ account, getClient })],
polyfill: false, // Workers' globalThis.fetch is read-only, so wrap the fetch you call
})
return mppx.fetch(url) // auto-pays an MPP 402
}
```

Pass `onChallenge` to inspect a challenge (e.g. `challenge.request.amount`) before it pays.

## Environment setup

Store the wallet key as a [Workers secret](https://developers.cloudflare.com/workers/configuration/secrets/) and fund it with testnet tokens while developing:

```bash [terminal]
$ npx wrangler secret put ACCOUNTS_PRIVATE_KEY
```

For local development, you can skip Accounts and pass a viem account straight to `tempo({ account })`—see [`McpClient.wrap`](/sdk/typescript/client/McpClient.wrap).

## Access keys vs. your root key

`connectWallet` authorizes a scoped, revocable [access key](https://accounts.tempo.xyz/docs) and pays every tool call and HTTP request with it—never your account's root key. Set its expiry once and revoke it any time; inspect or gate each payment with `onPaymentRequired` (MCP) or `onChallenge` (HTTP). If you add on-chain spend limits to the key, size them to include network fees, not just the charge amount.

## Related

- [`Mppx.create`](/sdk/typescript/client/Mppx.create) and [`McpClient.wrap`](/sdk/typescript/client/McpClient.wrap)
- [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)

8 changes: 8 additions & 0 deletions src/pages/quickstart/agent.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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` |
Expand Down Expand Up @@ -223,4 +225,10 @@ $ mppx https://mpp.dev/api/ping/paid
title="Wallets"
to="/tools/wallet"
/>
<Card
description="Pay for MPP-enabled services from a Cloudflare Agent"
icon="lucide:cloud"
title="Cloudflare Agents"
to="/partner-sdks/cloudflare-agents"
/>
</Cards>
50 changes: 41 additions & 9 deletions src/pages/sdk/typescript/client/McpClient.wrap.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { Tab, Tabs } from 'vocs'

# `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

Expand All @@ -12,7 +14,8 @@ Wraps an MCP SDK client with automatic payment handling. When a tool call return
```ts
import { Provider } from 'accounts'
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'

const client = new Client({ name: 'my-client', version: '1.0.0' })
await client.connect(transport)
Expand All @@ -36,7 +39,8 @@ const result = await mcp.callTool({ name: 'premium_tool', arguments: {} })

```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' })
Expand All @@ -53,20 +57,29 @@ const result = await mcp.callTool({ name: 'premium_tool', arguments: {} })
</Tab>
</Tabs>

`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<client, methods> = Omit<client, 'callTool'> & {
Expand All @@ -76,6 +89,7 @@ type McpClient<client, methods> = Omit<client, 'callTool'> & {
name: string
_meta?: Record<string, unknown>
},
resultSchema?: CallToolResultSchema,
options?: CallToolOptions<methods>,
) => Promise<CallToolResult>
}
Expand All @@ -95,10 +109,28 @@ type CallToolResult = Awaited<ReturnType<Client['callTool']>> & {

- **Type:** `Pick<Client, 'callTool'>`

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<boolean>`

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<ChallengeCandidate[]>`

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.
9 changes: 9 additions & 0 deletions vocs.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down
Loading