x402-proxy 0.5.2 → 0.6.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/dist/bin/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { a as buildX402Client, c as error, d as warn, f as appendHistory, g as readHistory, h as formatTxLine, i as walletInfoCommand, l as info, m as displayNetwork, o as resolveWallet, p as calcSpend, s as dim, u as isTTY } from "../wallet-BMYYtAP6.js";
3
- import { c as isConfigured, i as ensureConfigDir, l as loadConfig, o as getHistoryPath, u as loadWalletFile } from "../derive-CISr_ond.js";
4
- import { n as setupCommand } from "../setup-gla-Qyqi.js";
5
- import { n as statusCommand } from "../status-BoH_1kIH.js";
2
+ import { _ as formatTxLine, c as resolveWallet, d as info, f as isTTY, g as displayNetwork, h as calcSpend, l as dim, m as appendHistory, o as walletInfoCommand, p as warn, s as buildX402Client, u as error, v as readHistory } from "../wallet-DxKCHa7U.js";
3
+ import { a as getHistoryPath, c as loadConfig, l as loadWalletFile, s as isConfigured } from "../derive-ibF2UinV.js";
4
+ import { n as setupCommand } from "../setup-hJGkO2Lo.js";
5
+ import { n as statusCommand } from "../status-JNGv2Ghp.js";
6
6
  import { buildApplication, buildCommand, buildRouteMap, run } from "@stricli/core";
7
7
  import pc from "picocolors";
8
8
  import { decodePaymentResponseHeader, wrapFetchWithPayment } from "@x402/fetch";
@@ -11,25 +11,35 @@ import * as prompts from "@clack/prompts";
11
11
 
12
12
  //#region src/handler.ts
13
13
  /**
14
+ * Detect which payment protocols a 402 response advertises.
15
+ * - x402: PAYMENT-REQUIRED or X-PAYMENT-REQUIRED header
16
+ * - MPP: WWW-Authenticate header with Payment scheme
17
+ */
18
+ function detectProtocols(response) {
19
+ const pr = response.headers.get("PAYMENT-REQUIRED") ?? response.headers.get("X-PAYMENT-REQUIRED");
20
+ const wwwAuth = response.headers.get("WWW-Authenticate");
21
+ return {
22
+ x402: !!pr,
23
+ mpp: !!(wwwAuth && /^Payment\b/i.test(wwwAuth.trim()))
24
+ };
25
+ }
26
+ /**
14
27
  * Extract the on-chain transaction signature from an x402 payment response header.
15
28
  */
