@x402/mcp 2.3.0-alpha

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,351 @@
1
+ # @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 @x402/mcp @x402/core @modelcontextprotocol/sdk
9
+ ```
10
+
11
+ ## Quick Start (Recommended)
12
+
13
+ ### Server - Using Payment Wrapper
14
+
15
+ ```typescript
16
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
17
+ import { createPaymentWrapper, x402ResourceServer } from "@x402/mcp";
18
+ import { HTTPFacilitatorClient } from "@x402/core/server";
19
+ import { ExactEvmScheme } from "@x402/evm/exact/server";
20
+ import { z } from "zod";
21
+
22
+ // Create standard MCP server
23
+ const mcpServer = new McpServer({ name: "premium-api", version: "1.0.0" });
24
+
25
+ // Set up x402 for payment handling
26
+ const facilitatorClient = new HTTPFacilitatorClient({ url: "https://x402.org/facilitator" });
27
+ const resourceServer = new x402ResourceServer(facilitatorClient);
28
+ resourceServer.register("eip155:84532", new ExactEvmScheme());
29
+ await resourceServer.initialize();
30
+
31
+ // Build payment requirements
32
+ const accepts = await resourceServer.buildPaymentRequirements({
33
+ scheme: "exact",
34
+ network: "eip155:84532",
35
+ payTo: "0x...", // Your wallet address
36
+ price: "$0.10",
37
+ });
38
+
39
+ // Create payment wrapper with accepts array
40
+ const paid = createPaymentWrapper(resourceServer, {
41
+ accepts,
42
+ });
43
+
44
+ // Register paid tools - wrap handler
45
+ mcpServer.tool(
46
+ "financial_analysis",
47
+ "Advanced AI-powered financial analysis. Costs $0.10.",
48
+ { ticker: z.string() },
49
+ paid(async (args) => {
50
+ const analysis = await performAnalysis(args.ticker);
51
+ return { content: [{ type: "text", text: analysis }] };
52
+ })
53
+ );
54
+
55
+ // Register free tools - no wrapper needed
56
+ mcpServer.tool("ping", "Health check", {}, async () => ({
57
+ content: [{ type: "text", text: "pong" }],
58
+ }));
59
+
60
+ // Connect to transport
61
+ await mcpServer.connect(transport);
62
+ ```
63
+
64
+ ### Client - Using Factory Function
65
+
66
+ ```typescript
67
+ import { createX402MCPClient } from "@x402/mcp";
68
+ import { ExactEvmScheme } from "@x402/evm/exact/client";
69
+ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
70
+
71
+ // Create client with factory (simplest approach)
72
+ const client = createX402MCPClient({
73
+ name: "my-agent",
74
+ version: "1.0.0",
75
+ schemes: [{ network: "eip155:84532", client: new ExactEvmScheme(walletAccount) }],
76
+ autoPayment: true,
77
+ onPaymentRequested: async ({ paymentRequired }) => {
78
+ console.log(`Tool requires payment: ${paymentRequired.accepts[0].amount}`);
79
+ return true; // Return false to deny payment
80
+ },
81
+ });
82
+
83
+ // Connect and use
84
+ const transport = new SSEClientTransport(new URL("http://localhost:4022/sse"));
85
+ await client.connect(transport);
86
+
87
+ const result = await client.callTool("financial_analysis", { ticker: "AAPL" });
88
+ console.log(result.content);
89
+
90
+ if (result.paymentMade) {
91
+ console.log("Payment settled:", result.paymentResponse?.transaction);
92
+ }
93
+ ```
94
+
95
+ ## Advanced Features
96
+
97
+ ### Production Hooks
98
+
99
+ Add hooks for logging, rate limiting, receipts, and more:
100
+
101
+ ```typescript
102
+ // Build payment requirements
103
+ const accepts = await resourceServer.buildPaymentRequirements({
104
+ scheme: "exact",
105
+ network: "eip155:84532",
106
+ payTo: "0x...",
107
+ price: "$0.10",
108
+ });
109
+
110
+ const paid = createPaymentWrapper(resourceServer, {
111
+ accepts,
112
+ hooks: {
113
+ // Called after payment verification, before tool execution
114
+ // Return false to abort execution
115
+ onBeforeExecution: async ({ toolName, paymentPayload, paymentRequirements }) => {
116
+ console.log(`Executing ${toolName} for ${paymentPayload.payer}`);
117
+
118
+ // Rate limiting example
119
+ if (await isRateLimited(paymentPayload.payer)) {
120
+ console.log("Rate limit exceeded");
121
+ return false; // Abort execution, don't charge
122
+ }
123
+
124
+ return true; // Continue
125
+ },
126
+
127
+ // Called after tool execution, before settlement
128
+ onAfterExecution: async ({ toolName, result, paymentPayload }) => {
129
+ // Log metrics
130
+ await metrics.record(toolName, result.isError);
131
+ },
132
+
133
+ // Called after successful settlement
134
+ onAfterSettlement: async ({ toolName, settlement, paymentPayload }) => {
135
+ // Send receipt to user
136
+ await sendReceipt(paymentPayload.payer, {
137
+ tool: toolName,
138
+ transaction: settlement.transaction,
139
+ network: settlement.network,
140
+ });
141
+ },
142
+ },
143
+ });
144
+
145
+ // All tools using this wrapper inherit the hooks
146
+ mcpServer.tool("search", "Premium search", { query: z.string() },
147
+ paid(async (args) => ({ content: [...] }))
148
+ );
149
+ ```
150
+
151
+ ### Multiple Wrappers with Different Prices
152
+
153
+ Create separate wrappers for different payment tiers:
154
+
155
+ ```typescript
156
+ // Build requirements for different price points
157
+ const basicAccepts = await resourceServer.buildPaymentRequirements({
158
+ scheme: "exact",
159
+ network: "eip155:84532",
160
+ payTo: "0x...",
161
+ price: "$0.05",
162
+ });
163
+
164
+ const premiumAccepts = await resourceServer.buildPaymentRequirements({
165
+ scheme: "exact",
166
+ network: "eip155:84532",
167
+ payTo: "0x...",
168
+ price: "$0.50",
169
+ });
170
+
171
+ // Create wrappers with different prices
172
+ const paidBasic = createPaymentWrapper(resourceServer, { accepts: basicAccepts });
173
+ const paidPremium = createPaymentWrapper(resourceServer, { accepts: premiumAccepts });
174
+
175
+ // Register tools with appropriate pricing
176
+ mcpServer.tool("basic_search", "...", {}, paidBasic(async (args) => ({ content: [...] })));
177
+ mcpServer.tool("premium_search", "...", {}, paidPremium(async (args) => ({ content: [...] })));
178
+ ```
179
+
180
+ ### Multiple Payment Options
181
+
182
+ Support multiple payment methods by including multiple requirements:
183
+
184
+ ```typescript
185
+ // Build requirements for different payment schemes
186
+ const exactPayment = await resourceServer.buildPaymentRequirements({
187
+ scheme: "exact",
188
+ network: "eip155:84532",
189
+ payTo: "0x...",
190
+ price: "$0.10",
191
+ });
192
+
193
+ const subscriptionPayment = await resourceServer.buildPaymentRequirements({
194
+ scheme: "subscription",
195
+ network: "eip155:1",
196
+ payTo: "0x...",
197
+ price: "$50", // Monthly subscription
198
+ });
199
+
200
+ // Client can choose either payment method
201
+ const paid = createPaymentWrapper(resourceServer, {
202
+ accepts: [exactPayment[0], subscriptionPayment[0]],
203
+ });
204
+ ```
205
+
206
+ ### Client - Wrapper Functions
207
+
208
+ ```typescript
209
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
210
+ import { wrapMCPClientWithPayment, wrapMCPClientWithPaymentFromConfig } from "@x402/mcp";
211
+ import { x402Client } from "@x402/core/client";
212
+ import { ExactEvmScheme } from "@x402/evm/exact/client";
213
+
214
+ // Option 1: Wrap existing client with existing payment client
215
+ const mcpClient = new Client({ name: "my-agent", version: "1.0.0" });
216
+ const paymentClient = new x402Client()
217
+ .register("eip155:84532", new ExactEvmScheme(walletAccount));
218
+
219
+ const x402Mcp = wrapMCPClientWithPayment(mcpClient, paymentClient, {
220
+ autoPayment: true,
221
+ });
222
+
223
+ // Option 2: Wrap existing client with config
224
+ const x402Mcp2 = wrapMCPClientWithPaymentFromConfig(mcpClient, {
225
+ schemes: [{ network: "eip155:84532", client: new ExactEvmScheme(walletAccount) }],
226
+ });
227
+ ```
228
+
229
+ ## Payment Flow
230
+
231
+ 1. **Client calls tool** → No payment attached
232
+ 2. **Server returns 402** → PaymentRequired in structured result (see SDK Limitation below)
233
+ 3. **Client creates payment** → Using x402Client
234
+ 4. **Client retries with payment** → PaymentPayload in `_meta["x402/payment"]`
235
+ 5. **Server verifies & executes** → Tool runs if payment valid
236
+ 6. **Server settles payment** → Transaction submitted
237
+ 7. **Server returns result** → SettleResponse in `_meta["x402/payment-response"]`
238
+
239
+ ## MCP SDK Limitation
240
+
241
+ The x402 MCP transport spec defines payment errors using JSON-RPC's native error format:
242
+ ```json
243
+ { "error": { "code": 402, "data": { /* PaymentRequired */ } } }
244
+ ```
245
+
246
+ 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:
247
+
248
+ ```json
249
+ {
250
+ "content": [{ "type": "text", "text": "{\"x402/error\": {\"code\": 402, \"data\": {...}}}" }],
251
+ "isError": true
252
+ }
253
+ ```
254
+
255
+ The client parses this structure to extract PaymentRequired data. This is a pragmatic workaround that maintains compatibility while we track upstream SDK improvements.
256
+
257
+ ## Configuration Options
258
+
259
+ ### x402MCPClientOptions
260
+
261
+ | Option | Type | Default | Description |
262
+ |--------|------|---------|-------------|
263
+ | `autoPayment` | `boolean` | `true` | Automatically retry with payment on 402 |
264
+ | `onPaymentRequested` | `function` | `() => true` | Hook for human-in-the-loop approval when payment is requested |
265
+
266
+ ### X402MCPServerConfig (Factory)
267
+
268
+ | Option | Type | Default | Description |
269
+ |--------|------|---------|-------------|
270
+ | `name` | `string` | Required | MCP server name |
271
+ | `version` | `string` | Required | MCP server version |
272
+ | `facilitator` | `string \| FacilitatorClient` | Default facilitator | Facilitator for payment processing |
273
+ | `schemes` | `SchemeRegistration[]` | `[]` | Payment scheme registrations |
274
+ | `syncFacilitatorOnStart` | `boolean` | `true` | Initialize facilitator immediately |
275
+
276
+ ### MCPToolPaymentConfig
277
+
278
+ | Option | Type | Required | Description |
279
+ |--------|------|----------|-------------|
280
+ | `scheme` | `string` | Yes | Payment scheme (e.g., "exact") |
281
+ | `network` | `Network` | Yes | CAIP-2 network ID (e.g., "eip155:84532") |
282
+ | `price` | `Price` | Yes | Price (e.g., "$0.10" or "1000000") |
283
+ | `payTo` | `string` | Yes | Recipient wallet address |
284
+ | `maxTimeoutSeconds` | `number` | No | Payment timeout (default: 60) |
285
+ | `extra` | `object` | No | Scheme-specific parameters (e.g., EIP-712 domain) |
286
+ | `resource` | `object` | No | Resource metadata |
287
+
288
+ ### PaymentWrapperConfig (for createPaymentWrapper)
289
+
290
+ | Option | Type | Required | Description |
291
+ |--------|------|----------|-------------|
292
+ | `scheme` | `string` | Yes | Payment scheme (e.g., "exact") |
293
+ | `network` | `Network` | Yes | CAIP-2 network ID (e.g., "eip155:84532") |
294
+ | `payTo` | `string` | Yes | Recipient wallet address |
295
+ | `price` | `Price` | No | Price - omit to specify per-tool |
296
+ | `maxTimeoutSeconds` | `number` | No | Payment timeout (default: 60) |
297
+ | `extra` | `object` | No | Scheme-specific parameters |
298
+ | `resource` | `object` | No | Resource metadata |
299
+
300
+ ## Hooks
301
+
302
+ ### Client Hooks
303
+
304
+ ```typescript
305
+ const client = createX402MCPClient({...});
306
+
307
+ // Called when a 402 is received (before payment)
308
+ // Return { payment } to use custom payment, { abort: true } to stop
309
+ client.onPaymentRequired(async ({ toolName, paymentRequired }) => {
310
+ const cached = await cache.get(toolName);
311
+ if (cached) return { payment: cached };
312
+ });
313
+
314
+ // Called before payment is created
315
+ client.onBeforePayment(async ({ paymentRequired }) => {
316
+ await logPaymentAttempt(paymentRequired);
317
+ });
318
+
319
+ // Called after payment is submitted
320
+ client.onAfterPayment(async ({ paymentPayload, settleResponse }) => {
321
+ await saveReceipt(settleResponse.transaction);
322
+ });
323
+ ```
324
+
325
+ ### Server Hooks
326
+
327
+ ```typescript
328
+ const server = createX402MCPServer({...});
329
+
330
+ // Called after verification, before tool execution
331
+ // Return false to abort and return 402
332
+ server.onBeforeExecution(async ({ toolName, paymentPayload }) => {
333
+ if (isBlocked(paymentPayload.signer)) {
334
+ return false; // Aborts execution
335
+ }
336
+ });
337
+
338
+ // Called after tool execution, before settlement
339
+ server.onAfterExecution(async ({ toolName, result }) => {
340
+ metrics.recordExecution(toolName, result.isError);
341
+ });
342
+
343
+ // Called after successful settlement
344
+ server.onAfterSettlement(async ({ toolName, settlement }) => {
345
+ await logTransaction(toolName, settlement.transaction);
346
+ });
347
+ ```
348
+
349
+ ## License
350
+
351
+ Apache-2.0