gatepay-local-mcp 1.0.2 → 1.0.4

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.
Files changed (46) hide show
  1. package/Readme.md +271 -69
  2. package/dist/src/index.js +573 -104
  3. package/dist/src/index.js.map +1 -1
  4. package/dist/src/modes/build-pay-fetch.d.ts +3 -2
  5. package/dist/src/modes/build-pay-fetch.d.ts.map +1 -1
  6. package/dist/src/modes/build-pay-fetch.js +20 -4
  7. package/dist/src/modes/build-pay-fetch.js.map +1 -1
  8. package/dist/src/modes/local-private-key.d.ts.map +1 -1
  9. package/dist/src/modes/local-private-key.js +31 -10
  10. package/dist/src/modes/local-private-key.js.map +1 -1
  11. package/dist/src/modes/plugin-wallet.d.ts +12 -0
  12. package/dist/src/modes/plugin-wallet.d.ts.map +1 -1
  13. package/dist/src/modes/plugin-wallet.js +229 -9
  14. package/dist/src/modes/plugin-wallet.js.map +1 -1
  15. package/dist/src/modes/quick-wallet.d.ts +4 -0
  16. package/dist/src/modes/quick-wallet.d.ts.map +1 -1
  17. package/dist/src/modes/quick-wallet.js +46 -21
  18. package/dist/src/modes/quick-wallet.js.map +1 -1
  19. package/dist/src/modes/registry.d.ts.map +1 -1
  20. package/dist/src/modes/registry.js +10 -6
  21. package/dist/src/modes/registry.js.map +1 -1
  22. package/dist/src/modes/signers.d.ts +38 -1
  23. package/dist/src/modes/signers.d.ts.map +1 -1
  24. package/dist/src/modes/signers.js +447 -5
  25. package/dist/src/modes/signers.js.map +1 -1
  26. package/dist/src/modes/types.d.ts +16 -1
  27. package/dist/src/modes/types.d.ts.map +1 -1
  28. package/dist/src/wallets/plugin-wallet-client.d.ts +10 -0
  29. package/dist/src/wallets/plugin-wallet-client.d.ts.map +1 -1
  30. package/dist/src/wallets/plugin-wallet-client.js +32 -3
  31. package/dist/src/wallets/plugin-wallet-client.js.map +1 -1
  32. package/dist/src/wallets/wallet-mcp-clients.d.ts.map +1 -1
  33. package/dist/src/wallets/wallet-mcp-clients.js +2 -2
  34. package/dist/src/wallets/wallet-mcp-clients.js.map +1 -1
  35. package/dist/src/x402/exactSvmScheme.d.ts +20 -0
  36. package/dist/src/x402/exactSvmScheme.d.ts.map +1 -0
  37. package/dist/src/x402/exactSvmScheme.js +38 -0
  38. package/dist/src/x402/exactSvmScheme.js.map +1 -0
  39. package/dist/src/x402/fetch.d.ts.map +1 -1
  40. package/dist/src/x402/fetch.js +2 -0
  41. package/dist/src/x402/fetch.js.map +1 -1
  42. package/dist/src/x402/types.d.ts +5 -0
  43. package/dist/src/x402/types.d.ts.map +1 -1
  44. package/dist/src/x402/types.js +0 -3
  45. package/dist/src/x402/types.js.map +1 -1
  46. package/package.json +22 -7
package/dist/src/index.js CHANGED
@@ -13,8 +13,13 @@ import { normalizeX402RequestInput } from "./modes/input-normalizer.js";
13
13
  import { LocalPrivateKeyMode } from "./modes/local-private-key.js";
14
14
  import { PluginWalletMode } from "./modes/plugin-wallet.js";
15
15
  import { createSignModeRegistry, formatSignModeSelectionError, } from "./modes/registry.js";
16
- import { QuickWalletMode } from "./modes/quick-wallet.js";
17
- import { getMcpClientSync } from "./wallets/wallet-mcp-clients.js";
16
+ import { getQuickWalletAddressPayload, QuickWalletMode, runQuickWalletDeviceAuthIfNeeded, } from "./modes/quick-wallet.js";
17
+ import { getMcpClient, getMcpClientSync } from "./wallets/wallet-mcp-clients.js";
18
+ import { X402ClientStandalone } from "./x402/client.js";
19
+ import { ExactEvmScheme } from "./x402/exactEvmScheme.js";
20
+ import { ExactSvmScheme } from "./x402/exactSvmScheme.js";
21
+ import { decodePaymentRequiredHeader, encodePaymentSignatureHeader, getPaymentRequiredResponse } from "./x402/http.js";
22
+ import { normalizePaymentRequirements } from "./x402/utils.js";
18
23
  const __dirname = dirname(fileURLToPath(import.meta.url));
