@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 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
@@ -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);