16
29
  function extractTxSignature(response) {
17
- const header = response.headers.get("PAYMENT-RESPONSE") ?? response.headers.get("X-PAYMENT-RESPONSE");
18
- if (!header) return void 0;
19
- try {
20
- return decodePaymentResponseHeader(header).transaction ?? void 0;
30
+ const x402Header = response.headers.get("PAYMENT-RESPONSE") ?? response.headers.get("X-PAYMENT-RESPONSE");
31
+ if (x402Header) try {
32
+ return decodePaymentResponseHeader(x402Header).transaction ?? void 0;
33
+ } catch {}
34
+ const mppHeader = response.headers.get("Payment-Receipt");
35
+ if (mppHeader) try {
36
+ return JSON.parse(Buffer.from(mppHeader, "base64url").toString()).reference ?? void 0;
21
37
  } catch {
22
38
  return;
23
39
  }
24
40
  }
25
41
  /**
26
42
  * Create an x402 proxy handler that wraps fetch with automatic payment.
27
- *
28
- * Chain-agnostic: accepts a pre-configured x402Client with any registered
29
- * schemes (SVM, EVM, etc). The handler captures payment info via the
30
- * onAfterPaymentCreation hook. Callers use `x402Fetch` for requests that
31
- * may require x402 payment, and `shiftPayment` to retrieve captured
32
- * payment info after each call.
33
43
  */
34
44
  function createX402ProxyHandler(opts) {
35
45
  const { client } = opts;
@@ -37,6 +47,7 @@ function createX402ProxyHandler(opts) {
37
47
  client.onAfterPaymentCreation(async (hookCtx) => {
38
48
  const raw = hookCtx.selectedRequirements.amount;
39
49
  paymentQueue.push({
50
+ protocol: "x402",
40
51
  network: hookCtx.selectedRequirements.network,
41
52
  payTo: hookCtx.selectedRequirements.payTo,
42
53
  amount: raw,
@@ -48,6 +59,80 @@ function createX402ProxyHandler(opts) {
48
59
  shiftPayment: () => paymentQueue.shift()
49
60
  };
50
61
  }
62
+ const TEMPO_NETWORK = "eip155:4217";
63
+ /**
64
+ * Create an MPP proxy handler using mppx client.
65
+ * Dynamically imports mppx/client to keep startup fast.
66
+ */
67
+ async function createMppProxyHandler(opts) {
68
+ const { Mppx, tempo } = await import("mppx/client");
69
+ const { privateKeyToAccount } = await import("viem/accounts");
70
+ const account = privateKeyToAccount(opts.evmKey);
71
+ const maxDeposit = opts.maxDeposit ?? "1";
72
+ const paymentQueue = [];
73
+ const mppx = Mppx.create({
74
+ methods: [tempo({
75
+ account,
76
+ maxDeposit
77
+ })],
78
+ polyfill: false
79
+ });
80
+ let session;
81
+ return {
82
+ async fetch(input, init) {
83
+ const response = await mppx.fetch(typeof input === "string" ? input : input.toString(), init);
84
+ const receiptHeader = response.headers.get("Payment-Receipt");
85
+ if (receiptHeader) try {
86
+ const receipt = JSON.parse(Buffer.from(receiptHeader, "base64url").toString());
87
+ paymentQueue.push({
88
+ protocol: "mpp",
89
+ network: TEMPO_NETWORK,
90
+ receipt
91
+ });
92
+ } catch {
93
+ paymentQueue.push({
94
+ protocol: "mpp",
95
+ network: TEMPO_NETWORK
96
+ });
97
+ }
98
+ return response;
99
+ },
100
+ async sse(input, init) {
101
+ session ??= tempo.session({
102
+ account,
103
+ maxDeposit
104
+ });
105
+ const url = typeof input === "string" ? input : input.toString();
106
+ const iterable = await session.sse(url, init);
107
+ paymentQueue.push({
108
+ protocol: "mpp",
109
+ network: TEMPO_NETWORK,
110
+ intent: "session"
111
+ });
112
+ return iterable;
113
+ },
114
+ shiftPayment: () => paymentQueue.shift(),
115
+ async close() {
116
+ if (session?.opened) {
117
+ const receipt = await session.close();
118
+ if (receipt) paymentQueue.push({
119
+ protocol: "mpp",
120
+ network: TEMPO_NETWORK,
121
+ intent: "session",
122
+ channelId: session.channelId ?? void 0,
123
+ receipt: {
124
+ method: receipt.method,
125
+ reference: receipt.reference,
126
+ status: receipt.status,
127
+ timestamp: receipt.timestamp,
128
+ acceptedCumulative: receipt.acceptedCumulative,
129
+ txHash: receipt.txHash
130
+ }
131
+ });
132
+ }
133
+ }
134
+ };
135
+ }
51
136
 
52
137
  //#endregion
53
138
  //#region src/commands/fetch.ts
@@ -96,7 +181,13 @@ Examples:
96
181
  },
97
182
  network: {
98
183
  kind: "parsed",
99
- brief: "Require specific network (base, solana)",
184
+ brief: "Require specific network (base, solana, tempo)",
185
+ parse: String,
186
+ optional: true
187
+ },
188
+ protocol: {
189
+ kind: "parsed",
190
+ brief: "Payment protocol (x402, mpp)",
100
191
  parse: String,
101
192
  optional: true
102
193
  },
@@ -118,7 +209,7 @@ Examples:
118
209
  async func(flags, url) {
119
210
  if (!url) {
120
211
  if (isConfigured()) {
121
- const { displayStatus } = await import("../status-BVIU3-b6.js");
212
+ const { displayStatus } = await import("../status-w5y-fhhe.js");
122
213
  await displayStatus();
123
214
  console.log();
124
215
  console.log(pc.dim(" Commands:"));
@@ -170,28 +261,24 @@ Examples:
170
261
  process.exit(1);
171
262
  }
172
263
  dim(" No wallet found. Let's set one up first.\n");
173
- const { runSetup } = await import("../setup-Crq9TylJ.js");
264
+ const { runSetup } = await import("../setup-j_xQ14-4.js");
174
265
  await runSetup();
175
266
  console.log();
176
267
  wallet = resolveWallet();
177
268
  if (wallet.source === "none") return;
178
269
  }
179
270
  const config = loadConfig();
271
+ const resolvedProtocol = flags.protocol ?? config?.preferredProtocol;
272
+ const maxDeposit = config?.mppSessionBudget ?? "1";
180
273
  let preferredNetwork = config?.defaultNetwork;
181
274
  if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
182
- const { fetchEvmBalances, fetchSolanaBalances } = await import("../wallet-BoY0fgX7.js");
183
- const [evmBal, solBal] = await Promise.allSettled([fetchEvmBalances(wallet.evmAddress), fetchSolanaBalances(wallet.solanaAddress)]);
184
- const evmUsdc = evmBal.status === "fulfilled" ? Number(evmBal.value?.usdc ?? 0) : 0;
185
- const solUsdc = solBal.status === "fulfilled" ? Number(solBal.value?.usdc ?? 0) : 0;
275
+ const { fetchAllBalances } = await import("../wallet-DjixXCHy.js");
276
+ const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
277
+ const evmUsdc = balances.evm ? Number(balances.evm.usdc) : 0;
278
+ const solUsdc = balances.sol ? Number(balances.sol.usdc) : 0;
186
279
  if (evmUsdc > solUsdc) preferredNetwork = "base";
187
280
  else if (solUsdc > evmUsdc) preferredNetwork = "solana";
188
281
  }
189
- const { x402Fetch, shiftPayment } = createX402ProxyHandler({ client: await buildX402Client(wallet, {
190
- preferredNetwork,
191
- network: flags.network,
192
- spendLimitDaily: config?.spendLimitDaily,
193
- spendLimitPerTx: config?.spendLimitPerTx
194
- }) });
195
282
  const headers = new Headers();
196
283
  if (flags.header) for (const h of flags.header) {
197
284
  const idx = h.indexOf(":");
@@ -206,16 +293,102 @@ Examples:
206
293
  if (isTTY()) dim(` ${method} ${parsedUrl.toString()}`);
207
294
  const startMs = Date.now();
208
295
  let response;
296
+ let x402Payment;
297
+ let mppPayment;
298
+ let usedProtocol;
209
299
  try {
210
- response = await x402Fetch(parsedUrl.toString(), init);
300
+ if (resolvedProtocol === "mpp") {
301
+ if (!wallet.evmKey) {
302
+ error("MPP requires an EVM wallet. Configure one with: npx x402-proxy setup");
303
+ process.exit(1);
304
+ }
305
+ const mppHandler = await createMppProxyHandler({
306
+ evmKey: wallet.evmKey,
307
+ maxDeposit
308
+ });
309
+ if (flags.body != null && /"stream"\s*:\s*true/.test(flags.body)) {
310
+ try {
311
+ const tokens = await mppHandler.sse(parsedUrl.toString(), init);
312
+ for await (const token of tokens) process.stdout.write(token);
313
+ } finally {
314
+ await mppHandler.close();
315
+ }
316
+ mppPayment = mppHandler.shiftPayment();
317
+ usedProtocol = "mpp";
318
+ const elapsedMs = Date.now() - startMs;
319
+ if (mppPayment && isTTY()) info(` MPP session (${displayNetwork(mppPayment.network)})`);
320
+ if (isTTY()) dim(` Streamed (${elapsedMs}ms)`);
321
+ if (mppPayment) {
322
+ const record = {
323
+ t: Date.now(),
324
+ ok: true,
325
+ kind: "mpp_payment",
326
+ net: mppPayment.network,
327
+ from: wallet.evmAddress ?? "unknown",
328
+ tx: mppPayment.receipt?.reference,
329
+ amount: void 0,
330
+ token: "USDC",
331
+ ms: elapsedMs,
332
+ label: parsedUrl.hostname
333
+ };
334
+ appendHistory(getHistoryPath(), record);
335
+ }
336
+ if (isTTY()) process.stdout.write("\n");
337
+ return;
338
+ }
339
+ try {
340
+ response = await mppHandler.fetch(parsedUrl.toString(), init);
341
+ } finally {
342
+ await mppHandler.close();
343
+ }
344
+ mppPayment = mppHandler.shiftPayment();
345
+ usedProtocol = "mpp";
346
+ } else if (resolvedProtocol === "x402") {
347
+ const handler = createX402ProxyHandler({ client: await buildX402Client(wallet, {
348
+ preferredNetwork,
349
+ network: flags.network,
350
+ spendLimitDaily: config?.spendLimitDaily,
351
+ spendLimitPerTx: config?.spendLimitPerTx
352
+ }) });
353
+ response = await handler.x402Fetch(parsedUrl.toString(), init);
354
+ x402Payment = handler.shiftPayment();
355
+ usedProtocol = "x402";
356
+ } else {
357
+ const handler = createX402ProxyHandler({ client: await buildX402Client(wallet, {
358
+ preferredNetwork,
359
+ network: flags.network,
360
+ spendLimitDaily: config?.spendLimitDaily,
361
+ spendLimitPerTx: config?.spendLimitPerTx
362
+ }) });
363
+ response = await handler.x402Fetch(parsedUrl.toString(), init);
364
+ x402Payment = handler.shiftPayment();
365
+ usedProtocol = "x402";
366
+ if (response.status === 402 && wallet.evmKey) {
367
+ if (detectProtocols(response).mpp) {
368
+ const mppHandler = await createMppProxyHandler({
369
+ evmKey: wallet.evmKey,
370
+ maxDeposit
371
+ });
372
+ try {
373
+ response = await mppHandler.fetch(parsedUrl.toString(), init);
374
+ } finally {
375
+ await mppHandler.close();
376
+ }
377
+ mppPayment = mppHandler.shiftPayment();
378
+ x402Payment = void 0;
379
+ usedProtocol = "mpp";
380
+ }
381
+ }
382
+ }
211
383
  } catch (err) {
212
384
  error(`Request failed: ${err instanceof Error ? err.message : String(err)}`);
213
385
  process.exit(1);
214
386
  }
215
387
  const elapsedMs = Date.now() - startMs;
216
- const payment = shiftPayment();
388
+ const payment = x402Payment ?? mppPayment;
217
389
  const txSig = extractTxSignature(response);
218
390
  if (response.status === 402 && isTTY()) {
391
+ const detected = detectProtocols(response);
219
392
  const prHeader = response.headers.get("PAYMENT-REQUIRED") ?? response.headers.get("X-PAYMENT-REQUIRED");
220
393
  let accepts = [];
221
394
  if (prHeader) try {
@@ -230,19 +403,14 @@ Examples:
230
403
  }
231
404
  const hasEvm = accepts.some((a) => a.network.startsWith("eip155:"));
232
405
  const hasSolana = accepts.some((a) => a.network.startsWith("solana:"));
406
+ const hasMpp = detected.mpp;
233
407
  const hasOther = accepts.some((a) => !a.network.startsWith("eip155:") && !a.network.startsWith("solana:"));
234
- const { fetchEvmBalances, fetchSolanaBalances } = await import("../wallet-BoY0fgX7.js");
235
- let evmUsdc = 0;
236
- let solUsdc = 0;
237
- if (hasEvm && wallet.evmAddress) try {
238
- const bal = await fetchEvmBalances(wallet.evmAddress);
239
- evmUsdc = Number(bal.usdc);
240
- } catch {}
241
- if (hasSolana && wallet.solanaAddress) try {
242
- const bal = await fetchSolanaBalances(wallet.solanaAddress);
243
- solUsdc = Number(bal.usdc);
244
- } catch {}
245
- if (hasEvm && evmUsdc >= costNum || hasSolana && solUsdc >= costNum) {
408
+ const { fetchAllBalances } = await import("../wallet-DjixXCHy.js");
409
+ const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
410
+ const evmUsdc = hasEvm && balances.evm ? Number(balances.evm.usdc) : 0;
411
+ const solUsdc = hasSolana && balances.sol ? Number(balances.sol.usdc) : 0;
412
+ const tempoUsdc = hasMpp && balances.tempo ? Number(balances.tempo.usdc) : 0;
413
+ if (hasEvm && evmUsdc >= costNum || hasSolana && solUsdc >= costNum || hasMpp && tempoUsdc >= costNum) {
246
414
  let serverReason;
247
415
  try {
248
416
  const body = await response.text();
@@ -257,24 +425,30 @@ Examples:
257
425
  else dim(" Payment was not attempted despite sufficient balance.");
258
426
  if (serverReason) dim(` Reason: ${serverReason}`);
259
427
  if (hasEvm && wallet.evmAddress && evmUsdc > 0) console.error(` Base: ${pc.cyan(wallet.evmAddress)} ${pc.dim(`(${evmUsdc.toFixed(4)} USDC)`)}`);
428
+ if (hasMpp && wallet.evmAddress && tempoUsdc > 0) console.error(` Tempo: ${pc.cyan(wallet.evmAddress)} ${pc.dim(`(${tempoUsdc.toFixed(4)} USDC)`)}`);
260
429
  if (hasSolana && wallet.solanaAddress && solUsdc > 0) console.error(` Solana: ${pc.cyan(wallet.solanaAddress)} ${pc.dim(`(${solUsdc.toFixed(4)} USDC)`)}`);
261
430
  console.error();
262
431
  dim(" This may be a temporary server-side issue. Try again in a moment.");
263
432
  console.error();
264
433
  } else {
265
434
  error(`Payment required: ${costStr} USDC`);
266
- if (hasEvm || hasSolana) {
435
+ if (hasEvm || hasSolana || hasMpp) {
267
436
  console.error();
268
437
  dim(" Fund your wallet with USDC:");
269
438
  if (hasEvm && wallet.evmAddress) {
270
439
  const balHint = evmUsdc > 0 ? pc.dim(` (${evmUsdc.toFixed(4)} USDC)`) : "";
271
440
  console.error(` Base: ${pc.cyan(wallet.evmAddress)}${balHint}`);
272
441
  }
442
+ if (hasMpp && wallet.evmAddress) {
443
+ const balHint = tempoUsdc > 0 ? pc.dim(` (${tempoUsdc.toFixed(4)} USDC)`) : "";
444
+ console.error(` Tempo: ${pc.cyan(wallet.evmAddress)}${balHint}`);
445
+ }
273
446
  if (hasSolana && wallet.solanaAddress) {
274
447
  const balHint = solUsdc > 0 ? pc.dim(` (${solUsdc.toFixed(4)} USDC)`) : "";
275
448
  console.error(` Solana: ${pc.cyan(wallet.solanaAddress)}${balHint}`);
276
449
  }
277
450
  if (hasEvm && !wallet.evmAddress) dim(" Base: endpoint accepts EVM but no EVM wallet configured");
451
+ if (hasMpp && !wallet.evmAddress) dim(" Tempo: endpoint accepts MPP but no EVM wallet configured");
278
452
  if (hasSolana && !wallet.solanaAddress) dim(" Solana: endpoint accepts Solana but no Solana wallet configured");
279
453
  } else if (hasOther) {
280
454
  const networks = [...new Set(accepts.map((a) => a.network))].join(", ");
@@ -289,21 +463,34 @@ Examples:
289
463
  return;
290
464
  }
291
465
  if (payment && isTTY()) {
292
- info(` Payment: ${payment.amount ? (Number(payment.amount) / 1e6).toFixed(4) : "?"} USDC (${displayNetwork(payment.network ?? "unknown")})`);
466
+ if (usedProtocol === "mpp" && mppPayment) info(` Payment: MPP (${displayNetwork(mppPayment.network)})`);
467
+ else if (x402Payment) info(` Payment: ${x402Payment.amount ? (Number(x402Payment.amount) / 1e6).toFixed(4) : "?"} USDC (${displayNetwork(x402Payment.network ?? "unknown")})`);
293
468
  if (txSig) dim(` Tx: ${txSig}`);
294
469
  }
295
470
  if (isTTY()) dim(` ${response.status} ${response.statusText} (${elapsedMs}ms)`);
296
- if (payment) {
297
- ensureConfigDir();
471
+ if (x402Payment) {
298
472
  const record = {
299
473
  t: Date.now(),
300
474
  ok: response.ok,
301
475
  kind: "x402_payment",
302
- net: payment.network ?? "unknown",
476
+ net: x402Payment.network ?? "unknown",
303
477
  from: wallet.evmAddress ?? wallet.solanaAddress ?? "unknown",
304
- to: payment.payTo,
478
+ to: x402Payment.payTo,
305
479
  tx: txSig,
306
- amount: payment.amount ? Number(payment.amount) / 1e6 : void 0,
480
+ amount: x402Payment.amount ? Number(x402Payment.amount) / 1e6 : void 0,
481
+ token: "USDC",
482
+ ms: elapsedMs,
483
+ label: parsedUrl.hostname
484
+ };
485
+ appendHistory(getHistoryPath(), record);
486
+ } else if (mppPayment) {
487
+ const record = {
488
+ t: Date.now(),
489
+ ok: response.ok,
490
+ kind: "mpp_payment",
491
+ net: mppPayment.network,
492
+ from: wallet.evmAddress ?? "unknown",
493
+ tx: mppPayment.receipt?.reference ?? txSig,
307
494
  token: "USDC",
308
495
  ms: elapsedMs,
309
496
  label: parsedUrl.hostname
@@ -330,8 +517,8 @@ Examples:
330
517
  //#region src/commands/mcp.ts
331
518
  const mcpCommand = buildCommand({
332
519
  docs: {
333
- brief: "Start MCP stdio proxy with x402 payment",
334
- fullDescription: `Start an MCP stdio proxy with automatic x402 payment for AI agents.
520
+ brief: "Start MCP stdio proxy with automatic payment",
521
+ fullDescription: `Start an MCP stdio proxy with automatic payment (x402 or MPP) for AI agents.
335
522
 
336
523
  Add to your MCP client config (Claude, Cursor, etc.):
337
524
  "command": "npx",
@@ -354,7 +541,13 @@ Add to your MCP client config (Claude, Cursor, etc.):
354
541
  },
355
542
  network: {
356
543
  kind: "parsed",
357
- brief: "Require specific network (base, solana)",
544
+ brief: "Require specific network (base, solana, tempo)",
545
+ parse: String,
546
+ optional: true
547
+ },
548
+ protocol: {
549
+ kind: "parsed",
550
+ brief: "Payment protocol (x402, mpp)",
358
551
  parse: String,
359
552
  optional: true
360
553
  }
@@ -380,132 +573,244 @@ Add to your MCP client config (Claude, Cursor, etc.):
380
573
  if (wallet.evmAddress) dim(` EVM: ${wallet.evmAddress}`);
381
574
  if (wallet.solanaAddress) dim(` Solana: ${wallet.solanaAddress}`);
382
575
  const config = loadConfig();
383
- let preferredNetwork = config?.defaultNetwork;
384
- if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
385
- const { fetchEvmBalances, fetchSolanaBalances } = await import("../wallet-BoY0fgX7.js");
386
- const [evmBal, solBal] = await Promise.allSettled([fetchEvmBalances(wallet.evmAddress), fetchSolanaBalances(wallet.solanaAddress)]);
387
- const evmUsdc = evmBal.status === "fulfilled" ? Number(evmBal.value?.usdc ?? 0) : 0;
388
- const solUsdc = solBal.status === "fulfilled" ? Number(solBal.value?.usdc ?? 0) : 0;
389
- if (evmUsdc > solUsdc) preferredNetwork = "base";
390
- else if (solUsdc > evmUsdc) preferredNetwork = "solana";
391
- }
392
- const x402PaymentClient = await buildX402Client(wallet, {
393
- preferredNetwork,
394
- network: flags.network,
395
- spendLimitDaily: config?.spendLimitDaily,
396
- spendLimitPerTx: config?.spendLimitPerTx
397
- });
576
+ const resolvedProtocol = flags.protocol ?? config?.preferredProtocol ?? "x402";
398
577
  const { Client } = await import("@modelcontextprotocol/sdk/client/index.js");
399
578
  const { SSEClientTransport } = await import("@modelcontextprotocol/sdk/client/sse.js");
400
579
  const { StreamableHTTPClientTransport } = await import("@modelcontextprotocol/sdk/client/streamableHttp.js");
401
580
  const { Server } = await import("@modelcontextprotocol/sdk/server/index.js");
402
581
  const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
403
- const { x402MCPClient } = await import("@x402/mcp");
404
- const remoteClient = new Client({
405
- name: "x402-proxy",
406
- version: "0.5.2"
407
- });
408
- const x402Mcp = new x402MCPClient(remoteClient, x402PaymentClient, {
409
- autoPayment: true,
410
- onPaymentRequested: (ctx) => {
411
- const accept = ctx.paymentRequired.accepts?.[0];
412
- if (accept) warn(` Payment: ${accept.amount ? (Number(accept.amount) / 1e6).toFixed(4) : "?"} USDC on ${displayNetwork(accept.network)} for tool "${ctx.toolName}"`);
413
- return true;
582
+ const { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ToolListChangedNotificationSchema, ResourceListChangedNotificationSchema } = await import("@modelcontextprotocol/sdk/types.js");
583
+ async function connectTransport(target) {
584
+ try {
585
+ const transport = new StreamableHTTPClientTransport(new URL(remoteUrl));
586
+ await target.connect(transport);
587
+ dim(" Connected via StreamableHTTP");
588
+ return;
589
+ } catch {}
590
+ try {
591
+ const transport = new SSEClientTransport(new URL(remoteUrl));
592
+ await target.connect(transport);
593
+ dim(" Connected via SSE");
594
+ } catch (err) {
595
+ error(`Failed to connect to ${remoteUrl}: ${err instanceof Error ? err.message : String(err)}`);
596
+ process.exit(1);
414
597
  }
415
- });
416
- x402Mcp.onAfterPayment(async (ctx) => {
417
- ensureConfigDir();
418
- const accepted = ctx.paymentPayload.accepted;
419
- const tx = ctx.settleResponse?.transaction;
420
- const record = {
421
- t: Date.now(),
422
- ok: true,
423
- kind: "x402_payment",
424
- net: accepted?.network ?? "unknown",
425
- from: wallet.evmAddress ?? wallet.solanaAddress ?? "unknown",
426
- to: accepted?.payTo,
427
- tx: typeof tx === "string" ? tx : void 0,
428
- amount: accepted?.amount ? Number(accepted.amount) / 1e6 : void 0,
429
- token: "USDC",
430
- label: `mcp:${ctx.toolName}`
431
- };
432
- appendHistory(getHistoryPath(), record);
433
- });
434
- let connected = false;
435
- try {
436
- const transport = new StreamableHTTPClientTransport(new URL(remoteUrl));
437
- await x402Mcp.connect(transport);
438
- connected = true;
439
- dim(" Connected via StreamableHTTP");
440
- } catch {}
441
- if (!connected) try {
442
- const transport = new SSEClientTransport(new URL(remoteUrl));
443
- await x402Mcp.connect(transport);
444
- connected = true;
445
- dim(" Connected via SSE");
446
- } catch (err) {
447
- error(`Failed to connect to ${remoteUrl}: ${err instanceof Error ? err.message : String(err)}`);
448
- process.exit(1);
449
598
  }
450
- let { tools } = await x402Mcp.listTools();
451
- dim(` ${tools.length} tools available`);
452
- let remoteResources = [];
453
- try {
454
- remoteResources = (await x402Mcp.listResources()).resources;
455
- if (remoteResources.length > 0) dim(` ${remoteResources.length} resources available`);
456
- } catch {
457
- dim(" Resources not available from remote");
458
- }
459
- const localServer = new Server({
460
- name: "x402-proxy",
461
- version: "0.5.2"
462
- }, { capabilities: {
463
- tools: tools.length > 0 ? {} : void 0,
464
- resources: remoteResources.length > 0 ? {} : void 0
465
- } });
466
- const { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ToolListChangedNotificationSchema, ResourceListChangedNotificationSchema } = await import("@modelcontextprotocol/sdk/types.js");
467
- localServer.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: tools.map((t) => ({
468
- name: t.name,
469
- description: t.description,
470
- inputSchema: t.inputSchema,
471
- annotations: t.annotations
472
- })) }));
473
- localServer.setRequestHandler(CallToolRequestSchema, async (request) => {
474
- const { name, arguments: args } = request.params;
475
- const result = await x402Mcp.callTool(name, args ?? {});
476
- return {
477
- content: result.content,
478
- isError: result.isError
599
+ if (resolvedProtocol === "mpp") await startMppProxy();
600
+ else await startX402Proxy();
601
+ async function startX402Proxy() {
602
+ let preferredNetwork = config?.defaultNetwork;
603
+ if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
604
+ const { fetchAllBalances } = await import("../wallet-DjixXCHy.js");
605
+ const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
606
+ const evmUsdc = balances.evm ? Number(balances.evm.usdc) : 0;
607
+ const solUsdc = balances.sol ? Number(balances.sol.usdc) : 0;
608
+ if (evmUsdc > solUsdc) preferredNetwork = "base";
609
+ else if (solUsdc > evmUsdc) preferredNetwork = "solana";
610
+ }
611
+ const x402PaymentClient = await buildX402Client(wallet, {
612
+ preferredNetwork,
613
+ network: flags.network,
614
+ spendLimitDaily: config?.spendLimitDaily,
615
+ spendLimitPerTx: config?.spendLimitPerTx
616
+ });
617
+ const { x402MCPClient } = await import("@x402/mcp");
618
+ const remoteClient = new Client({
619
+ name: "x402-proxy",
620
+ version: "0.6.0"
621
+ });
622
+ const x402Mcp = new x402MCPClient(remoteClient, x402PaymentClient, {
623
+ autoPayment: true,
624
+ onPaymentRequested: (ctx) => {
625
+ const accept = ctx.paymentRequired.accepts?.[0];
626
+ if (accept) warn(` Payment: ${accept.amount ? (Number(accept.amount) / 1e6).toFixed(4) : "?"} USDC on ${displayNetwork(accept.network)} for tool "${ctx.toolName}"`);
627
+ return true;
628
+ }
629
+ });
630
+ x402Mcp.onAfterPayment(async (ctx) => {
631
+ const accepted = ctx.paymentPayload.accepted;
632
+ const tx = ctx.settleResponse?.transaction;
633
+ const record = {
634
+ t: Date.now(),
635
+ ok: true,
636
+ kind: "x402_payment",
637
+ net: accepted?.network ?? "unknown",
638
+ from: wallet.evmAddress ?? wallet.solanaAddress ?? "unknown",
639
+ to: accepted?.payTo,
640
+ tx: typeof tx === "string" ? tx : void 0,
641
+ amount: accepted?.amount ? Number(accepted.amount) / 1e6 : void 0,
642
+ token: "USDC",
643
+ label: `mcp:${ctx.toolName}`
644
+ };
645
+ appendHistory(getHistoryPath(), record);
646
+ });
647
+ await connectTransport(x402Mcp);
648
+ let { tools } = await x402Mcp.listTools();
649
+ dim(` ${tools.length} tools available`);
650
+ let remoteResources = [];
651
+ try {
652
+ remoteResources = (await x402Mcp.listResources()).resources;
653
+ if (remoteResources.length > 0) dim(` ${remoteResources.length} resources available`);
654
+ } catch {
655
+ dim(" Resources not available from remote");
656
+ }
657
+ const localServer = new Server({
658
+ name: "x402-proxy",
659
+ version: "0.6.0"
660
+ }, { capabilities: {
661
+ tools: tools.length > 0 ? {} : void 0,
662
+ resources: remoteResources.length > 0 ? {} : void 0
663
+ } });
664
+ localServer.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: tools.map((t) => ({
665
+ name: t.name,
666
+ description: t.description,
667
+ inputSchema: t.inputSchema,
668
+ annotations: t.annotations
669
+ })) }));
670
+ localServer.setRequestHandler(CallToolRequestSchema, async (request) => {
671
+ const { name, arguments: args } = request.params;
672
+ const result = await x402Mcp.callTool(name, args ?? {});
673
+ return {
674
+ content: result.content,
675
+ isError: result.isError
676
+ };
677
+ });
678
+ if (remoteResources.length > 0) {
679
+ localServer.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: remoteResources.map((r) => ({
680
+ name: r.name,
681
+ uri: r.uri,
682
+ description: r.description,
683
+ mimeType: r.mimeType
684
+ })) }));
685
+ localServer.setRequestHandler(ReadResourceRequestSchema, async (request) => {
686
+ return { contents: (await x402Mcp.readResource({ uri: request.params.uri })).contents.map((c) => ({ ...c })) };
687
+ });
688
+ }
689
+ remoteClient.setNotificationHandler(ToolListChangedNotificationSchema, async () => {
690
+ tools = (await x402Mcp.listTools()).tools;
691
+ dim(` Tools updated: ${tools.length} available`);
692
+ await localServer.notification({ method: "notifications/tools/list_changed" });
693
+ });
694
+ if (remoteResources.length > 0) remoteClient.setNotificationHandler(ResourceListChangedNotificationSchema, async () => {
695
+ remoteResources = (await x402Mcp.listResources()).resources;
696
+ dim(` Resources updated: ${remoteResources.length} available`);
697
+ await localServer.notification({ method: "notifications/resources/list_changed" });
698
+ });
699
+ const stdioTransport = new StdioServerTransport();
700
+ await localServer.connect(stdioTransport);
701
+ dim(" MCP proxy running (stdio, x402)");
702
+ let closing = false;
703
+ const cleanup = async () => {
704
+ if (closing) return;
705
+ closing = true;
706
+ await x402Mcp.close();
707
+ process.exit(0);
479
708
  };
480
- });
481
- if (remoteResources.length > 0) {
482
- localServer.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: remoteResources.map((r) => ({
483
- name: r.name,
484
- uri: r.uri,
485
- description: r.description,
486
- mimeType: r.mimeType
709
+ process.stdin.on("end", cleanup);
710
+ process.on("SIGINT", cleanup);
711
+ process.on("SIGTERM", cleanup);
712
+ }
713
+ async function startMppProxy() {
714
+ if (!wallet.evmKey) {
715
+ error("MPP requires an EVM wallet. Configure one with: npx x402-proxy setup");
716
+ process.exit(1);
717
+ }
718
+ const { tempo } = await import("mppx/client");
719
+ const { McpClient } = await import("mppx/mcp-sdk/client");
720
+ const { privateKeyToAccount } = await import("viem/accounts");
721
+ const account = privateKeyToAccount(wallet.evmKey);
722
+ const maxDeposit = config?.mppSessionBudget ?? "1";
723
+ const remoteClient = new Client({
724
+ name: "x402-proxy",
725
+ version: "0.6.0"
726
+ });
727
+ await connectTransport(remoteClient);
728
+ const mppClient = McpClient.wrap(remoteClient, { methods: [tempo({
729
+ account,
730
+ maxDeposit
731
+ })] });
732
+ let { tools } = await remoteClient.listTools();
733
+ dim(` ${tools.length} tools available`);
734
+ let remoteResources = [];
735
+ try {
736
+ remoteResources = (await remoteClient.listResources()).resources;
737
+ if (remoteResources.length > 0) dim(` ${remoteResources.length} resources available`);
738
+ } catch {
739
+ dim(" Resources not available from remote");
740
+ }
741
+ const localServer = new Server({
742
+ name: "x402-proxy",
743
+ version: "0.6.0"
744
+ }, { capabilities: {
745
+ tools: tools.length > 0 ? {} : void 0,
746
+ resources: remoteResources.length > 0 ? {} : void 0
747
+ } });
748
+ localServer.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: tools.map((t) => ({
749
+ name: t.name,
750
+ description: t.description,
751
+ inputSchema: t.inputSchema,
752
+ annotations: t.annotations
487
753
  })) }));
488
- localServer.setRequestHandler(ReadResourceRequestSchema, async (request) => {
489
- return { contents: (await x402Mcp.readResource({ uri: request.params.uri })).contents.map((c) => ({ ...c })) };
754
+ localServer.setRequestHandler(CallToolRequestSchema, async (request) => {
755
+ const { name, arguments: args } = request.params;
756
+ const result = await mppClient.callTool({
757
+ name,
758
+ arguments: args ?? {}
759
+ });
760
+ if (result.receipt) {
761
+ const record = {
762
+ t: Date.now(),
763
+ ok: true,
764
+ kind: "mpp_payment",
765
+ net: TEMPO_NETWORK,
766
+ from: wallet.evmAddress ?? "unknown",
767
+ tx: result.receipt.reference,
768
+ token: "USDC",
769
+ label: `mcp:${name}`
770
+ };
771
+ appendHistory(getHistoryPath(), record);
772
+ warn(` MPP payment for tool "${name}" (Tempo)`);
773
+ }
774
+ return {
775
+ content: result.content,
776
+ isError: result.isError
777
+ };
490
778
  });
779
+ if (remoteResources.length > 0) {
780
+ localServer.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: remoteResources.map((r) => ({
781
+ name: r.name,
782
+ uri: r.uri,
783
+ description: r.description,
784
+ mimeType: r.mimeType
785
+ })) }));
786
+ localServer.setRequestHandler(ReadResourceRequestSchema, async (request) => {
787
+ return { contents: (await remoteClient.readResource({ uri: request.params.uri })).contents.map((c) => ({ ...c })) };
788
+ });
789
+ }
790
+ remoteClient.setNotificationHandler(ToolListChangedNotificationSchema, async () => {
791
+ tools = (await remoteClient.listTools()).tools;
792
+ dim(` Tools updated: ${tools.length} available`);
793
+ await localServer.notification({ method: "notifications/tools/list_changed" });
794
+ });
795
+ if (remoteResources.length > 0) remoteClient.setNotificationHandler(ResourceListChangedNotificationSchema, async () => {
796
+ remoteResources = (await remoteClient.listResources()).resources;
797
+ dim(` Resources updated: ${remoteResources.length} available`);
798
+ await localServer.notification({ method: "notifications/resources/list_changed" });
799
+ });
800
+ const stdioTransport = new StdioServerTransport();
801
+ await localServer.connect(stdioTransport);
802
+ dim(" MCP proxy running (stdio, mpp)");
803
+ let closing = false;
804
+ const cleanup = async () => {
805
+ if (closing) return;
806
+ closing = true;
807
+ await remoteClient.close();
808
+ process.exit(0);
809
+ };
810
+ process.stdin.on("end", cleanup);
811
+ process.on("SIGINT", cleanup);
812
+ process.on("SIGTERM", cleanup);
491
813
  }
492
- remoteClient.setNotificationHandler(ToolListChangedNotificationSchema, async () => {
493
- tools = (await x402Mcp.listTools()).tools;
494
- dim(` Tools updated: ${tools.length} available`);
495
- await localServer.notification({ method: "notifications/tools/list_changed" });
496
- });
497
- if (remoteResources.length > 0) remoteClient.setNotificationHandler(ResourceListChangedNotificationSchema, async () => {
498
- remoteResources = (await x402Mcp.listResources()).resources;
499
- dim(` Resources updated: ${remoteResources.length} available`);
500
- await localServer.notification({ method: "notifications/resources/list_changed" });
501
- });
502
- const stdioTransport = new StdioServerTransport();
503
- await localServer.connect(stdioTransport);
504
- dim(" MCP proxy running (stdio)");
505
- process.stdin.on("end", async () => {
506
- await x402Mcp.close();
507
- process.exit(0);
508
- });
509
814
  }
510
815
  });
511
816
 
@@ -635,7 +940,7 @@ const routes = buildRouteMap({
635
940
  });
636
941
  const app = buildApplication(routes, {
637
942
  name: "x402-proxy",
638
- versionInfo: { currentVersion: "0.5.2" },
943
+ versionInfo: { currentVersion: "0.6.0" },
639
944
  scanner: { caseStyle: "allow-kebab-for-camel" }
640
945
  });
641
946