@bankofai/x402-mcp 2.6.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +363 -0
- package/bin/x402-mcp.js +23 -0
- package/bin/x402.js +23 -0
- package/dist/cjs/index.d.ts +971 -0
- package/dist/cjs/index.js +812 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cli/x402-mcp.js +1113 -0
- package/dist/cli/x402-mcp.js.map +1 -0
- package/dist/esm/index.d.mts +971 -0
- package/dist/esm/index.mjs +768 -0
- package/dist/esm/index.mjs.map +1 -0
- package/package.json +76 -0
- package/src/command/cli.ts +58 -0
- package/src/command/mcp-server.ts +122 -0
- package/src/command/runtime.ts +844 -0
package/README.md
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
# @bankofai/x402-mcp
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) integration for the x402 payment protocol. This package enables paid tool calls in MCP servers and automatic payment handling in MCP clients.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @bankofai/x402-mcp @bankofai/x402-core @modelcontextprotocol/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Related packages typically used with this package:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @bankofai/x402-evm @bankofai/x402-tron
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The examples in this repository use SSE transport and `createPaymentWrapper()` on the server side.
|
|
18
|
+
|
|
19
|
+
## CLI
|
|
20
|
+
|
|
21
|
+
This package also ships an `x402` command for Coinbase-style paid HTTP requests.
|
|
22
|
+
|
|
23
|
+
Install globally:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install -g @bankofai/x402-mcp
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Or run from a local project:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx -y -p @bankofai/x402-mcp x402 status
|
|
33
|
+
npx -y -p @bankofai/x402-mcp x402 balance
|
|
34
|
+
npx -y -p @bankofai/x402-mcp x402 pay https://example.com/api/weather
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Common options:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
x402 pay <url> \
|
|
41
|
+
-X POST \
|
|
42
|
+
-d '{"prompt":"hello"}' \
|
|
43
|
+
-q '{"verbose":"true"}' \
|
|
44
|
+
-h '{"X-App":"demo"}' \
|
|
45
|
+
--network nile \
|
|
46
|
+
--asset USDT \
|
|
47
|
+
--pair tron:nile:USDT \
|
|
48
|
+
--max-amount 100000
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Selection priority when an endpoint returns multiple `accepts` options:
|
|
52
|
+
|
|
53
|
+
1. `network + pair/asset`
|
|
54
|
+
2. `network`
|
|
55
|
+
3. first available option
|
|
56
|
+
|
|
57
|
+
`x402 balance` shows native balances and, when known, the default payment token for that network. To inspect a specific payment token balance, pass `--network` and `--asset` (or `--pair`).
|
|
58
|
+
|
|
59
|
+
## MCP Server Quick Start
|
|
60
|
+
|
|
61
|
+
This package also ships a stdio MCP server so MCP hosts can install it directly.
|
|
62
|
+
|
|
63
|
+
Claude Desktop:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
claude mcp add -e TRON_PRIVATE_KEY=xxx -e EVM_PRIVATE_KEY=xxx x402 -- npx -y @bankofai/x402-mcp
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Cursor (`.cursor/mcp.json`):
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"mcpServers": {
|
|
74
|
+
"x402": {
|
|
75
|
+
"command": "npx",
|
|
76
|
+
"args": ["-y", "@bankofai/x402-mcp"],
|
|
77
|
+
"env": {
|
|
78
|
+
"TRON_PRIVATE_KEY": "YOUR_TRON_KEY",
|
|
79
|
+
"EVM_PRIVATE_KEY": "YOUR_EVM_KEY",
|
|
80
|
+
"TRON_GRID_API_KEY": "OPTIONAL_TRONGRID_KEY",
|
|
81
|
+
"BSC_TESTNET_RPC_URL": "OPTIONAL_RPC",
|
|
82
|
+
"BSC_MAINNET_RPC_URL": "OPTIONAL_RPC"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Provided tools:
|
|
90
|
+
|
|
91
|
+
- `x402_status`
|
|
92
|
+
- `x402_balance`
|
|
93
|
+
- `x402_pay`
|
|
94
|
+
|
|
95
|
+
## Quick Start (Recommended)
|
|
96
|
+
|
|
97
|
+
### Server - Using Payment Wrapper
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
101
|
+
import { createPaymentWrapper, x402ResourceServer } from "@bankofai/x402-mcp";
|
|
102
|
+
import { HTTPFacilitatorClient } from "@bankofai/x402-core/server";
|
|
103
|
+
import { ExactEvmScheme } from "@bankofai/x402-evm/exact/server";
|
|
104
|
+
import { z } from "zod";
|
|
105
|
+
|
|
106
|
+
// Create standard MCP server
|
|
107
|
+
const mcpServer = new McpServer({ name: "premium-api", version: "1.0.0" });
|
|
108
|
+
|
|
109
|
+
// Set up x402 for payment handling
|
|
110
|
+
const facilitatorClient = new HTTPFacilitatorClient({ url: "https://x402.org/facilitator" });
|
|
111
|
+
const resourceServer = new x402ResourceServer(facilitatorClient);
|
|
112
|
+
resourceServer.register("eip155:84532", new ExactEvmScheme());
|
|
113
|
+
await resourceServer.initialize();
|
|
114
|
+
|
|
115
|
+
// Build payment requirements
|
|
116
|
+
const accepts = await resourceServer.buildPaymentRequirements({
|
|
117
|
+
scheme: "exact",
|
|
118
|
+
network: "eip155:84532",
|
|
119
|
+
payTo: "0x...",
|
|
120
|
+
price: "$0.10",
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Create payment wrapper with accepts array
|
|
124
|
+
const paid = createPaymentWrapper(resourceServer, {
|
|
125
|
+
accepts,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Register paid tools - wrap handler
|
|
129
|
+
mcpServer.tool(
|
|
130
|
+
"financial_analysis",
|
|
131
|
+
"Advanced AI-powered financial analysis. Costs $0.10.",
|
|
132
|
+
{ ticker: z.string() },
|
|
133
|
+
paid(async (args) => {
|
|
134
|
+
return { content: [{ type: "text", text: `analysis for ${args.ticker}` }] };
|
|
135
|
+
}),
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
// Register free tools - no wrapper needed
|
|
139
|
+
mcpServer.tool("ping", "Health check", {}, async () => ({
|
|
140
|
+
content: [{ type: "text", text: "pong" }],
|
|
141
|
+
}));
|
|
142
|
+
|
|
143
|
+
// Connect to transport
|
|
144
|
+
await mcpServer.connect(transport);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Client - Using Factory Function
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { createx402MCPClient } from "@bankofai/x402-mcp";
|
|
151
|
+
import { ExactEvmScheme } from "@bankofai/x402-evm/exact/client";
|
|
152
|
+
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
153
|
+
|
|
154
|
+
// Create client with factory (simplest approach)
|
|
155
|
+
const client = createx402MCPClient({
|
|
156
|
+
name: "my-agent",
|
|
157
|
+
version: "1.0.0",
|
|
158
|
+
schemes: [{ network: "eip155:84532", client: new ExactEvmScheme(walletAccount) }],
|
|
159
|
+
autoPayment: true,
|
|
160
|
+
onPaymentRequested: async ({ paymentRequired }) => {
|
|
161
|
+
console.log(`Tool requires payment: ${paymentRequired.accepts[0].amount}`);
|
|
162
|
+
return true; // Return false to deny payment
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Connect and use
|
|
167
|
+
const transport = new SSEClientTransport(new URL("http://localhost:4022/sse"));
|
|
168
|
+
await client.connect(transport);
|
|
169
|
+
|
|
170
|
+
const result = await client.callTool("financial_analysis", { ticker: "AAPL" });
|
|
171
|
+
console.log(result.content);
|
|
172
|
+
|
|
173
|
+
if (result.paymentMade) {
|
|
174
|
+
console.log("Payment settled:", result.paymentResponse?.transaction);
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Advanced Features
|
|
179
|
+
|
|
180
|
+
### Production Hooks
|
|
181
|
+
|
|
182
|
+
Add hooks for logging, rate limiting, receipts, and more:
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
const accepts = await resourceServer.buildPaymentRequirements({
|
|
186
|
+
scheme: "exact",
|
|
187
|
+
network: "eip155:84532",
|
|
188
|
+
payTo: "0x...",
|
|
189
|
+
price: "$0.10",
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const paid = createPaymentWrapper(resourceServer, {
|
|
193
|
+
accepts,
|
|
194
|
+
hooks: {
|
|
195
|
+
onBeforeExecution: async ({ toolName, paymentPayload }) => {
|
|
196
|
+
console.log(`Executing ${toolName} for ${paymentPayload.payer}`);
|
|
197
|
+
if (await isRateLimited(paymentPayload.payer)) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
return true;
|
|
201
|
+
},
|
|
202
|
+
onAfterExecution: async ({ toolName, result }) => {
|
|
203
|
+
await metrics.record(toolName, result.isError);
|
|
204
|
+
},
|
|
205
|
+
onAfterSettlement: async ({ toolName, settlement, paymentPayload }) => {
|
|
206
|
+
await sendReceipt(paymentPayload.payer, {
|
|
207
|
+
tool: toolName,
|
|
208
|
+
transaction: settlement.transaction,
|
|
209
|
+
network: settlement.network,
|
|
210
|
+
});
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
mcpServer.tool("search", "Premium search", { query: z.string() }, paid(async () => ({
|
|
216
|
+
content: [{ type: "text", text: "ok" }],
|
|
217
|
+
})));
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Multiple Wrappers with Different Prices
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
const basicAccepts = await resourceServer.buildPaymentRequirements({
|
|
224
|
+
scheme: "exact",
|
|
225
|
+
network: "eip155:84532",
|
|
226
|
+
payTo: "0x...",
|
|
227
|
+
price: "$0.05",
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const premiumAccepts = await resourceServer.buildPaymentRequirements({
|
|
231
|
+
scheme: "exact",
|
|
232
|
+
network: "eip155:84532",
|
|
233
|
+
payTo: "0x...",
|
|
234
|
+
price: "$0.50",
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const paidBasic = createPaymentWrapper(resourceServer, { accepts: basicAccepts });
|
|
238
|
+
const paidPremium = createPaymentWrapper(resourceServer, { accepts: premiumAccepts });
|
|
239
|
+
|
|
240
|
+
mcpServer.tool("basic_search", "...", {}, paidBasic(async () => ({ content: [] })));
|
|
241
|
+
mcpServer.tool("premium_search", "...", {}, paidPremium(async () => ({ content: [] })));
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Multiple Payment Options
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
const exactPayment = await resourceServer.buildPaymentRequirements({
|
|
248
|
+
scheme: "exact",
|
|
249
|
+
network: "eip155:84532",
|
|
250
|
+
payTo: "0x...",
|
|
251
|
+
price: "$0.10",
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const subscriptionPayment = await resourceServer.buildPaymentRequirements({
|
|
255
|
+
scheme: "subscription",
|
|
256
|
+
network: "eip155:1",
|
|
257
|
+
payTo: "0x...",
|
|
258
|
+
price: "$50",
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const paid = createPaymentWrapper(resourceServer, {
|
|
262
|
+
accepts: [exactPayment[0], subscriptionPayment[0]],
|
|
263
|
+
});
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Client - Wrapper Functions
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
270
|
+
import {
|
|
271
|
+
wrapMCPClientWithPayment,
|
|
272
|
+
wrapMCPClientWithPaymentFromConfig,
|
|
273
|
+
} from "@bankofai/x402-mcp";
|
|
274
|
+
import { x402Client } from "@bankofai/x402-core/client";
|
|
275
|
+
import { ExactEvmScheme } from "@bankofai/x402-evm/exact/client";
|
|
276
|
+
|
|
277
|
+
// Option 1: Wrap existing client with existing payment client
|
|
278
|
+
const mcpClient = new Client({ name: "my-agent", version: "1.0.0" });
|
|
279
|
+
const paymentClient = new x402Client()
|
|
280
|
+
.register("eip155:84532", new ExactEvmScheme(walletAccount));
|
|
281
|
+
|
|
282
|
+
const x402Mcp = wrapMCPClientWithPayment(mcpClient, paymentClient, {
|
|
283
|
+
autoPayment: true,
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// Option 2: Wrap existing client with config
|
|
287
|
+
const x402Mcp2 = wrapMCPClientWithPaymentFromConfig(mcpClient, {
|
|
288
|
+
schemes: [{ network: "eip155:84532", client: new ExactEvmScheme(walletAccount) }],
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Client Hooks
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
const client = createx402MCPClient({...});
|
|
296
|
+
|
|
297
|
+
client.onPaymentRequired(async ({ toolName, paymentRequired }) => {
|
|
298
|
+
const cached = await cache.get(toolName);
|
|
299
|
+
if (cached) return { payment: cached };
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
client.onBeforePayment(async ({ paymentRequired }) => {
|
|
303
|
+
await logPaymentAttempt(paymentRequired);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
client.onAfterPayment(async ({ paymentPayload, settleResponse }) => {
|
|
307
|
+
await saveReceipt(settleResponse?.transaction);
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## Payment Flow
|
|
312
|
+
|
|
313
|
+
1. **Client calls tool** → No payment attached
|
|
314
|
+
2. **Server returns 402** → PaymentRequired in structured result (see SDK Limitation below)
|
|
315
|
+
3. **Client creates payment** → Using x402Client
|
|
316
|
+
4. **Client retries with payment** → PaymentPayload in `_meta["x402/payment"]`
|
|
317
|
+
5. **Server verifies & executes** → Tool runs if payment valid
|
|
318
|
+
6. **Server settles payment** → Transaction submitted
|
|
319
|
+
7. **Server returns result** → SettleResponse in `_meta["x402/payment-response"]`
|
|
320
|
+
|
|
321
|
+
## MCP SDK Limitation
|
|
322
|
+
|
|
323
|
+
The x402 MCP transport spec defines payment errors using JSON-RPC's native error format:
|
|
324
|
+
```json
|
|
325
|
+
{ "error": { "code": 402, "data": { /* PaymentRequired */ } } }
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
However, the MCP SDK converts `McpError` exceptions to tool results with `isError: true`, losing the `error.data` field. To work around this, we embed the error structure in the result content:
|
|
329
|
+
|
|
330
|
+
```json
|
|
331
|
+
{
|
|
332
|
+
"content": [{ "type": "text", "text": "{\"x402/error\": {\"code\": 402, \"data\": {...}}}" }],
|
|
333
|
+
"isError": true
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
The client parses this structure to extract PaymentRequired data. This is a pragmatic workaround that maintains compatibility while we track upstream SDK improvements.
|
|
338
|
+
|
|
339
|
+
## Configuration Options
|
|
340
|
+
|
|
341
|
+
### x402MCPClientOptions
|
|
342
|
+
|
|
343
|
+
| Option | Type | Default | Description |
|
|
344
|
+
|--------|------|---------|-------------|
|
|
345
|
+
| `autoPayment` | `boolean` | `true` | Automatically retry with payment on 402 |
|
|
346
|
+
| `onPaymentRequested` | `function` | `() => true` | Hook for human-in-the-loop approval when payment is requested |
|
|
347
|
+
|
|
348
|
+
### PaymentWrapperConfig
|
|
349
|
+
|
|
350
|
+
| Option | Type | Required | Description |
|
|
351
|
+
|--------|------|----------|-------------|
|
|
352
|
+
| `accepts` | `PaymentRequirements[]` | Yes | One or more payment requirements built by `x402ResourceServer.buildPaymentRequirements()` |
|
|
353
|
+
| `resource` | `object` | No | Optional MCP resource metadata |
|
|
354
|
+
| `hooks` | `object` | No | Optional lifecycle hooks for verification, execution, and settlement |
|
|
355
|
+
|
|
356
|
+
## Notes
|
|
357
|
+
|
|
358
|
+
- The currently documented and tested server pattern is: native `McpServer` + `x402ResourceServer` + `createPaymentWrapper()`.
|
|
359
|
+
- The examples in this repository are SSE-based. The client itself is transport-agnostic because it forwards `connect()` to the underlying MCP SDK client, but SSE is the path covered by examples and integration tests.
|
|
360
|
+
|
|
361
|
+
## License
|
|
362
|
+
|
|
363
|
+
Apache-2.0
|
package/bin/x402-mcp.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { spawnSync } = require("node:child_process");
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
|
|
5
|
+
const entryPath = path.resolve(__dirname, "../src/command/mcp-server.ts");
|
|
6
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
7
|
+
const tsxLoaderPath = path.resolve(packageRoot, "node_modules/tsx/dist/loader.mjs");
|
|
8
|
+
|
|
9
|
+
const result = spawnSync(
|
|
10
|
+
process.execPath,
|
|
11
|
+
["--import", tsxLoaderPath, entryPath, ...process.argv.slice(2)],
|
|
12
|
+
{
|
|
13
|
+
stdio: "inherit",
|
|
14
|
+
env: process.env,
|
|
15
|
+
cwd: packageRoot,
|
|
16
|
+
},
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
if (result.error) {
|
|
20
|
+
throw result.error;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
process.exit(result.status ?? 0);
|
package/bin/x402.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { spawnSync } = require("node:child_process");
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
|
|
5
|
+
const cliPath = path.resolve(__dirname, "../src/command/cli.ts");
|
|
6
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
7
|
+
const tsxLoaderPath = path.resolve(packageRoot, "node_modules/tsx/dist/loader.mjs");
|
|
8
|
+
|
|
9
|
+
const result = spawnSync(
|
|
10
|
+
process.execPath,
|
|
11
|
+
["--import", tsxLoaderPath, cliPath, ...process.argv.slice(2)],
|
|
12
|
+
{
|
|
13
|
+
stdio: "inherit",
|
|
14
|
+
env: process.env,
|
|
15
|
+
cwd: packageRoot,
|
|
16
|
+
},
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
if (result.error) {
|
|
20
|
+
throw result.error;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
process.exit(result.status ?? 0);
|