19
24
  function findPackageRoot(startDir) {
20
25
  let dir = startDir;
@@ -31,6 +36,17 @@ const packageRoot = findPackageRoot(__dirname);
31
36
  config({ path: join(packageRoot, ".env") });
32
37
  const TOOL_NAME = "x402_request";
33
38
  const INSUFFICIENT_BALANCE_CODE = "800001001";
39
+ const SUPPORTED_NETWORKS = [
40
+ "gatelayer_testnet",
41
+ "eth",
42
+ "base",
43
+ "Polygon",
44
+ "gatelayer",
45
+ "gatechain",
46
+ "Arbitrum One",
47
+ ];
48
+ // 保留原有工具的schema定义(虽然不再对外暴露,但保留用于向后兼容)
49
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
34
50
  const INPUT_SCHEMA = {
35
51
  type: "object",
36
52
  properties: {
@@ -48,7 +64,8 @@ const INPUT_SCHEMA = {
48
64
  },
49
65
  sign_mode: {
50
66
  type: "string",
51
- description: "Optional preferred signing mode. Omit to auto-select the highest-priority ready mode.",
67
+ description: "Optional preferred signing mode. Omit to auto-select the highest-priority ready mode. " +
68
+ "If the initial payment fails, ask the user which payment method to use instead of automatically retrying.",
52
69
  enum: ["local_private_key", "quick_wallet", "plugin_wallet"],
53
70
  },
54
71
  wallet_login_provider: {
@@ -59,8 +76,145 @@ const INPUT_SCHEMA = {
59
76
  },
60
77
  required: ["url"],
61
78
  };
79
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
62
80
  const TOOL_DESCRIPTION = "Execute a single HTTP request with automatic x402 payment on 402. Use ONLY for endpoints that require payment (402). " +
63
- "Set sign_mode to choose a signing mode, or omit it to auto-select the highest-priority ready mode.";
81
+ "Set sign_mode to choose a signing mode, or omit it to auto-select the highest-priority ready mode. " +
82
+ "IMPORTANT: If a payment fails, do NOT automatically retry with a different sign_mode. Instead, ask the user which payment method they would like to try.";
83
+ // New tool schemas
84
+ const PLACE_ORDER_INPUT_SCHEMA = {
85
+ type: "object",
86
+ properties: {
87
+ url: {
88
+ type: "string",
89
+ description: "Full URL of the endpoint. Must be a complete http/https URL.",
90
+ },
91
+ method: {
92
+ type: "string",
93
+ description: "HTTP method: GET, POST, PUT, or PATCH. Default POST.",
94
+ enum: ["GET", "POST", "PUT", "PATCH"],
95
+ },
96
+ body: {
97
+ type: "string",
98
+ description: "JSON string request body for POST/PUT/PATCH. Omit for GET.",
99
+ },
100
+ },
101
+ required: ["url"],
102
+ };
103
+ const PLACE_ORDER_DESCRIPTION = "Send an HTTP request and return complete response information including headers, body, and the original request details. " +
104
+ "Returns status code, all response headers (including PAYMENT-REQUIRED if present), response body, and the original request parameters. " +
105
+ "Use this for any HTTP request where you need full response details.";
106
+ const SIGN_PAYMENT_INPUT_SCHEMA = {
107
+ type: "object",
108
+ properties: {
109
+ url: {
110
+ type: "string",
111
+ description: "Target URL for the payment request",
112
+ },
113
+ method: {
114
+ type: "string",
115
+ description: "HTTP method for the request",
116
+ enum: ["GET", "POST", "PUT", "PATCH"],
117
+ },
118
+ body: {
119
+ type: "string",
120
+ description: "JSON string request body (optional)",
121
+ },
122
+ payment_required_header: {
123
+ type: "string",
124
+ description: "Base64-encoded PAYMENT-REQUIRED header value from a 402 response",
125
+ },
126
+ response_body: {
127
+ type: "string",
128
+ description: "Optional: Response body from 402 response, used for parsing payment requirements if PAYMENT-REQUIRED header is not available",
129
+ },
130
+ sign_mode: {
131
+ type: "string",
132
+ description: "Optional preferred signing mode. Omit to auto-select the highest-priority ready mode.",
133
+ enum: ["local_private_key", "quick_wallet", "plugin_wallet"],
134
+ },
135
+ wallet_login_provider: {
136
+ type: "string",
137
+ description: "When quick_wallet needs login: OAuth provider. google = Google account, gate = Gate account. Defaults to gate.",
138
+ enum: ["google", "gate"],
139
+ },
140
+ },
141
+ required: ["url"],
142
+ };
143
+ const SIGN_PAYMENT_DESCRIPTION = "Parse X402 payment requirements from PAYMENT-REQUIRED header or response body, create a signed payment authorization, " +
144
+ "and submit the payment to complete a 402-protected request. " +
145
+ "Supports three signing modes: local_private_key (local EVM wallet), quick_wallet (custodial MCP wallet), and plugin_wallet (browser extension wallet). " +
146
+ "Provide either payment_required_header or response_body containing X402 payment requirements.";
147
+ const QUICK_WALLET_AUTH_INPUT_SCHEMA = {
148
+ type: "object",
149
+ properties: {
150
+ wallet_login_provider: {
151
+ type: "string",
152
+ description: "Device-flow OAuth provider when MCP token is missing or expired. google = Google account, gate = Gate account. Defaults to gate.",
153
+ enum: ["google", "gate"],
154
+ },
155
+ },
156
+ required: [],
157
+ };
158
+ const QUICK_WALLET_AUTH_DESCRIPTION = "When the user selects sign_mode quick_wallet, run this tool first to perform the same device-flow login/authorization as the quick_wallet signing path. " +
159
+ "If the in-process MCP token is already valid, returns ready status and wallet addresses; otherwise opens the browser flow (Gate by default, or Google if wallet_login_provider is google). " +
160
+ "After a fresh login succeeds, the user may need to confirm before continuing to payment. " +
161
+ "To switch authorization provider (e.g. Gate vs Google), restart the MCP server; the in-process wallet client keeps the current session until restart.";
162
+ // New tool schemas for signature creation and payment submission
163
+ const CREATE_SIGNATURE_INPUT_SCHEMA = {
164
+ type: "object",
165
+ properties: {
166
+ payment_required_header: {
167
+ type: "string",
168
+ description: "Base64-encoded PAYMENT-REQUIRED header value from a 402 response",
169
+ },
170
+ response_body: {
171
+ type: "string",
172
+ description: "Optional: Response body from 402 response, used if PAYMENT-REQUIRED header is not available",
173
+ },
174
+ sign_mode: {
175
+ type: "string",
176
+ description: "Optional preferred signing mode. Omit to auto-select the highest-priority ready mode.",
177
+ enum: ["local_private_key", "quick_wallet", "plugin_wallet"],
178
+ },
179
+ wallet_login_provider: {
180
+ type: "string",
181
+ description: "When quick_wallet needs login: OAuth provider (google or gate). Defaults to gate.",
182
+ enum: ["google", "gate"],
183
+ },
184
+ },
185
+ required: [],
186
+ };
187
+ const CREATE_SIGNATURE_DESCRIPTION = "Parse X402 payment requirements and create a signed payment authorization. " +
188
+ "Returns the complete payment payload including signature and the base64-encoded " +
189
+ "PAYMENT-SIGNATURE header value. Supports three signing modes: local_private_key, " +
190
+ "quick_wallet, and plugin_wallet. The output can be used with x402_submit_payment " +
191
+ "to complete the payment request.";
192
+ const SUBMIT_PAYMENT_INPUT_SCHEMA = {
193
+ type: "object",
194
+ properties: {
195
+ url: {
196
+ type: "string",
197
+ description: "Target URL for the payment request",
198
+ },
199
+ method: {
200
+ type: "string",
201
+ description: "HTTP method for the request. Default POST.",
202
+ enum: ["GET", "POST", "PUT", "PATCH"],
203
+ },
204
+ body: {
205
+ type: "string",
206
+ description: "JSON string request body (optional)",
207
+ },
208
+ payment_signature: {
209
+ type: "string",
210
+ description: "Base64-encoded PAYMENT-SIGNATURE header value from x402_create_signature",
211
+ },
212
+ },
213
+ required: ["url", "payment_signature"],
214
+ };
215
+ const SUBMIT_PAYMENT_DESCRIPTION = "Submit a signed payment to complete a 402-protected request. Takes the " +
216
+ "payment_signature from x402_create_signature and sends it to the merchant " +
217
+ "along with the original request. Returns the final response from the merchant.";
64
218
  function parsePossiblyNestedJson(text) {
65
219
  try {
66
220
  const parsed = JSON.parse(text);
@@ -130,10 +284,363 @@ function buildRequestInit(method, body) {
130
284
  }
131
285
  throw new Error(`不支持的 method: ${method}`);
132
286
  }
287
+ function createErrorResponse(message) {
288
+ return {
289
+ content: [{ type: "text", text: message }],
290
+ isError: true,
291
+ };
292
+ }
293
+ function createSuccessResponse(text) {
294
+ return {
295
+ content: [{ type: "text", text }],
296
+ isError: false,
297
+ };
298
+ }
299
+ async function validateToolRequest(name, args) {
300
+ if (name !== TOOL_NAME) {
301
+ return createErrorResponse(`未知工具: ${name}. 仅支持 ${TOOL_NAME}。`);
302
+ }
303
+ const normalized = normalizeX402RequestInput((args ?? {}));
304
+ if (!normalized.url || !normalized.url.startsWith("http")) {
305
+ return createErrorResponse("缺少或无效参数 url(需完整 http/https URL)。");
306
+ }
307
+ return null;
308
+ }
309
+ function isCallToolResult(result) {
310
+ return (typeof result === "object" &&
311
+ result !== null &&
312
+ "content" in result &&
313
+ Array.isArray(result.content));
314
+ }
315
+ async function selectSignModeAndGetPayFetch(registry, signMode, walletLoginProvider) {
316
+ try {
317
+ const selectedMode = await registry.selectMode(signMode);
318
+ const payFetch = await registry.getOrCreatePayFetch(selectedMode.mode, {
319
+ walletLoginProvider,
320
+ });
321
+ return { payFetch };
322
+ }
323
+ catch (error) {
324
+ return createErrorResponse(formatSignModeSelectionError(error));
325
+ }
326
+ }
327
+ function formatResponseText(responseText) {
328
+ try {
329
+ const json = JSON.parse(responseText);
330
+ return json.data != null
331
+ ? JSON.stringify(json.data, null, 2)
332
+ : JSON.stringify(json, null, 2);
333
+ }
334
+ catch {
335
+ return responseText;
336
+ }
337
+ }
338
+ async function handleResponseWithBalanceCheck(response, responseText) {
339
+ const text = formatResponseText(responseText);
340
+ const insufficientBalance = containsInsufficientBalanceSignal(responseText) ||
341
+ containsInsufficientBalanceSignal(text);
342
+ if (!response.ok && response.status !== 402) {
343
+ if (insufficientBalance) {
344
+ return createErrorResponse(await buildInsufficientBalanceReply(text));
345
+ }
346
+ return createErrorResponse(`HTTP ${response.status}: ${text}`);
347
+ }
348
+ if (insufficientBalance) {
349
+ return createErrorResponse(await buildInsufficientBalanceReply(text));
350
+ }
351
+ return createSuccessResponse(text);
352
+ }
353
+ async function executeX402Request(payFetch, normalized) {
354
+ try {
355
+ const init = buildRequestInit(normalized.method, normalized.body);
356
+ const response = await payFetch(normalized.url, init);
357
+ const responseText = await response.text();
358
+ return await handleResponseWithBalanceCheck(response, responseText);
359
+ }
360
+ catch (error) {
361
+ if (error instanceof Error && error.message.includes("不支持的 method")) {
362
+ return createErrorResponse(error.message);
363
+ }
364
+ throw error;
365
+ }
366
+ }
367
+ async function handleRequestError(err) {
368
+ const message = err instanceof Error ? err.message : String(err);
369
+ if (containsInsufficientBalanceSignal(message)) {
370
+ return createErrorResponse(await buildInsufficientBalanceReply(message));
371
+ }
372
+ const hint = message.toLowerCase().includes("fetch") || message.toLowerCase().includes("econnrefused")
373
+ ? " 请确认 url 可访问;402 支付需托管钱包已登录且有足够余额。"
374
+ : "";
375
+ return createErrorResponse(`请求失败: ${message}.${hint}`);
376
+ }
377
+ async function handlePlaceOrder(args) {
378
+ try {
379
+ const normalized = normalizeX402RequestInput(args);
380
+ if (!normalized.url || !normalized.url.startsWith("http")) {
381
+ return createErrorResponse("缺少或无效参数 url(需完整 http/https URL)。");
382
+ }
383
+ const init = buildRequestInit(normalized.method, normalized.body);
384
+ const response = await fetch(normalized.url, init);
385
+ const responseText = await response.text();
386
+ const headers = {};
387
+ response.headers.forEach((value, key) => {
388
+ headers[key] = value;
389
+ });
390
+ const result = {
391
+ request: {
392
+ url: normalized.url,
393
+ method: normalized.method,
394
+ body: normalized.body || null,
395
+ },
396
+ response: {
397
+ status: response.status,
398
+ statusText: response.statusText,
399
+ headers,
400
+ body: responseText,
401
+ },
402
+ };
403
+ return createSuccessResponse(JSON.stringify(result, null, 2));
404
+ }
405
+ catch (err) {
406
+ return await handleRequestError(err);
407
+ }
408
+ }
409
+ async function handleQuickWalletAuth(args, options) {
410
+ const walletLoginProvider = String(args.wallet_login_provider ?? "gate").toLowerCase() === "google"
411
+ ? "google"
412
+ : "gate";
413
+ try {
414
+ const mcp = await getMcpClient({
415
+ serverUrl: options.mcpWalletUrl,
416
+ apiKey: options.mcpApiKey,
417
+ });
418
+ const phase = await runQuickWalletDeviceAuthIfNeeded(mcp, options.mcpWalletUrl, { walletLoginProvider });
419
+ const addresses = await getQuickWalletAddressPayload(mcp);
420
+ if (phase === "login_succeeded") {
421
+ return createSuccessResponse([
422
+ "quick_wallet 登录成功。",
423
+ `钱包地址信息:${JSON.stringify(addresses, null, 2)}`
424
+ ].join("\n"));
425
+ }
426
+ return createSuccessResponse(JSON.stringify({
427
+ status: "ready",
428
+ summary: "quick_wallet 进程内已有有效 MCP 登录态。",
429
+ wallet_addresses: addresses,
430
+ }, null, 2));
431
+ }
432
+ catch (err) {
433
+ const message = err instanceof Error ? err.message : String(err);
434
+ return createErrorResponse(message);
435
+ }
436
+ }
437
+ async function handleSignPayment(args, signModeRegistry) {
438
+ try {
439
+ // 1. 解析参数
440
+ const url = String(args.url ?? "").trim();
441
+ const method = String(args.method ?? "POST").trim().toUpperCase();
442
+ const body = args.body != null ? String(args.body) : "";
443
+ const paymentRequiredHeader = args.payment_required_header != null ? String(args.payment_required_header).trim() : "";
444
+ const responseBody = args.response_body != null ? String(args.response_body).trim() : "";
445
+ const signMode = args.sign_mode != null ? String(args.sign_mode).trim() : undefined;
446
+ const walletLoginProvider = String(args.wallet_login_provider ?? "gate").toLowerCase() === "google" ? "google" : "gate";
447
+ if (!url || !url.startsWith("http")) {
448
+ return createErrorResponse("缺少或无效参数 url(需完整 http/https URL)。");
449
+ }
450
+ // 2. 解析 PAYMENT-REQUIRED
451
+ let paymentRequired;
452
+ try {
453
+ if (paymentRequiredHeader) {
454
+ paymentRequired = decodePaymentRequiredHeader(paymentRequiredHeader);
455
+ }
456
+ else if (responseBody) {
457
+ // 尝试从响应体解析
458
+ const getHeader = () => null;
459
+ let bodyObj;
460
+ try {
461
+ bodyObj = JSON.parse(responseBody);
462
+ }
463
+ catch {
464
+ return createErrorResponse("无法解析响应体为JSON,且未提供payment_required_header参数。");
465
+ }
466
+ paymentRequired = getPaymentRequiredResponse(getHeader, bodyObj);
467
+ }
468
+ else {
469
+ return createErrorResponse("缺少payment_required_header或response_body参数,无法解析支付要求。");
470
+ }
471
+ paymentRequired = {
472
+ ...paymentRequired,
473
+ accepts: normalizePaymentRequirements(paymentRequired.accepts),
474
+ };
475
+ }
476
+ catch (error) {
477
+ return createErrorResponse(`解析PAYMENT-REQUIRED失败: ${error instanceof Error ? error.message : String(error)}`);
478
+ }
479
+ // 3. 获取签名器
480
+ const signModeResult = await selectSignModeAndGetPayFetch(signModeRegistry, signMode, walletLoginProvider);
481
+ if (isCallToolResult(signModeResult)) {
482
+ return signModeResult;
483
+ }
484
+ // 4. 创建支付payload
485
+ const selectedMode = await signModeRegistry.selectMode(signMode);
486
+ const signerSession = await selectedMode.mode.resolveSigner({ walletLoginProvider });
487
+ const client = new X402ClientStandalone();
488
+ // Register EVM networks
489
+ if (signerSession.signer) {
490
+ const evmScheme = new ExactEvmScheme(signerSession.signer);
491
+ for (const network of SUPPORTED_NETWORKS) {
492
+ client.register(network, evmScheme);
493
+ }
494
+ }
495
+ // Register Solana networks if solanaSigner is available
496
+ if (signerSession.solanaSigner) {
497
+ const solanaNetworks = [
498
+ { name: "solana", rpcUrl: "https://api.mainnet-beta.solana.com" },
499
+ { name: "solana-devnet", rpcUrl: "https://api.devnet.solana.com" },
500
+ ];
501
+ for (const network of solanaNetworks) {
502
+ const svmScheme = new ExactSvmScheme(signerSession.solanaSigner, {
503
+ rpcUrl: network.rpcUrl,
504
+ });
505
+ client.register(network.name, svmScheme);
506
+ }
507
+ }
508
+ let paymentPayload;
509
+ try {
510
+ paymentPayload = await client.createPaymentPayload(paymentRequired);
511
+ }
512
+ catch (error) {
513
+ return createErrorResponse(`创建支付payload失败: ${error instanceof Error ? error.message : String(error)}`);
514
+ }
515
+ // 5. 携带签名重新请求
516
+ const init = buildRequestInit(method, body);
517
+ const encoded = encodePaymentSignatureHeader(paymentPayload);
518
+ const request = new Request(url, init);
519
+ request.headers.set("PAYMENT-SIGNATURE", encoded);
520
+ request.headers.set("Access-Control-Expose-Headers", "PAYMENT-RESPONSE,X-PAYMENT-RESPONSE");
521
+ const finalResponse = await fetch(request);
522
+ const finalResponseText = await finalResponse.text();
523
+ return await handleResponseWithBalanceCheck(finalResponse, finalResponseText);
524
+ }
525
+ catch (err) {
526
+ return await handleRequestError(err);
527
+ }
528
+ }
529
+ async function handleCreateSignature(args, signModeRegistry) {
530
+ try {
531
+ const paymentRequiredHeader = args.payment_required_header != null ? String(args.payment_required_header).trim() : "";
532
+ const responseBody = args.response_body != null ? String(args.response_body).trim() : "";
533
+ const signMode = args.sign_mode != null ? String(args.sign_mode).trim() : undefined;
534
+ const walletLoginProvider = String(args.wallet_login_provider ?? "gate").toLowerCase() === "google" ? "google" : "gate";
535
+ // 1. 解析 PAYMENT-REQUIRED
536
+ let paymentRequired;
537
+ try {
538
+ if (paymentRequiredHeader) {
539
+ paymentRequired = decodePaymentRequiredHeader(paymentRequiredHeader);
540
+ }
541
+ else if (responseBody) {
542
+ const getHeader = () => null;
543
+ let bodyObj;
544
+ try {
545
+ bodyObj = JSON.parse(responseBody);
546
+ }
547
+ catch {
548
+ return createErrorResponse("无法解析响应体为JSON,且未提供payment_required_header参数。");
549
+ }
550
+ paymentRequired = getPaymentRequiredResponse(getHeader, bodyObj);
551
+ }
552
+ else {
553
+ return createErrorResponse("缺少payment_required_header或response_body参数。");
554
+ }
555
+ paymentRequired = {
556
+ ...paymentRequired,
557
+ accepts: normalizePaymentRequirements(paymentRequired.accepts),
558
+ };
559
+ }
560
+ catch (error) {
561
+ return createErrorResponse(`解析PAYMENT-REQUIRED失败: ${error instanceof Error ? error.message : String(error)}`);
562
+ }
563
+ // 2. 获取签名器并创建签名
564
+ const signModeResult = await selectSignModeAndGetPayFetch(signModeRegistry, signMode, walletLoginProvider);
565
+ if (isCallToolResult(signModeResult)) {
566
+ return signModeResult;
567
+ }
568
+ const selectedMode = await signModeRegistry.selectMode(signMode);
569
+ const signerSession = await selectedMode.mode.resolveSigner({ walletLoginProvider });
570
+ const client = new X402ClientStandalone();
571
+ // Register EVM networks
572
+ if (signerSession.signer) {
573
+ const evmScheme = new ExactEvmScheme(signerSession.signer);
574
+ for (const network of SUPPORTED_NETWORKS) {
575
+ client.register(network, evmScheme);
576
+ }
577
+ }
578
+ // Register Solana networks if solanaSigner is available
579
+ if (signerSession.solanaSigner) {
580
+ const solanaNetworks = [
581
+ { name: "solana", rpcUrl: "https://api.mainnet-beta.solana.com" },
582
+ { name: "solana-devnet", rpcUrl: "https://api.devnet.solana.com" },
583
+ ];
584
+ for (const network of solanaNetworks) {
585
+ const svmScheme = new ExactSvmScheme(signerSession.solanaSigner, {
586
+ rpcUrl: network.rpcUrl,
587
+ });
588
+ client.register(network.name, svmScheme);
589
+ }
590
+ }
591
+ let paymentPayload;
592
+ try {
593
+ paymentPayload = await client.createPaymentPayload(paymentRequired);
594
+ }
595
+ catch (error) {
596
+ return createErrorResponse(`创建支付payload失败: ${error instanceof Error ? error.message : String(error)}`);
597
+ }
598
+ // 3. 返回完整的payload和编码后的签名
599
+ const encoded = encodePaymentSignatureHeader(paymentPayload);
600
+ const result = {
601
+ paymentPayload,
602
+ encodedSignature: encoded,
603
+ };
604
+ return createSuccessResponse(JSON.stringify(result, null, 2));
605
+ }
606
+ catch (err) {
607
+ return await handleRequestError(err);
608
+ }
609
+ }
610
+ async function handleSubmitPayment(args) {
611
+ try {
612
+ const url = String(args.url ?? "").trim();
613
+ const method = String(args.method ?? "POST").trim().toUpperCase();
614
+ const body = args.body != null ? String(args.body) : "";
615
+ const paymentSignature = String(args.payment_signature ?? "").trim();
616
+ if (!url || !url.startsWith("http")) {
617
+ return createErrorResponse("缺少或无效参数 url(需完整 http/https URL)。");
618
+ }
619
+ if (!paymentSignature) {
620
+ return createErrorResponse("缺少 payment_signature 参数。");
621
+ }
622
+ // 构建请求并添加签名头
623
+ const init = buildRequestInit(method, body);
624
+ const request = new Request(url, init);
625
+ request.headers.set("PAYMENT-SIGNATURE", paymentSignature);
626
+ request.headers.set("Access-Control-Expose-Headers", "PAYMENT-RESPONSE,X-PAYMENT-RESPONSE");
627
+ // 发送支付请求
628
+ const finalResponse = await fetch(request);
629
+ const finalResponseText = await finalResponse.text();
630
+ return await handleResponseWithBalanceCheck(finalResponse, finalResponseText);
631
+ }
632
+ catch (err) {
633
+ return await handleRequestError(err);
634
+ }
635
+ }
133
636
  async function main() {
134
- const quickWalletMcpUrl = process.env.MCP_WALLET_URL ?? "https://api.gatemcp.ai/mcp/dex";
135
- const quickWalletApiKey = process.env.MCP_WALLET_API_KEY;
136
- const pluginWalletServerUrl = process.env.PLUGIN_WALLET_SERVER_URL ?? "https://walletmcp-test.gateweb3.cc/mcp";
637
+ const quickWalletMcpUrl = process.env.QUICK_WALLET_SERVER_URL ?? "https://api.gatemcp.ai/mcp/dex";
638
+ const quickWalletApiKey = process.env.QUICK_WALLET_API_KEY;
639
+ const pluginWalletBaseUrl = process.env.PLUGIN_WALLET_SERVER_URL ?? "https://walletmcp.gate.com/mcp";
640
+ const pluginWalletToken = process.env.PLUGIN_WALLET_TOKEN;
641
+ const pluginWalletServerUrl = pluginWalletToken
642
+ ? `${pluginWalletBaseUrl}?token=${encodeURIComponent(pluginWalletToken)}`
643
+ : undefined;
137
644
  const signModeRegistry = createSignModeRegistry([
138
645
  new LocalPrivateKeyMode(),
139
646
  new QuickWalletMode({ mcpWalletUrl: quickWalletMcpUrl, mcpApiKey: quickWalletApiKey }),
@@ -146,119 +653,81 @@ async function main() {
146
653
  server.registerCapabilities({ tools: { listChanged: false } });
147
654
  server.setRequestHandler(ListToolsRequestSchema, () => ({
148
655
  tools: [
656
+ // 原有 x402_request 工具保留代码但不再对外暴露
657
+ // {
658
+ // name: TOOL_NAME,
659
+ // description: TOOL_DESCRIPTION,
660
+ // inputSchema: INPUT_SCHEMA,
661
+ // },
149
662
  {
150
- name: TOOL_NAME,
151
- description: TOOL_DESCRIPTION,
152
- inputSchema: INPUT_SCHEMA,
663
+ name: "x402_place_order",
664
+ description: PLACE_ORDER_DESCRIPTION,
665
+ inputSchema: PLACE_ORDER_INPUT_SCHEMA,
666
+ },
667
+ {
668
+ name: "x402_sign_payment",
669
+ description: SIGN_PAYMENT_DESCRIPTION,
670
+ inputSchema: SIGN_PAYMENT_INPUT_SCHEMA,
671
+ },
672
+ {
673
+ name: "x402_create_signature",
674
+ description: CREATE_SIGNATURE_DESCRIPTION,
675
+ inputSchema: CREATE_SIGNATURE_INPUT_SCHEMA,
676
+ },
677
+ {
678
+ name: "x402_submit_payment",
679
+ description: SUBMIT_PAYMENT_DESCRIPTION,
680
+ inputSchema: SUBMIT_PAYMENT_INPUT_SCHEMA,
681
+ },
682
+ {
683
+ name: "x402_quick_wallet_auth",
684
+ description: QUICK_WALLET_AUTH_DESCRIPTION,
685
+ inputSchema: QUICK_WALLET_AUTH_INPUT_SCHEMA,
153
686
  },
154
687
  ],
155
688
  }));
156
689
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
157
690
  const { name, arguments: args } = request.params;
158
- if (name !== TOOL_NAME) {
159
- return {
160
- content: [{ type: "text", text: `未知工具: ${name}. 仅支持 ${TOOL_NAME}。` }],
161
- isError: true,
162
- };
691
+ // 处理新工具
692
+ if (name === "x402_place_order") {
693
+ return await handlePlaceOrder(args ?? {});
163
694
  }
164
- const normalized = normalizeX402RequestInput((args ?? {}));
165
- if (!normalized.url || !normalized.url.startsWith("http")) {
166
- return {
167
- content: [{ type: "text", text: "缺少或无效参数 url(需完整 http/https URL)。" }],
168
- isError: true,
169
- };
695
+ if (name === "x402_sign_payment") {
696
+ return await handleSignPayment(args ?? {}, signModeRegistry);
170
697
  }
171
- let payFetch;
172
- try {
173
- const selectedMode = await signModeRegistry.selectMode(normalized.signMode);
174
- payFetch = await signModeRegistry.getOrCreatePayFetch(selectedMode.mode, {
175
- walletLoginProvider: normalized.walletLoginProvider,
176
- });
698
+ if (name === "x402_create_signature") {
699
+ return await handleCreateSignature(args ?? {}, signModeRegistry);
177
700
  }
178
- catch (error) {
179
- return {
180
- content: [{ type: "text", text: formatSignModeSelectionError(error) }],
181
- isError: true,
182
- };
701
+ if (name === "x402_submit_payment") {
702
+ return await handleSubmitPayment(args ?? {});
183
703
  }
184
- try {
185
- let init;
186
- try {
187
- init = buildRequestInit(normalized.method, normalized.body);
704
+ if (name === "x402_quick_wallet_auth") {
705
+ return await handleQuickWalletAuth(args ?? {}, {
706
+ mcpWalletUrl: quickWalletMcpUrl,
707
+ mcpApiKey: quickWalletApiKey,
708
+ });
709
+ }
710
+ // 保留原有 x402_request 工具的处理逻辑(虽然不再对外暴露)
711
+ if (name === TOOL_NAME) {
712
+ const validationError = await validateToolRequest(name, args);
713
+ if (validationError) {
714
+ return validationError;
188
715
  }
189
- catch (error) {
190
- return {
191
- content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
192
- isError: true,
193
- };
716
+ const normalized = normalizeX402RequestInput((args ?? {}));
717
+ const signModeResult = await selectSignModeAndGetPayFetch(signModeRegistry, normalized.signMode, normalized.walletLoginProvider);
718
+ if (isCallToolResult(signModeResult)) {
719
+ return signModeResult;
194
720
  }
195
- const response = await payFetch(normalized.url, init);
196
- const responseText = await response.text();
197
- let text;
721
+ const payFetch = signModeResult.payFetch;
198
722
  try {
199
- const json = JSON.parse(responseText);
200
- text =
201
- json.data != null
202
- ? JSON.stringify(json.data, null, 2)
203
- : JSON.stringify(json, null, 2);
204
- }
205
- catch {
206
- text = responseText;
207
- }
208
- const insufficientBalance = containsInsufficientBalanceSignal(responseText) ||
209
- containsInsufficientBalanceSignal(text);
210
- if (!response.ok && response.status !== 402) {
211
- if (insufficientBalance) {
212
- return {
213
- content: [
214
- {
215
- type: "text",
216
- text: await buildInsufficientBalanceReply(text),
217
- },
218
- ],
219
- isError: true,
220
- };
221
- }
222
- return {
223
- content: [{ type: "text", text: `HTTP ${response.status}: ${text}` }],
224
- isError: true,
225
- };
723
+ const result = await executeX402Request(payFetch, normalized);
724
+ return result;
226
725
  }
227
- // 上游常以 HTTP 200 + 业务 JSON(message 含 insufficient balance)表示余额不足,需同样拉取钱包余额
228
- if (insufficientBalance) {
229
- return {
230
- content: [
231
- {
232
- type: "text",
233
- text: await buildInsufficientBalanceReply(text),
234
- },
235
- ],
236
- isError: true,
237
- };
726
+ catch (err) {
727
+ return await handleRequestError(err);
238
728
  }
239
- return { content: [{ type: "text", text }], isError: false };
240
- }
241
- catch (err) {
242
- const message = err instanceof Error ? err.message : String(err);
243
- if (containsInsufficientBalanceSignal(message)) {
244
- return {
245
- content: [
246
- {
247
- type: "text",
248
- text: await buildInsufficientBalanceReply(message),
249
- },
250
- ],
251
- isError: true,
252
- };
253
- }
254
- const hint = message.toLowerCase().includes("fetch") || message.toLowerCase().includes("econnrefused")
255
- ? " 请确认 url 可访问;402 支付需托管钱包已登录且有足够余额。"
256
- : "";
257
- return {
258
- content: [{ type: "text", text: `请求失败: ${message}.${hint}` }],
259
- isError: true,
260
- };
261
729
  }
730
+ return createErrorResponse(`未知工具: ${name}`);
262
731
  });
263
732
  const stdio = new StdioServerTransport();
264
733
  await server.connect(stdio);