@x402scan/mcp 0.0.7-beta.0 → 0.0.7-beta.1

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/esm/index.js CHANGED
@@ -1,28 +1,23 @@
1
1
  #!/usr/bin/env node
2
+ import { ok, resultFromPromise, err, resultFromThrowable } from '@x402scan/neverthrow';
2
3
  import z$1, { z } from 'zod';
3
- import { getAddress, formatUnits } from 'viem';
4
+ import open from 'open';
5
+ import * as fs from 'fs/promises';
6
+ import * as fs2 from 'fs';
7
+ import fs2__default, { existsSync } from 'fs';
4
8
  import path2, { join } from 'path';
5
9
  import os, { homedir } from 'os';
6
- import * as fs from 'fs';
7
- import fs__default, { appendFileSync } from 'fs';
8
10
  import { polygon, arbitrum, optimism, sepolia, mainnet, baseSepolia, base } from 'viem/chains';
9
- import { intro, outro, log as log$1, confirm, stream, spinner, select, text } from '@clack/prompts';
10
- import { errAsync, ResultAsync, err } from 'neverthrow';
11
- import chalk from 'chalk';
12
- import open from 'open';
13
- import { x402HTTPClient, x402Client } from '@x402/core/client';
11
+ import { getAddress, formatUnits } from 'viem';
12
+ import { encodeSIWxHeader, createSIWxPayload } from '@x402scan/siwx';
13
+ import { x402Client, x402HTTPClient } from '@x402/core/client';
14
14
  import { ExactEvmScheme } from '@x402/evm/exact/client';
15
- import { wrapFetchWithPayment } from '@x402/fetch';
16
- import { base58 } from '@scure/base';
17
- import 'tweetnacl';
18
- import { SiweMessage } from 'siwe';
19
- import { safeBase64Encode } from '@x402/core/utils';
20
15
  import 'url';
21
- import { randomBytes } from 'crypto';
22
- import * as fs3 from 'fs/promises';
23
- import { privateKeyToAccount } from 'viem/accounts';
16
+ import { privateKeyToAccount, generatePrivateKey } from 'viem/accounts';
24
17
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
25
18
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
19
+ import { intro, outro, log as log$1, select, confirm, stream, spinner, text } from '@clack/prompts';
20
+ import chalk2 from 'chalk';
26
21
  import process2 from 'process';
27
22
  import * as TOML from '@iarna/toml';
28
23
  import yaml from 'js-yaml';
@@ -40,10 +35,10 @@ var __export = (target, all) => {
40
35
  __defProp(target, name, { get: all[name], enumerable: true });
41
36
  };
42
37
 
43
- // src/install/clients.ts
38
+ // src/cli/install/clients.ts
44
39
  var Clients, clientMetadata;
45
40
  var init_clients = __esm({
46
- "src/install/clients.ts"() {
41
+ "src/cli/install/clients.ts"() {
47
42
  Clients = /* @__PURE__ */ ((Clients2) => {
48
43
  Clients2["ClaudeCode"] = "claude-code";
49
44
  Clients2["Cursor"] = "cursor";
@@ -129,79 +124,389 @@ var init_clients = __esm({
129
124
  };
130
125
  }
131
126
  });
127
+ var errorType, fetchErr, fetchHttpErr, safeFetch, safeFetchJson, safeParseResponse, isFetchError;
128
+ var init_fetch = __esm({
129
+ "src/shared/neverthrow/fetch/index.ts"() {
130
+ errorType = "fetch";
131
+ fetchErr = (surface3, error) => err(errorType, surface3, error);
132
+ fetchHttpErr = (surface3, response) => fetchErr(surface3, {
133
+ cause: "http",
134
+ statusCode: response.status,
135
+ message: response.statusText,
136
+ response
137
+ });
138
+ safeFetch = (surface3, request) => {
139
+ return resultFromPromise(
140
+ errorType,
141
+ surface3,
142
+ fetch(request),
143
+ (error) => ({
144
+ cause: "network",
145
+ message: error instanceof Error ? error.message : "Network error"
146
+ })
147
+ );
148
+ };
149
+ safeFetchJson = (surface3, request) => {
150
+ return safeFetch(surface3, request).andThen((response) => {
151
+ if (!response.ok) {
152
+ return fetchHttpErr(surface3, response);
153
+ }
154
+ return resultFromPromise(
155
+ errorType,
156
+ surface3,
157
+ response.json(),
158
+ () => ({
159
+ cause: "parse",
160
+ message: "Could not parse JSON from response",
161
+ statusCode: response.status,
162
+ contentType: response.headers.get("content-type") ?? "Not specified"
163
+ })
164
+ );
165
+ });
166
+ };
167
+ safeParseResponse = (surface3, response) => {
168
+ return resultFromPromise(
169
+ errorType,
170
+ surface3,
171
+ (async () => {
172
+ const contentType = response.headers.get("content-type") ?? "";
173
+ switch (contentType) {
174
+ case "application/json":
175
+ return {
176
+ type: "json",
177
+ data: await response.json()
178
+ };
179
+ case "image/png":
180
+ case "image/jpeg":
181
+ case "image/gif":
182
+ case "image/webp":
183
+ case "image/svg+xml":
184
+ case "image/tiff":
185
+ case "image/bmp":
186
+ case "image/ico":
187
+ return {
188
+ type: "image",
189
+ mimeType: contentType,
190
+ data: await response.arrayBuffer()
191
+ };
192
+ case "audio/":
193
+ return {
194
+ type: "audio",
195
+ mimeType: contentType,
196
+ data: await response.arrayBuffer()
197
+ };
198
+ case "video/":
199
+ return {
200
+ type: "video",
201
+ mimeType: contentType,
202
+ data: await response.arrayBuffer()
203
+ };
204
+ case "application/pdf":
205
+ return {
206
+ type: "pdf",
207
+ mimeType: contentType,
208
+ data: await response.arrayBuffer()
209
+ };
210
+ case "application/octet-stream":
211
+ return {
212
+ type: "octet-stream",
213
+ mimeType: contentType,
214
+ data: await response.arrayBuffer()
215
+ };
216
+ case "multipart/form-data":
217
+ return { type: "formData", data: await response.formData() };
218
+ case "text/":
219
+ return { type: "text", data: await response.text() };
220
+ default:
221
+ throw new Error(`Unsupported content type: ${contentType}`);
222
+ }
223
+ })(),
224
+ (e) => ({
225
+ cause: "parse",
226
+ message: e instanceof Error ? e.message : "Could not parse response",
227
+ statusCode: response.status,
228
+ contentType: response.headers.get("content-type") ?? "Not specified"
229
+ })
230
+ );
231
+ };
232
+ isFetchError = (error) => {
233
+ return error.type === errorType;
234
+ };
235
+ }
236
+ });
237
+ var type, jsonErr, safeStringifyJson, safeParseJson;
238
+ var init_json = __esm({
239
+ "src/shared/neverthrow/json/index.ts"() {
240
+ type = "json";
241
+ jsonErr = (surface3, error) => {
242
+ return err(type, surface3, error);
243
+ };
244
+ safeStringifyJson = (surface3, value) => {
245
+ return resultFromThrowable(
246
+ type,
247
+ surface3,
248
+ () => JSON.stringify(value),
249
+ () => ({
250
+ cause: "stringify",
251
+ message: "Could not stringify JSON"
252
+ })
253
+ );
254
+ };
255
+ safeParseJson = (surface3, value) => {
256
+ return resultFromThrowable(
257
+ type,
258
+ surface3,
259
+ () => JSON.parse(value),
260
+ (e) => ({
261
+ cause: "parse",
262
+ message: e instanceof Error ? e.message : "Could not parse JSON"
263
+ })
264
+ );
265
+ };
266
+ }
267
+ });
268
+
269
+ // src/server/tools/response/lib.ts
270
+ var parsedResponseToToolContentPart;
271
+ var init_lib = __esm({
272
+ "src/server/tools/response/lib.ts"() {
273
+ parsedResponseToToolContentPart = (data) => {
274
+ switch (data.type) {
275
+ case "json":
276
+ return {
277
+ type: "text",
278
+ text: JSON.stringify(data.data, null, 2)
279
+ };
280
+ case "image":
281
+ return {
282
+ type: "image",
283
+ mimeType: data.mimeType,
284
+ data: Buffer.from(data.data).toString("base64")
285
+ };
286
+ case "audio":
287
+ return {
288
+ type: "audio",
289
+ mimeType: data.mimeType,
290
+ data: Buffer.from(data.data).toString("base64")
291
+ };
292
+ case "text":
293
+ return { type: "text", text: data.data };
294
+ default:
295
+ return {
296
+ type: "text",
297
+ text: `Unsupported response type: ${data.type}`
298
+ };
299
+ }
300
+ };
301
+ }
302
+ });
132
303
 
133
- // src/server/lib/response.ts
134
- var mcpSuccess, mcpError;
135
- var init_response = __esm({
136
- "src/server/lib/response.ts"() {
137
- mcpSuccess = (data) => {
304
+ // src/server/tools/response/error.ts
305
+ var buildMcpError, mcpErrorJson, mcpError, mcpErrorFetch;
306
+ var init_error = __esm({
307
+ "src/server/tools/response/error.ts"() {
308
+ init_json();
309
+ init_lib();
310
+ init_fetch();
311
+ buildMcpError = (content) => {
138
312
  return {
139
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
313
+ content,
314
+ isError: true
140
315
  };
141
316
  };
142
- mcpError = (error, context) => {
143
- const message = error instanceof Error ? error.message : typeof error === "string" ? error : String(error);
144
- const details = error instanceof Error && error.cause ? { cause: JSON.stringify(error.cause) } : void 0;
317
+ mcpErrorJson = (error) => {
318
+ return safeStringifyJson("mcp-error-json", error).match(
319
+ (success) => buildMcpError([{ type: "text", text: success }]),
320
+ (error2) => buildMcpError([
321
+ { type: "text", text: JSON.stringify(error2, null, 2) }
322
+ ])
323
+ );
324
+ };
325
+ mcpError = async (err9) => {
326
+ const { error } = err9;
327
+ if (isFetchError(error)) {
328
+ switch (error.cause) {
329
+ case "network":
330
+ case "parse":
331
+ return mcpErrorJson({ ...error });
332
+ case "http":
333
+ const { response, ...rest } = error;
334
+ const parseResponseResult = await safeParseResponse(
335
+ "mcp-error-fetch-parse-response",
336
+ response
337
+ );
338
+ return buildMcpError([
339
+ { type: "text", text: JSON.stringify(rest, null, 2) },
340
+ ...parseResponseResult.match(
341
+ (success) => [parsedResponseToToolContentPart(success)],
342
+ () => []
343
+ )
344
+ ]);
345
+ }
346
+ }
347
+ return mcpErrorJson({ ...error });
348
+ };
349
+ mcpErrorFetch = async (surface3, response) => {
350
+ return mcpError(fetchHttpErr(surface3, response));
351
+ };
352
+ }
353
+ });
354
+
355
+ // src/server/tools/response/success.ts
356
+ var buildMcpSuccess, mcpSuccessJson, mcpSuccessResponse;
357
+ var init_success = __esm({
358
+ "src/server/tools/response/success.ts"() {
359
+ init_json();
360
+ init_error();
361
+ init_lib();
362
+ buildMcpSuccess = (content) => {
145
363
  return {
146
- content: [
147
- {
148
- type: "text",
149
- text: JSON.stringify(
150
- {
151
- error: message,
152
- ...details && { details },
153
- ...context && { context }
154
- },
155
- null,
156
- 2
157
- )
158
- }
159
- ],
160
- isError: true
364
+ content
161
365
  };
162
366
  };
367
+ mcpSuccessJson = (data) => {
368
+ return safeStringifyJson("mcp-success-text", data).match(
369
+ (success) => buildMcpSuccess([{ type: "text", text: success }]),
370
+ (error) => mcpErrorJson(error)
371
+ );
372
+ };
373
+ mcpSuccessResponse = (data, extra) => {
374
+ const parsedExtra = extra ? safeStringifyJson("mcp-success-extra", extra).match(
375
+ (success) => success,
376
+ () => void 0
377
+ ) : void 0;
378
+ return buildMcpSuccess([
379
+ parsedResponseToToolContentPart(data),
380
+ ...parsedExtra ? [{ type: "text", text: parsedExtra }] : []
381
+ ]);
382
+ };
383
+ }
384
+ });
385
+
386
+ // src/server/tools/response/index.ts
387
+ var init_response = __esm({
388
+ "src/server/tools/response/index.ts"() {
389
+ init_success();
390
+ init_error();
163
391
  }
164
392
  });
165
- var ethereumAddressSchema, ethereumPrivateKeySchema, requestSchema, requestWithHeadersSchema;
166
- var init_schemas = __esm({
167
- "src/server/lib/schemas.ts"() {
168
- ethereumAddressSchema = z$1.string().regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address").transform((address) => getAddress(address));
169
- ethereumPrivateKeySchema = z$1.string().regex(/^0x[a-fA-F0-9]{64}$/, "Invalid Ethereum private key").transform((privateKey) => privateKey);
393
+ var requestSchema, buildRequest;
394
+ var init_request = __esm({
395
+ "src/server/tools/lib/request.ts"() {
170
396
  requestSchema = z$1.object({
171
397
  url: z$1.url().describe("The endpoint URL"),
172
398
  method: z$1.enum(["GET", "POST", "PUT", "DELETE", "PATCH"]).default("GET").describe("HTTP method"),
173
- body: z$1.unknown().optional().describe("Request body for POST/PUT/PATCH methods")
174
- });
175
- requestWithHeadersSchema = requestSchema.extend({
399
+ body: z$1.unknown().optional().describe("Request body for POST/PUT/PATCH methods"),
176
400
  headers: z$1.record(z$1.string(), z$1.string()).optional().describe("Additional headers to include").default({})
177
401
  });
402
+ buildRequest = (input) => {
403
+ return new Request(input.url, {
404
+ method: input.method,
405
+ body: input.body ? typeof input.body === "string" ? input.body : JSON.stringify(input.body) : void 0,
406
+ headers: {
407
+ ...input.body ? { "Content-Type": "application/json" } : {},
408
+ ...input.headers
409
+ }
410
+ });
411
+ };
412
+ }
413
+ });
414
+ var getBaseUrl, getDepositLink, openDepositLink;
415
+ var init_utils = __esm({
416
+ "src/shared/utils.ts"() {
417
+ getBaseUrl = (dev) => {
418
+ return dev ? "http://localhost:3000" : "https://x402scan.com";
419
+ };
420
+ getDepositLink = (address, flags) => {
421
+ return `${getBaseUrl(flags.dev)}/mcp/deposit/${address}`;
422
+ };
423
+ openDepositLink = async (address, flags) => {
424
+ const depositLink = getDepositLink(address, flags);
425
+ await open(depositLink);
426
+ };
178
427
  }
179
428
  });
180
429
 
181
- // src/server/types.ts
182
- var init_types = __esm({
183
- "src/server/types.ts"() {
430
+ // src/shared/balance.ts
431
+ var getBalance;
432
+ var init_balance = __esm({
433
+ "src/shared/balance.ts"() {
434
+ init_utils();
435
+ init_fetch();
436
+ getBalance = async ({
437
+ address,
438
+ flags,
439
+ surface: surface3
440
+ }) => {
441
+ const url = `${getBaseUrl(flags.dev)}/api/rpc/balance/${address}`;
442
+ const res = await safeFetchJson(
443
+ surface3,
444
+ new Request(url, {
445
+ method: "GET",
446
+ headers: {
447
+ accept: "application/json"
448
+ }
449
+ })
450
+ );
451
+ return res;
452
+ };
184
453
  }
185
454
  });
186
- var BASE_DIRECTORY, configFile;
455
+ var errorType2, fsErr, fsResultFromPromise, safeReadFile, safeWriteFile, safeAppendFile, safeChmod, safeFileExists;
187
456
  var init_fs = __esm({
188
- "src/lib/fs.ts"() {
457
+ "src/shared/neverthrow/fs/index.ts"() {
458
+ errorType2 = "fs";
459
+ fsErr = (surface3, error) => err(errorType2, surface3, error);
460
+ fsResultFromPromise = (surface3, promise, error) => resultFromPromise(errorType2, surface3, promise, error);
461
+ safeReadFile = (surface3, path3) => fsResultFromPromise(surface3, fs.readFile(path3, "utf-8"), () => ({
462
+ cause: "file_not_readable",
463
+ message: "Failed to read file"
464
+ }));
465
+ safeWriteFile = (surface3, path3, data) => fsResultFromPromise(surface3, fs.writeFile(path3, data), () => ({
466
+ cause: "file_not_writable",
467
+ message: "Failed to write file"
468
+ }));
469
+ safeAppendFile = (surface3, path3, data) => fsResultFromPromise(surface3, fs.appendFile(path3, data), () => ({
470
+ cause: "file_not_writable",
471
+ message: "Failed to append file"
472
+ }));
473
+ safeChmod = (surface3, path3, mode) => fsResultFromPromise(surface3, fs.chmod(path3, mode), () => ({
474
+ cause: "file_not_chmodable",
475
+ message: "Failed to chmod file"
476
+ }));
477
+ safeFileExists = (surface3, path3) => {
478
+ const fileExists = existsSync(path3);
479
+ if (fileExists) {
480
+ return ok(true);
481
+ }
482
+ return err(errorType2, surface3, {
483
+ cause: "file_not_found",
484
+ message: "File not found"
485
+ });
486
+ };
487
+ }
488
+ });
489
+ var BASE_DIRECTORY, configFile;
490
+ var init_fs2 = __esm({
491
+ "src/shared/fs.ts"() {
189
492
  BASE_DIRECTORY = join(homedir(), ".x402scan-mcp");
190
- if (!fs.existsSync(BASE_DIRECTORY)) {
191
- fs.mkdirSync(BASE_DIRECTORY, { recursive: true });
493
+ if (!fs2.existsSync(BASE_DIRECTORY)) {
494
+ fs2.mkdirSync(BASE_DIRECTORY, { recursive: true });
192
495
  }
193
- configFile = (name) => {
194
- if (!fs.existsSync(BASE_DIRECTORY)) {
195
- fs.mkdirSync(BASE_DIRECTORY, { recursive: true });
496
+ configFile = (name, defaultValue) => {
497
+ if (!fs2.existsSync(BASE_DIRECTORY)) {
498
+ fs2.mkdirSync(BASE_DIRECTORY, { recursive: true });
196
499
  }
197
500
  const filePath = join(BASE_DIRECTORY, name);
198
- if (!fs.existsSync(filePath)) {
199
- fs.writeFileSync(filePath, "{}");
501
+ if (!fs2.existsSync(filePath)) {
502
+ fs2.writeFileSync(filePath, defaultValue);
200
503
  }
201
504
  return filePath;
202
505
  };
203
506
  }
204
507
  });
508
+
509
+ // src/shared/log.ts
205
510
  function format(args) {
206
511
  return args.map(
207
512
  (a) => typeof a === "object" && a !== null ? JSON.stringify(a) : String(a)
@@ -211,19 +516,17 @@ function write(level, msg, args) {
211
516
  const formatted = args.length ? `${msg} ${format(args)}` : msg;
212
517
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [${level}] ${formatted}
213
518
  `;
214
- try {
215
- appendFileSync(LOG_FILE, line);
216
- } catch {
217
- }
519
+ safeAppendFile("log", LOG_FILE, line);
218
520
  if (process.env.X402_DEBUG === "true") {
219
521
  console.error(`[x402scan] ${formatted}`);
220
522
  }
221
523
  }
222
524
  var LOG_FILE, DEBUG, log;
223
525
  var init_log = __esm({
224
- "src/lib/log.ts"() {
526
+ "src/shared/log.ts"() {
225
527
  init_fs();
226
- LOG_FILE = configFile("mcp.log");
528
+ init_fs2();
529
+ LOG_FILE = configFile("mcp.log", "");
227
530
  DEBUG = process.env.X402_DEBUG === "true";
228
531
  log = {
229
532
  info: (msg, ...args) => write("INFO", msg, args),
@@ -233,6 +536,53 @@ var init_log = __esm({
233
536
  };
234
537
  }
235
538
  });
539
+
540
+ // src/server/tools/lib/check-balance.ts
541
+ var checkBalance;
542
+ var init_check_balance = __esm({
543
+ "src/server/tools/lib/check-balance.ts"() {
544
+ init_balance();
545
+ init_utils();
546
+ init_log();
547
+ checkBalance = async ({
548
+ server,
549
+ address,
550
+ amountNeeded,
551
+ message,
552
+ flags,
553
+ surface: surface3
554
+ }) => {
555
+ const balanceResult = await getBalance({ address, flags, surface: surface3 });
556
+ if (balanceResult.isErr()) {
557
+ log.error(JSON.stringify(balanceResult.error, null, 2));
558
+ return;
559
+ }
560
+ const balance = balanceResult.value;
561
+ if (balance.balance < amountNeeded) {
562
+ const capabilities = server.server.getClientCapabilities();
563
+ if (!capabilities?.elicitation) {
564
+ throw new Error(
565
+ `${message(balance.balance)}
566
+
567
+ You can deposit USDC at ${getDepositLink(address, flags)}`
568
+ );
569
+ }
570
+ const result = await server.server.elicitInput({
571
+ mode: "form",
572
+ message: message(balance.balance),
573
+ requestedSchema: {
574
+ type: "object",
575
+ properties: {}
576
+ }
577
+ });
578
+ if (result.action === "accept") {
579
+ await openDepositLink(address, flags);
580
+ }
581
+ }
582
+ return balance.balance;
583
+ };
584
+ }
585
+ });
236
586
  function toCaip2(network) {
237
587
  if (network.startsWith("eip155:")) return network;
238
588
  return V1_TO_CAIP2[network.toLowerCase()] ?? network;
@@ -245,7 +595,7 @@ function getChainName(network) {
245
595
  }
246
596
  var CHAIN_CONFIGS, V1_TO_CAIP2, DEFAULT_NETWORK;
247
597
  var init_networks = __esm({
248
- "src/lib/networks.ts"() {
598
+ "src/shared/networks.ts"() {
249
599
  CHAIN_CONFIGS = {
250
600
  "eip155:8453": {
251
601
  chain: base,
@@ -304,841 +654,316 @@ var init_networks = __esm({
304
654
  });
305
655
  var tokenStringToNumber;
306
656
  var init_token = __esm({
307
- "src/lib/token.ts"() {
657
+ "src/shared/token.ts"() {
308
658
  tokenStringToNumber = (amount, decimals = 6) => {
309
659
  return Number(formatUnits(BigInt(amount), decimals));
310
660
  };
311
661
  }
312
662
  });
313
-
314
- // src/lib/utils.ts
315
- var getBaseUrl;
316
- var init_utils = __esm({
317
- "src/lib/utils.ts"() {
318
- getBaseUrl = (dev) => {
319
- return dev ? "http://localhost:3000" : "https://x402scan.com";
663
+ var errorType3, x402Err, x402ResultFromPromise, x402ResultFromThrowable, safeGetPaymentRequired, safeCreatePaymentPayload, safeGetPaymentSettlement, safeCreateSIWxPayload;
664
+ var init_x402 = __esm({
665
+ "src/shared/neverthrow/x402/index.ts"() {
666
+ errorType3 = "x402";
667
+ x402Err = (cause, error) => err(errorType3, cause, error);
668
+ x402ResultFromPromise = (surface3, promise, error) => resultFromPromise(errorType3, surface3, promise, error);
669
+ x402ResultFromThrowable = (surface3, fn, error) => resultFromThrowable(errorType3, surface3, fn, error);
670
+ safeGetPaymentRequired = (surface3, client, response) => {
671
+ return x402ResultFromPromise(
672
+ surface3,
673
+ response.json().then(
674
+ (json) => client.getPaymentRequiredResponse(
675
+ (name) => response.headers.get(name),
676
+ json
677
+ ),
678
+ () => client.getPaymentRequiredResponse((name) => response.headers.get(name))
679
+ ),
680
+ (error) => ({
681
+ cause: "parse_payment_required",
682
+ message: error instanceof Error ? error.message : "Failed to parse payment required"
683
+ })
684
+ );
685
+ };
686
+ safeCreatePaymentPayload = (surface3, client, paymentRequired) => {
687
+ return x402ResultFromPromise(
688
+ surface3,
689
+ client.createPaymentPayload(paymentRequired),
690
+ (error) => ({
691
+ cause: "create_payment_payload",
692
+ message: error instanceof Error ? error.message : "Failed to create payment payload"
693
+ })
694
+ );
695
+ };
696
+ safeGetPaymentSettlement = (surface3, client, response) => {
697
+ return x402ResultFromThrowable(
698
+ surface3,
699
+ () => client.getPaymentSettleResponse((name) => response.headers.get(name)),
700
+ (error) => ({
701
+ cause: "get_payment_settlement",
702
+ message: error instanceof Error ? error.message : "Failed to get payment settlement"
703
+ })
704
+ );
705
+ };
706
+ safeCreateSIWxPayload = (surface3, serverInfo, signer) => {
707
+ return x402ResultFromPromise(
708
+ surface3,
709
+ createSIWxPayload(serverInfo, signer),
710
+ (error) => ({
711
+ cause: "create_siwx_payload",
712
+ message: error instanceof Error ? error.message : "Failed to create SIWX payload"
713
+ })
714
+ );
320
715
  };
321
716
  }
322
717
  });
323
- async function getUSDCBalance(address, flags) {
324
- const url = `${getBaseUrl(flags.dev)}/api/rpc/balance/${address}`;
325
- const res = await fetch(url, {
326
- method: "GET",
327
- headers: {
328
- accept: "application/json"
718
+ function safeWrapFetchWithPayment(client) {
719
+ return async (input, init) => {
720
+ const request = new Request(input, init);
721
+ const clonedRequest = request.clone();
722
+ const probeResult = await safeFetch(toolName, request);
723
+ if (probeResult.isErr()) {
724
+ return probeResult;
329
725
  }
330
- });
331
- if (!res.ok) {
332
- throw new Error(
333
- `Balance API request failed (${res.status} ${res.statusText})`
726
+ if (probeResult.value.status !== 402) {
727
+ return probeResult;
728
+ }
729
+ const response = probeResult.value;
730
+ const paymentRequiredResult = await safeGetPaymentRequired(
731
+ toolName,
732
+ client,
733
+ response
334
734
  );
335
- }
336
- let json;
337
- try {
338
- json = await res.json();
339
- } catch {
340
- throw new Error(
341
- `Failed to parse balance API response as JSON (${res.status} ${res.statusText})`
735
+ if (paymentRequiredResult.isErr()) {
736
+ return paymentRequiredResult;
737
+ }
738
+ const paymentRequired = paymentRequiredResult.value;
739
+ const paymentPayloadResult = await safeCreatePaymentPayload(
740
+ toolName,
741
+ client,
742
+ paymentRequired
342
743
  );
343
- }
344
- const result = balanceApiResponseSchema.safeParse(json);
345
- if (!result.success) {
346
- throw new Error(
347
- `Failed to safeParse balance API response (${res.status} ${res.statusText})`
744
+ if (paymentPayloadResult.isErr()) {
745
+ return paymentPayloadResult;
746
+ }
747
+ const paymentPayload = paymentPayloadResult.value;
748
+ const paymentHeaders = client.encodePaymentSignatureHeader(paymentPayload);
749
+ if (clonedRequest.headers.has("PAYMENT-SIGNATURE") || clonedRequest.headers.has("X-PAYMENT")) {
750
+ return x402Err(toolName, {
751
+ cause: "payment_already_attempted",
752
+ message: "Payment already attempted"
753
+ });
754
+ }
755
+ for (const [key, value] of Object.entries(paymentHeaders)) {
756
+ clonedRequest.headers.set(key, value);
757
+ }
758
+ clonedRequest.headers.set(
759
+ "Access-Control-Expose-Headers",
760
+ "PAYMENT-RESPONSE,X-PAYMENT-RESPONSE"
348
761
  );
349
- }
350
- return {
351
- balanceFormatted: result.data.balance,
352
- balanceRaw: result.data.rawBalance
762
+ return await safeFetch(toolName, clonedRequest);
353
763
  };
354
764
  }
355
- var balanceApiResponseSchema;
356
- var init_balance = __esm({
357
- "src/lib/balance.ts"() {
358
- init_utils();
359
- balanceApiResponseSchema = z.object({
360
- address: z.string(),
361
- chain: z.number().int(),
362
- balance: z.coerce.number(),
363
- rawBalance: z.string()
364
- });
365
- }
366
- });
367
- var wait;
368
- var init_wait = __esm({
369
- "src/lib/wait.ts"() {
370
- wait = async ({ startText, stopText, ms }) => {
371
- const { start: startSpinner, stop: stopSpinner } = spinner();
372
- startSpinner(startText);
373
- await new Promise((resolve) => setTimeout(resolve, ms));
374
- stopSpinner(stopText);
375
- };
376
- }
377
- });
378
- function safeFetch(input, init) {
379
- return ResultAsync.fromPromise(
380
- fetch(input, init),
381
- (error) => ({
382
- type: "network",
383
- message: "Network error",
384
- error: error instanceof Error ? error : new Error(String(error))
385
- })
386
- );
387
- }
388
- var safeFetchJson;
389
- var init_safe_fetch = __esm({
390
- "src/lib/safe-fetch.ts"() {
391
- safeFetchJson = (input, init, errorMessage) => {
392
- return safeFetch(input, init).andThen((response) => {
393
- if (!response.ok) {
394
- return ResultAsync.fromSafePromise(
395
- response.json().catch(() => void 0)
396
- ).andThen(
397
- (json) => err({
398
- type: "http",
399
- message: json !== void 0 && errorMessage ? errorMessage(json) : response.statusText,
400
- status: response.status,
401
- headers: response.headers,
402
- json
403
- })
765
+ var toolName, registerFetchX402ResourceTool;
766
+ var init_x402_fetch = __esm({
767
+ "src/server/tools/x402-fetch.ts"() {
768
+ init_fetch();
769
+ init_response();
770
+ init_request();
771
+ init_check_balance();
772
+ init_networks();
773
+ init_token();
774
+ init_x402();
775
+ toolName = "fetch";
776
+ registerFetchX402ResourceTool = ({
777
+ server,
778
+ account,
779
+ flags
780
+ }) => {
781
+ server.registerTool(
782
+ toolName,
783
+ {
784
+ description: "Makes an http fetch request. If the request is to an x402-protected resource, it will handle payment automatically.",
785
+ inputSchema: requestSchema
786
+ },
787
+ async (input) => {
788
+ const coreClient = x402Client.fromConfig({
789
+ schemes: [
790
+ { network: DEFAULT_NETWORK, client: new ExactEvmScheme(account) }
791
+ ]
792
+ });
793
+ coreClient.onBeforePaymentCreation(async ({ selectedRequirements }) => {
794
+ const amount = tokenStringToNumber(selectedRequirements.amount);
795
+ await checkBalance({
796
+ surface: toolName,
797
+ server,
798
+ address: account.address,
799
+ amountNeeded: amount,
800
+ message: (balance) => `This request costs ${amount} USDC. Your current balance is ${balance} USDC.`,
801
+ flags
802
+ });
803
+ });
804
+ const client = new x402HTTPClient(coreClient);
805
+ const fetchWithPay = safeWrapFetchWithPayment(client);
806
+ const fetchResult = await fetchWithPay(buildRequest(input));
807
+ if (fetchResult.isErr()) {
808
+ return mcpError(fetchResult);
809
+ }
810
+ const response = fetchResult.value;
811
+ if (!response.ok) {
812
+ return mcpErrorFetch(toolName, response);
813
+ }
814
+ const parseResponseResult = await safeParseResponse(toolName, response);
815
+ if (parseResponseResult.isErr()) {
816
+ return mcpError(parseResponseResult);
817
+ }
818
+ const settlementResult = safeGetPaymentSettlement(
819
+ toolName,
820
+ client,
821
+ response
822
+ );
823
+ return mcpSuccessResponse(
824
+ parseResponseResult.value,
825
+ settlementResult.isOk() ? { payment: settlementResult.value } : void 0
404
826
  );
405
827
  }
406
- return ResultAsync.fromPromise(
407
- response.json(),
408
- (error) => ({
409
- type: "parse",
410
- message: "Could not parse JSON from response",
411
- error: error instanceof Error ? error : new Error(String(error))
412
- })
413
- );
414
- });
828
+ );
415
829
  };
416
830
  }
417
831
  });
418
- var STATE_FILE, stateSchema, getState, setState;
419
- var init_state = __esm({
420
- "src/lib/state.ts"() {
421
- init_fs();
422
- init_log();
423
- STATE_FILE = configFile("state.json");
424
- stateSchema = z$1.looseObject({
425
- redeemedCodes: z$1.array(z$1.string())
426
- }).partial();
427
- getState = () => {
428
- if (!fs__default.existsSync(STATE_FILE)) {
429
- fs__default.writeFileSync(STATE_FILE, JSON.stringify({}));
430
- return {};
832
+
833
+ // src/server/lib/x402-extensions.ts
834
+ var getBazaarExtension, getInputSchema, getSiwxExtension;
835
+ var init_x402_extensions = __esm({
836
+ "src/server/lib/x402-extensions.ts"() {
837
+ getBazaarExtension = (extensions) => {
838
+ const { bazaar } = extensions ?? {};
839
+ if (!bazaar) {
840
+ return void 0;
431
841
  }
432
- const result = stateSchema.safeParse(
433
- JSON.parse(fs__default.readFileSync(STATE_FILE, "utf-8"))
434
- );
435
- if (!result.success) {
436
- log.error("Failed to parse state", { error: result.error });
437
- return {};
438
- }
439
- return result.data;
440
- };
441
- setState = (state) => {
442
- const existing = getState();
443
- const newState = stateSchema.parse({ ...existing, ...state });
444
- fs__default.writeFileSync(STATE_FILE, JSON.stringify(newState, null, 2));
445
- };
446
- }
447
- });
448
- var redeemInviteCode;
449
- var init_redeem_invite = __esm({
450
- "src/lib/redeem-invite.ts"() {
451
- init_safe_fetch();
452
- init_utils();
453
- init_state();
454
- redeemInviteCode = async ({
455
- code,
456
- dev,
457
- address
458
- }) => {
459
- const state = getState();
460
- if (state.redeemedCodes?.includes(code)) {
461
- return Promise.resolve(
462
- errAsync({
463
- success: false,
464
- message: "This invite code has already been redeemed"
465
- })
466
- );
467
- }
468
- return await safeFetchJson(
469
- `${getBaseUrl(dev)}/api/invite/redeem`,
470
- {
471
- method: "POST",
472
- headers: {
473
- "Content-Type": "application/json"
474
- },
475
- body: JSON.stringify({
476
- code,
477
- recipientAddr: address
478
- })
479
- },
480
- ({ message }) => message
481
- ).andTee((result) => {
482
- if (result.success) {
483
- setState({
484
- redeemedCodes: [...state.redeemedCodes ?? [], code]
485
- });
486
- }
487
- });
842
+ return bazaar;
488
843
  };
489
- }
490
- });
491
- var getDepositLink, openDepositLink, redeemInviteCodePrompt, promptDeposit;
492
- var init_deposit = __esm({
493
- "src/lib/deposit.ts"() {
494
- init_networks();
495
- init_wait();
496
- init_redeem_invite();
497
- init_utils();
498
- getDepositLink = (address, flags) => {
499
- return `${getBaseUrl(flags.dev)}/mcp/deposit/${address}`;
500
- };
501
- openDepositLink = async (address, flags) => {
502
- const depositLink = getDepositLink(address, flags);
503
- await open(depositLink);
504
- };
505
- redeemInviteCodePrompt = async (address, flags) => {
506
- const code = await text({
507
- message: "Enter your invite code",
508
- placeholder: "MRT-XXXXX",
509
- validate: (value) => {
510
- if (!value || value.trim().length === 0) {
511
- return "Please enter an invite code";
512
- }
513
- }
514
- });
515
- if (typeof code !== "string") {
516
- return false;
517
- }
518
- const s = spinner();
519
- s.start("Redeeming invite code...");
520
- const result = await redeemInviteCode({ code, dev: flags.dev, address });
521
- return result.match(
522
- async ({ data: { amount, txHash } }) => {
523
- s.stop("Invite code redeemed successfully!");
524
- await wait({
525
- startText: "Processing...",
526
- stopText: chalk.green(
527
- `${chalk.bold(amount)} USDC has been sent to your wallet!`
528
- ),
529
- ms: 1500
530
- });
531
- log$1.success(
532
- chalk.bold(`Your wallet has been funded with ${amount} USDC`)
533
- );
534
- if (txHash) {
535
- log$1.info(chalk.dim(`Transaction: https://basescan.org/tx/${txHash}`));
536
- }
537
- return true;
538
- },
539
- (error) => {
540
- s.stop("Invite code redemption failed");
541
- log$1.warning(
542
- chalk.yellow(`Failed to redeem invite code: ${error?.message}`)
543
- );
544
- return false;
545
- }
546
- );
547
- };
548
- promptDeposit = async (address, flags) => {
549
- const depositLink = getDepositLink(address, flags);
550
- const depositChoice = flags.yes ? "manual" : await select({
551
- message: chalk.bold("How would you like to deposit?"),
552
- initialValue: "guided",
553
- options: [
554
- {
555
- label: "Guided - Recommended",
556
- value: "guided",
557
- hint: "Online portal in x402scan"
558
- },
559
- {
560
- label: "Manual",
561
- value: "manual",
562
- hint: "Print deposit instructions"
563
- },
564
- {
565
- label: "Redeem Invite Code",
566
- value: "invite",
567
- hint: "Enter an invite code for starter money"
568
- },
569
- {
570
- label: "Skip",
571
- value: void 0,
572
- hint: "Skip deposit process - functionality limited"
573
- }
574
- ]
575
- });
576
- if (depositChoice === "guided") {
577
- await wait({
578
- startText: "Opening deposit page...",
579
- stopText: `Opening ${chalk.underline.hex("#2563eb")(depositLink)}`,
580
- ms: 1e3
581
- });
582
- await open(depositLink);
583
- } else if (depositChoice === "manual") {
584
- log$1.step(chalk.bold("Account Information"));
585
- log$1.message(`Address: ${address}`);
586
- log$1.message(`Network: ${getChainName(DEFAULT_NETWORK)}`);
587
- log$1.step(chalk.bold("Online Portal"));
588
- log$1.message(`${chalk.underline(depositLink)}`);
589
- } else if (depositChoice === "invite") {
590
- const redeemed = await redeemInviteCodePrompt(address, flags);
591
- if (!redeemed) {
592
- await promptDeposit(address, flags);
593
- }
594
- }
595
- };
596
- }
597
- });
598
-
599
- // src/server/lib/check-balance.ts
600
- var checkBalance;
601
- var init_check_balance = __esm({
602
- "src/server/lib/check-balance.ts"() {
603
- init_balance();
604
- init_deposit();
605
- checkBalance = async ({
606
- server,
607
- address,
608
- amountNeeded,
609
- message,
610
- flags
611
- }) => {
612
- const { balanceFormatted } = await getUSDCBalance(address, flags);
613
- if (balanceFormatted < amountNeeded) {
614
- const capabilities = server.server.getClientCapabilities();
615
- if (!capabilities?.elicitation) {
616
- throw new Error(
617
- `${message(balanceFormatted)}
618
-
619
- You can deposit USDC at ${getDepositLink(address, flags)}`
620
- );
621
- }
622
- const result = await server.server.elicitInput({
623
- mode: "form",
624
- message: message(balanceFormatted),
625
- requestedSchema: {
626
- type: "object",
627
- properties: {}
628
- }
629
- });
630
- if (result.action === "accept") {
631
- await openDepositLink(address, flags);
632
- }
633
- }
634
- return balanceFormatted;
635
- };
636
- }
637
- });
638
-
639
- // src/server/lib/parse-response.ts
640
- var parseResponse;
641
- var init_parse_response = __esm({
642
- "src/server/lib/parse-response.ts"() {
643
- parseResponse = async (response) => {
644
- try {
645
- const contentType = response.headers.get("content-type") ?? "";
646
- if (contentType.includes("application/json")) {
647
- return await response.json();
648
- } else if (contentType.includes("image/")) {
649
- return await response.arrayBuffer();
650
- } else {
651
- return await response.text();
652
- }
653
- } catch {
844
+ getInputSchema = (extensions) => getBazaarExtension(extensions)?.schema.properties.input;
845
+ getSiwxExtension = (extensions) => {
846
+ const siwx = extensions?.["sign-in-with-x"];
847
+ if (!siwx?.info) {
654
848
  return void 0;
655
849
  }
850
+ return siwx.info;
656
851
  };
657
852
  }
658
853
  });
659
- var registerFetchX402ResourceTool;
660
- var init_fetch_x402_resource = __esm({
661
- "src/server/tools/fetch-x402-resource.ts"() {
662
- init_response();
663
- init_schemas();
664
- init_types();
665
- init_log();
666
- init_networks();
667
- init_token();
668
- init_check_balance();
669
- init_parse_response();
670
- registerFetchX402ResourceTool = ({
671
- server,
672
- account,
673
- flags
674
- }) => {
675
- server.registerTool(
676
- "fetch",
677
- {
678
- description: "Fetches an x402-protected resource and handles payment automatically. If the resource is not x402-protected, it will return the raw response.",
679
- inputSchema: requestWithHeadersSchema
680
- },
681
- async ({ url, method, body, headers }) => {
682
- const coreClient = x402Client.fromConfig({
683
- schemes: [
684
- { network: DEFAULT_NETWORK, client: new ExactEvmScheme(account) }
685
- ]
686
- });
687
- let state = "initial_request" /* INITIAL_REQUEST */;
688
- coreClient.onBeforePaymentCreation(async ({ selectedRequirements }) => {
689
- const amount = tokenStringToNumber(selectedRequirements.amount);
690
- await checkBalance({
691
- server,
692
- address: account.address,
693
- amountNeeded: amount,
694
- message: (balance) => `This request costs ${amount} USDC. Your current balance is ${balance} USDC.`,
695
- flags
696
- });
697
- state = "payment_required" /* PAYMENT_REQUIRED */;
698
- });
699
- coreClient.onAfterPaymentCreation(async (ctx) => {
700
- state = "payment_created" /* PAYMENT_CREATED */;
701
- log.info("After payment creation", ctx);
702
- return Promise.resolve();
703
- });
704
- coreClient.onPaymentCreationFailure(async (ctx) => {
705
- state = "payment_failed" /* PAYMENT_FAILED */;
706
- log.info("Payment creation failure", ctx);
707
- return Promise.resolve();
708
- });
709
- const client = new x402HTTPClient(coreClient);
710
- const fetchWithPay = wrapFetchWithPayment(fetch, client);
711
- try {
712
- const response = await fetchWithPay(url, {
713
- method,
714
- body: typeof body === "string" ? body : body ? JSON.stringify(body) : void 0,
715
- headers: {
716
- ...body ? { "Content-Type": "application/json" } : {},
717
- ...headers
718
- }
719
- });
720
- if (!response.ok) {
721
- const errorResponse = {
722
- data: await parseResponse(response),
723
- statusCode: response.status,
724
- state
725
- };
726
- if (response.status === 402) {
727
- return mcpError("Payment required", errorResponse);
728
- }
729
- return mcpError(
730
- response.statusText ?? "Request failed",
731
- errorResponse
732
- );
733
- }
734
- const getSettlement = () => {
735
- try {
736
- return client.getPaymentSettleResponse(
737
- (name) => response.headers.get(name)
738
- );
739
- } catch {
740
- return void 0;
741
- }
742
- };
743
- const settlement = getSettlement();
744
- return mcpSuccess({
745
- data: await parseResponse(response),
746
- payment: settlement
747
- });
748
- } catch (err2) {
749
- return mcpError(err2, { state });
750
- }
751
- }
752
- );
753
- };
754
- }
755
- });
756
-
757
- // src/server/lib/x402/protocol.ts
758
- function isV1Response(pr) {
759
- if (!pr || typeof pr !== "object") return false;
760
- const obj = pr;
761
- if (obj.x402Version === 1) return true;
762
- const accepts = obj.accepts;
763
- if (Array.isArray(accepts) && accepts.length > 0) {
764
- return "maxAmountRequired" in accepts[0];
765
- }
766
- return false;
767
- }
768
- function normalizeV1Requirement(req) {
769
- if (!req.maxAmountRequired) {
770
- throw new Error("v1 requirement missing maxAmountRequired field");
771
- }
772
- return {
773
- scheme: req.scheme,
774
- network: req.network,
775
- amount: req.maxAmountRequired,
776
- asset: req.asset,
777
- payTo: req.payTo,
778
- maxTimeoutSeconds: req.maxTimeoutSeconds,
779
- extra: req.extra,
780
- resource: req.resource,
781
- description: req.description,
782
- mimeType: req.mimeType
783
- };
784
- }
785
- function normalizeV2Requirement(req) {
786
- if (!req.amount) {
787
- throw new Error("v2 requirement missing amount field");
788
- }
789
- return {
790
- scheme: req.scheme,
791
- network: req.network,
792
- amount: req.amount,
793
- asset: req.asset,
794
- payTo: req.payTo,
795
- maxTimeoutSeconds: req.maxTimeoutSeconds,
796
- extra: req.extra
797
- };
798
- }
799
- function normalizePaymentRequired(pr) {
800
- const version = pr.x402Version ?? 1;
801
- if (isV1Response(pr)) {
802
- const v1 = pr;
803
- return {
804
- x402Version: 1,
805
- error: v1.error,
806
- accepts: v1.accepts.map(normalizeV1Requirement)
807
- };
808
- }
809
- const v2 = pr;
810
- return {
811
- x402Version: version,
812
- error: v2.error,
813
- accepts: v2.accepts.map(normalizeV2Requirement),
814
- resource: v2.resource,
815
- extensions: v2.extensions
816
- };
817
- }
818
- var init_protocol = __esm({
819
- "src/server/lib/x402/protocol.ts"() {
820
- }
821
- });
822
- function extractSolanaChainReference(chainId) {
823
- const [, reference] = chainId.split(":");
824
- return reference;
825
- }
826
- function formatSIWSMessage(info, address) {
827
- const lines = [
828
- `${info.domain} wants you to sign in with your Solana account:`,
829
- address,
830
- ""
831
- ];
832
- if (info.statement) {
833
- lines.push(info.statement, "");
834
- }
835
- lines.push(
836
- `URI: ${info.uri}`,
837
- `Version: ${info.version}`,
838
- `Chain ID: ${extractSolanaChainReference(info.chainId)}`,
839
- `Nonce: ${info.nonce}`,
840
- `Issued At: ${info.issuedAt}`
841
- );
842
- if (info.expirationTime) {
843
- lines.push(`Expiration Time: ${info.expirationTime}`);
844
- }
845
- if (info.notBefore) {
846
- lines.push(`Not Before: ${info.notBefore}`);
847
- }
848
- if (info.requestId) {
849
- lines.push(`Request ID: ${info.requestId}`);
850
- }
851
- if (info.resources && info.resources.length > 0) {
852
- lines.push("Resources:");
853
- for (const resource of info.resources) {
854
- lines.push(`- ${resource}`);
855
- }
856
- }
857
- return lines.join("\n");
858
- }
859
- function encodeBase58(bytes) {
860
- return base58.encode(bytes);
861
- }
862
- var init_solana = __esm({
863
- "src/server/vendor/sign-in-with-x/solana.ts"() {
864
- }
865
- });
866
-
867
- // src/server/vendor/sign-in-with-x/sign.ts
868
- function getEVMAddress(signer) {
869
- if (signer.account?.address) {
870
- return signer.account.address;
871
- }
872
- if (signer.address) {
873
- return signer.address;
874
- }
875
- throw new Error("EVM signer missing address");
876
- }
877
- function getSolanaAddress(signer) {
878
- const pk = signer.publicKey;
879
- return typeof pk === "string" ? pk : pk.toBase58();
880
- }
881
- async function signEVMMessage(message, signer) {
882
- if (signer.account) {
883
- return signer.signMessage({ message, account: signer.account });
884
- }
885
- return signer.signMessage({ message });
886
- }
887
- async function signSolanaMessage(message, signer) {
888
- const messageBytes = new TextEncoder().encode(message);
889
- const signatureBytes = await signer.signMessage(messageBytes);
890
- return encodeBase58(signatureBytes);
891
- }
892
- var init_sign = __esm({
893
- "src/server/vendor/sign-in-with-x/sign.ts"() {
894
- init_solana();
895
- }
896
- });
897
- function extractEVMChainId(chainId) {
898
- const match = /^eip155:(\d+)$/.exec(chainId);
899
- if (!match) {
900
- throw new Error(
901
- `Invalid EVM chainId format: ${chainId}. Expected eip155:<number>`
902
- );
903
- }
904
- return parseInt(match[1], 10);
905
- }
906
- function formatSIWEMessage(info, address) {
907
- const numericChainId = extractEVMChainId(info.chainId);
908
- const siweMessage = new SiweMessage({
909
- domain: info.domain,
910
- address,
911
- statement: info.statement,
912
- uri: info.uri,
913
- version: info.version,
914
- chainId: numericChainId,
915
- nonce: info.nonce,
916
- issuedAt: info.issuedAt,
917
- expirationTime: info.expirationTime,
918
- notBefore: info.notBefore,
919
- requestId: info.requestId,
920
- resources: info.resources
921
- });
922
- return siweMessage.prepareMessage();
923
- }
924
- var init_evm = __esm({
925
- "src/server/vendor/sign-in-with-x/evm.ts"() {
926
- }
927
- });
928
-
929
- // src/server/vendor/sign-in-with-x/message.ts
930
- function createSIWxMessage(serverInfo, address) {
931
- if (serverInfo.chainId.startsWith("eip155:")) {
932
- return formatSIWEMessage(serverInfo, address);
933
- }
934
- if (serverInfo.chainId.startsWith("solana:")) {
935
- return formatSIWSMessage(serverInfo, address);
936
- }
937
- throw new Error(
938
- `Unsupported chain namespace: ${serverInfo.chainId}. Supported: eip155:* (EVM), solana:* (Solana)`
939
- );
940
- }
941
- var init_message = __esm({
942
- "src/server/vendor/sign-in-with-x/message.ts"() {
943
- init_evm();
944
- init_solana();
945
- }
946
- });
947
-
948
- // src/server/vendor/sign-in-with-x/client.ts
949
- async function createSIWxPayload(serverExtension, signer) {
950
- const isSolana = serverExtension.chainId.startsWith("solana:");
951
- const address = isSolana ? getSolanaAddress(signer) : getEVMAddress(signer);
952
- const message = createSIWxMessage(serverExtension, address);
953
- const signature = isSolana ? await signSolanaMessage(message, signer) : await signEVMMessage(message, signer);
954
- return {
955
- domain: serverExtension.domain,
956
- address,
957
- statement: serverExtension.statement,
958
- uri: serverExtension.uri,
959
- version: serverExtension.version,
960
- chainId: serverExtension.chainId,
961
- type: serverExtension.type,
962
- nonce: serverExtension.nonce,
963
- issuedAt: serverExtension.issuedAt,
964
- expirationTime: serverExtension.expirationTime,
965
- notBefore: serverExtension.notBefore,
966
- requestId: serverExtension.requestId,
967
- resources: serverExtension.resources,
968
- signatureScheme: serverExtension.signatureScheme,
969
- signature
970
- };
971
- }
972
- var init_client = __esm({
973
- "src/server/vendor/sign-in-with-x/client.ts"() {
974
- init_sign();
975
- init_message();
976
- }
977
- });
978
- function encodeSIWxHeader(payload) {
979
- return safeBase64Encode(JSON.stringify(payload));
980
- }
981
- var init_encode = __esm({
982
- "src/server/vendor/sign-in-with-x/encode.ts"() {
983
- }
984
- });
985
- var registerAuthTools;
986
- var init_auth = __esm({
987
- "src/server/tools/auth.ts"() {
854
+ var toolName2, registerAuthTools;
855
+ var init_auth_fetch = __esm({
856
+ "src/server/tools/auth-fetch.ts"() {
857
+ init_fetch();
858
+ init_x402();
988
859
  init_response();
989
- init_protocol();
990
- init_client();
991
- init_encode();
992
- init_schemas();
860
+ init_request();
861
+ init_x402_extensions();
862
+ toolName2 = "fetchWithAuth";
993
863
  registerAuthTools = ({ server, account }) => {
994
864
  server.registerTool(
995
- "authed_call",
865
+ toolName2,
996
866
  {
997
867
  description: "Make a request to a SIWX-protected endpoint. Handles auth flow automatically: detects SIWX requirement from 402 response, signs proof with server-provided challenge, retries.",
998
- inputSchema: requestWithHeadersSchema
868
+ inputSchema: requestSchema
999
869
  },
1000
- async ({ url, method, body, headers }) => {
1001
- try {
1002
- const httpClient = new x402HTTPClient(new x402Client());
1003
- const firstResponse = await fetch(url, {
1004
- method,
1005
- headers: {
1006
- "Content-Type": "application/json",
1007
- ...headers
1008
- },
1009
- body: body ? JSON.stringify(body) : void 0
1010
- });
1011
- if (firstResponse.status !== 402) {
1012
- const responseHeaders2 = Object.fromEntries(
1013
- firstResponse.headers.entries()
1014
- );
1015
- if (firstResponse.ok) {
1016
- let data2;
1017
- const contentType2 = firstResponse.headers.get("content-type");
1018
- if (contentType2?.includes("application/json")) {
1019
- data2 = await firstResponse.json();
1020
- } else {
1021
- data2 = await firstResponse.text();
1022
- }
1023
- return mcpSuccess({
1024
- statusCode: firstResponse.status,
1025
- headers: responseHeaders2,
1026
- data: data2
1027
- });
1028
- }
1029
- let errorBody;
1030
- try {
1031
- errorBody = await firstResponse.json();
1032
- } catch {
1033
- errorBody = await firstResponse.text();
1034
- }
1035
- return mcpError(`HTTP ${firstResponse.status}`, {
1036
- statusCode: firstResponse.status,
1037
- headers: responseHeaders2,
1038
- body: errorBody
1039
- });
1040
- }
1041
- let rawBody;
1042
- try {
1043
- rawBody = await firstResponse.clone().json();
1044
- } catch {
1045
- rawBody = void 0;
1046
- }
1047
- const rawPaymentRequired = httpClient.getPaymentRequiredResponse(
1048
- (name) => firstResponse.headers.get(name),
1049
- rawBody
1050
- );
1051
- const paymentRequired = normalizePaymentRequired(rawPaymentRequired);
1052
- const siwxExtension = paymentRequired.extensions?.["sign-in-with-x"];
1053
- if (!siwxExtension?.info) {
1054
- return mcpError(
1055
- "Endpoint returned 402 but no sign-in-with-x extension found",
1056
- {
1057
- statusCode: 402,
1058
- x402Version: paymentRequired.x402Version,
1059
- extensions: Object.keys(paymentRequired.extensions ?? {}),
1060
- hint: "This endpoint may require payment instead of authentication. Use execute_call for paid requests."
1061
- }
1062
- );
870
+ async (input) => {
871
+ const httpClient = new x402HTTPClient(new x402Client());
872
+ const firstResult = await safeFetch(toolName2, buildRequest(input));
873
+ if (firstResult.isErr()) {
874
+ return mcpError(firstResult);
875
+ }
876
+ const firstResponse = firstResult.value;
877
+ if (firstResponse.status !== 402) {
878
+ if (!firstResponse.ok) {
879
+ return mcpErrorFetch(toolName2, firstResponse);
1063
880
  }
1064
- const serverInfo = siwxExtension.info;
1065
- const requiredFields = [
1066
- "domain",
1067
- "uri",
1068
- "version",
1069
- "chainId",
1070
- "nonce",
1071
- "issuedAt"
1072
- ];
1073
- const missingFields = requiredFields.filter(
1074
- (f) => !serverInfo[f]
881
+ const parseResponseResult2 = await safeParseResponse(
882
+ toolName2,
883
+ firstResponse
1075
884
  );
1076
- if (missingFields.length > 0) {
1077
- return mcpError(
1078
- "Invalid sign-in-with-x extension: missing required fields",
1079
- {
1080
- missingFields,
1081
- receivedInfo: serverInfo
1082
- }
1083
- );
885
+ if (parseResponseResult2.isErr()) {
886
+ return mcpError(parseResponseResult2);
1084
887
  }
1085
- if (serverInfo.chainId.startsWith("solana:")) {
1086
- return mcpError("Solana authentication not supported", {
1087
- chainId: serverInfo.chainId,
1088
- hint: "This endpoint requires a Solana wallet. The MCP server currently only supports EVM wallets."
1089
- });
1090
- }
1091
- const payload = await createSIWxPayload(serverInfo, account);
1092
- const siwxHeader = encodeSIWxHeader(payload);
1093
- const authedResponse = await fetch(url, {
1094
- method,
1095
- headers: {
1096
- "Content-Type": "application/json",
1097
- "SIGN-IN-WITH-X": siwxHeader,
1098
- ...headers
1099
- },
1100
- body: body ? JSON.stringify(body) : void 0
888
+ return mcpSuccessResponse(parseResponseResult2.value);
889
+ }
890
+ const getPaymentRequiredResult = await safeGetPaymentRequired(
891
+ toolName2,
892
+ httpClient,
893
+ firstResponse
894
+ );
895
+ if (getPaymentRequiredResult.isErr()) {
896
+ return mcpError(getPaymentRequiredResult);
897
+ }
898
+ const paymentRequired = getPaymentRequiredResult.value;
899
+ const siwxExtension = getSiwxExtension(paymentRequired.extensions);
900
+ if (!siwxExtension) {
901
+ return mcpErrorJson({
902
+ message: "Endpoint returned 402 but no sign-in-with-x extension found",
903
+ statusCode: 402,
904
+ extensions: Object.keys(paymentRequired.extensions ?? {}),
905
+ hint: "This endpoint may require payment instead of authentication. Use execute_call for paid requests."
1101
906
  });
1102
- const responseHeaders = Object.fromEntries(
1103
- authedResponse.headers.entries()
1104
- );
1105
- if (!authedResponse.ok) {
1106
- let errorBody;
1107
- try {
1108
- errorBody = await authedResponse.json();
1109
- } catch {
1110
- errorBody = await authedResponse.text();
1111
- }
1112
- return mcpError(
1113
- `HTTP ${authedResponse.status} after authentication`,
1114
- {
1115
- statusCode: authedResponse.status,
1116
- headers: responseHeaders,
1117
- body: errorBody,
1118
- authAddress: account.address
1119
- }
1120
- );
1121
- }
1122
- let data;
1123
- const contentType = authedResponse.headers.get("content-type");
1124
- if (contentType?.includes("application/json")) {
1125
- data = await authedResponse.json();
1126
- } else {
1127
- data = await authedResponse.text();
1128
- }
1129
- return mcpSuccess({
1130
- statusCode: authedResponse.status,
1131
- headers: responseHeaders,
1132
- data,
1133
- authentication: {
1134
- address: account.address,
1135
- domain: serverInfo.domain,
1136
- chainId: serverInfo.chainId
1137
- }
907
+ }
908
+ const serverInfo = siwxExtension;
909
+ const requiredFields = [
910
+ "domain",
911
+ "uri",
912
+ "version",
913
+ "chainId",
914
+ "nonce",
915
+ "issuedAt"
916
+ ];
917
+ const missingFields = requiredFields.filter(
918
+ (f) => !serverInfo[f]
919
+ );
920
+ if (missingFields.length > 0) {
921
+ return mcpErrorJson({
922
+ message: "Invalid sign-in-with-x extension: missing required fields",
923
+ missingFields,
924
+ receivedInfo: { ...serverInfo }
925
+ });
926
+ }
927
+ if (serverInfo.chainId.startsWith("solana:")) {
928
+ return mcpErrorJson({
929
+ message: "Solana authentication not supported",
930
+ chainId: serverInfo.chainId,
931
+ hint: "This endpoint requires a Solana wallet. The MCP server currently only supports EVM wallets."
1138
932
  });
1139
- } catch (err2) {
1140
- return mcpError(err2, { tool: "authed_call", url });
1141
933
  }
934
+ const payloadResult = await safeCreateSIWxPayload(
935
+ toolName2,
936
+ serverInfo,
937
+ account
938
+ );
939
+ if (payloadResult.isErr()) {
940
+ return mcpError(payloadResult);
941
+ }
942
+ const siwxHeader = encodeSIWxHeader(payloadResult.value);
943
+ const authedRequest = buildRequest(input);
944
+ authedRequest.headers.set("SIGN-IN-WITH-X", siwxHeader);
945
+ const authedResult = await safeFetch(toolName2, authedRequest);
946
+ if (authedResult.isErr()) {
947
+ return mcpError(authedResult);
948
+ }
949
+ const authedResponse = authedResult.value;
950
+ if (!authedResponse.ok) {
951
+ return mcpErrorFetch(toolName2, authedResponse);
952
+ }
953
+ const parseResponseResult = await safeParseResponse(
954
+ toolName2,
955
+ authedResponse
956
+ );
957
+ if (parseResponseResult.isErr()) {
958
+ return mcpError(parseResponseResult);
959
+ }
960
+ return mcpSuccessResponse(parseResponseResult.value, {
961
+ authentication: {
962
+ address: account.address,
963
+ domain: serverInfo.domain,
964
+ chainId: serverInfo.chainId
965
+ }
966
+ });
1142
967
  }
1143
968
  );
1144
969
  };
@@ -1146,138 +971,195 @@ var init_auth = __esm({
1146
971
  });
1147
972
 
1148
973
  // src/server/tools/wallet.ts
1149
- var registerWalletTools;
974
+ var toolName3, registerWalletTools;
1150
975
  var init_wallet = __esm({
1151
976
  "src/server/tools/wallet.ts"() {
1152
- init_response();
1153
977
  init_balance();
1154
978
  init_networks();
1155
- init_deposit();
979
+ init_utils();
980
+ init_response();
981
+ toolName3 = "getWalletInfo";
1156
982
  registerWalletTools = ({
1157
983
  server,
1158
984
  account: { address },
1159
985
  flags
1160
986
  }) => {
1161
987
  server.registerTool(
1162
- "check_balance",
988
+ toolName3,
1163
989
  {
1164
990
  description: "Check wallet address and USDC balance. Creates wallet if needed."
1165
991
  },
1166
992
  async () => {
1167
- const { balanceFormatted } = await getUSDCBalance(address, flags);
1168
- return mcpSuccess({
993
+ const balanceResult = await getBalance({
994
+ address,
995
+ flags,
996
+ surface: toolName3
997
+ });
998
+ if (balanceResult.isErr()) {
999
+ return mcpError(balanceResult);
1000
+ }
1001
+ const { balance } = balanceResult.value;
1002
+ return mcpSuccessJson({
1169
1003
  address,
1170
1004
  network: DEFAULT_NETWORK,
1171
1005
  networkName: getChainName(DEFAULT_NETWORK),
1172
- usdcBalance: balanceFormatted,
1173
- balanceFormatted: balanceFormatted.toString(),
1174
- isNewWallet: balanceFormatted === 0,
1175
- depositLink: getDepositLink(address, flags)
1006
+ usdcBalance: balance,
1007
+ isNewWallet: balance === 0,
1008
+ depositLink: getDepositLink(address, flags),
1009
+ ...balance < 2.5 ? {
1010
+ message: `Your balance is low. Consider topping it up`
1011
+ } : {}
1176
1012
  });
1177
1013
  }
1178
1014
  );
1015
+ };
1016
+ }
1017
+ });
1018
+ var toolName4, registerCheckX402EndpointTool;
1019
+ var init_check_endpoint = __esm({
1020
+ "src/server/tools/check-endpoint.ts"() {
1021
+ init_fetch();
1022
+ init_response();
1023
+ init_log();
1024
+ init_token();
1025
+ init_x402();
1026
+ init_x402_extensions();
1027
+ init_request();
1028
+ toolName4 = "checkEndpointSchema";
1029
+ registerCheckX402EndpointTool = ({ server }) => {
1179
1030
  server.registerTool(
1180
- "get_wallet_address",
1031
+ toolName4,
1181
1032
  {
1182
- description: "Get the wallet address."
1033
+ description: "Check if an endpoint is x402-protected and get pricing options, schema, and auth requirements (if applicable).",
1034
+ inputSchema: requestSchema
1183
1035
  },
1184
- () => mcpSuccess({ address })
1036
+ async (input) => {
1037
+ log.info("Querying endpoint", input);
1038
+ const responseResult = await safeFetch(toolName4, buildRequest(input));
1039
+ if (responseResult.isErr()) {
1040
+ return mcpError(responseResult);
1041
+ }
1042
+ const response = responseResult.value;
1043
+ if (response.status !== 402) {
1044
+ if (!response.ok) {
1045
+ return mcpErrorFetch(toolName4, response);
1046
+ }
1047
+ const parseResponseResult = await safeParseResponse(toolName4, response);
1048
+ if (parseResponseResult.isErr()) {
1049
+ return mcpError(parseResponseResult);
1050
+ }
1051
+ return mcpSuccessResponse(parseResponseResult.value, {
1052
+ requiresPayment: false
1053
+ });
1054
+ }
1055
+ const client = new x402HTTPClient(new x402Client());
1056
+ const paymentRequiredResult = await safeGetPaymentRequired(
1057
+ toolName4,
1058
+ client,
1059
+ response
1060
+ );
1061
+ if (paymentRequiredResult.isErr()) {
1062
+ return mcpError(paymentRequiredResult);
1063
+ }
1064
+ const { resource, extensions, accepts } = paymentRequiredResult.value;
1065
+ return mcpSuccessJson({
1066
+ requiresPayment: true,
1067
+ statusCode: response.status,
1068
+ routeDetails: {
1069
+ ...resource,
1070
+ schema: getInputSchema(extensions),
1071
+ paymentMethods: accepts.map((accept) => ({
1072
+ price: tokenStringToNumber(accept.amount),
1073
+ network: accept.network,
1074
+ asset: accept.asset
1075
+ }))
1076
+ }
1077
+ });
1078
+ }
1185
1079
  );
1186
1080
  };
1187
1081
  }
1188
1082
  });
1189
-
1190
- // src/server/lib/x402/get-route-details.ts
1191
- var getRouteDetails, getSchema;
1192
- var init_get_route_details = __esm({
1193
- "src/server/lib/x402/get-route-details.ts"() {
1194
- init_token();
1195
- getRouteDetails = (paymentRequired) => {
1196
- const { accepts, extensions, resource } = paymentRequired;
1197
- return {
1198
- ...resource,
1199
- schema: getSchema(extensions),
1200
- paymentMethods: accepts.map((accept) => ({
1201
- price: tokenStringToNumber(accept.amount),
1202
- network: accept.network,
1203
- asset: accept.asset
1204
- }))
1205
- };
1083
+ var STATE_FILE, stateSchema, getState, setState;
1084
+ var init_state = __esm({
1085
+ "src/shared/state.ts"() {
1086
+ init_fs2();
1087
+ init_log();
1088
+ STATE_FILE = configFile("state.json", "{}");
1089
+ stateSchema = z$1.looseObject({
1090
+ redeemedCodes: z$1.array(z$1.string())
1091
+ }).partial();
1092
+ getState = () => {
1093
+ const result = stateSchema.safeParse(
1094
+ JSON.parse(fs2__default.readFileSync(STATE_FILE, "utf-8"))
1095
+ );
1096
+ if (!result.success) {
1097
+ log.error("Failed to parse state", { error: result.error });
1098
+ return {};
1099
+ }
1100
+ return result.data;
1206
1101
  };
1207
- getSchema = (extensions) => {
1208
- const { bazaar } = extensions ?? {};
1209
- if (!bazaar) {
1210
- return void 0;
1102
+ setState = (state) => {
1103
+ const existing = getState();
1104
+ const newState = stateSchema.parse({ ...existing, ...state });
1105
+ fs2__default.writeFileSync(STATE_FILE, JSON.stringify(newState, null, 2));
1106
+ };
1107
+ }
1108
+ });
1109
+ var redeemInviteCode;
1110
+ var init_redeem_invite = __esm({
1111
+ "src/shared/redeem-invite.ts"() {
1112
+ init_fetch();
1113
+ init_utils();
1114
+ init_state();
1115
+ redeemInviteCode = async ({
1116
+ code,
1117
+ dev,
1118
+ address,
1119
+ surface: surface3
1120
+ }) => {
1121
+ const state = getState();
1122
+ if (state.redeemedCodes?.includes(code)) {
1123
+ return err("user", surface3, {
1124
+ cause: "conflict",
1125
+ message: "This invite code has already been redeemed"
1126
+ });
1211
1127
  }
1212
- const { schema } = bazaar;
1213
- return schema.properties.input;
1214
- };
1215
- }
1216
- });
1217
- var registerCheckX402EndpointTool;
1218
- var init_check_endpoint_schema = __esm({
1219
- "src/server/tools/check-endpoint-schema.ts"() {
1220
- init_response();
1221
- init_schemas();
1222
- init_log();
1223
- init_get_route_details();
1224
- registerCheckX402EndpointTool = ({ server }) => {
1225
- server.registerTool(
1226
- "check_x402_endpoint",
1227
- {
1228
- description: "Check if an endpoint is x402-protected and get pricing options, schema, and auth requirements (if applicable).",
1229
- inputSchema: requestSchema
1230
- },
1231
- async ({ url, method, body }) => {
1232
- try {
1233
- log.info("Querying endpoint", { url, method, body });
1234
- const response = await fetch(url, {
1235
- method,
1236
- body: body ? typeof body === "string" ? body : JSON.stringify(body) : void 0,
1237
- headers: {
1238
- "Content-Type": "application/json"
1239
- }
1240
- });
1241
- const bodyText = await response.text().catch(() => void 0);
1242
- if (response.status !== 402) {
1243
- return mcpSuccess({
1244
- data: bodyText,
1245
- statusCode: response.status,
1246
- requiresPayment: false
1247
- });
1248
- }
1249
- const paymentRequired = new x402HTTPClient(
1250
- new x402Client()
1251
- ).getPaymentRequiredResponse(
1252
- (name) => response.headers.get(name),
1253
- JSON.parse(bodyText ?? "{}")
1254
- );
1255
- const routeDetails = getRouteDetails(paymentRequired);
1256
- return mcpSuccess({
1257
- requiresPayment: true,
1258
- statusCode: response.status,
1259
- routeDetails
1260
- });
1261
- } catch (err2) {
1262
- return mcpError(err2, { tool: "query_endpoint", url });
1263
- }
1264
- }
1128
+ const result = await safeFetchJson(
1129
+ surface3,
1130
+ new Request(`${getBaseUrl(dev)}/api/invite/redeem`, {
1131
+ method: "POST",
1132
+ headers: {
1133
+ "Content-Type": "application/json"
1134
+ },
1135
+ body: JSON.stringify({
1136
+ code,
1137
+ recipientAddr: address
1138
+ })
1139
+ })
1265
1140
  );
1141
+ if (result.isOk()) {
1142
+ setState({
1143
+ redeemedCodes: [...state.redeemedCodes ?? [], code]
1144
+ });
1145
+ }
1146
+ return result;
1266
1147
  };
1267
1148
  }
1268
1149
  });
1269
- var registerRedeemInviteTool;
1150
+ var toolName5, registerRedeemInviteTool;
1270
1151
  var init_redeem_invite2 = __esm({
1271
1152
  "src/server/tools/redeem-invite.ts"() {
1272
1153
  init_response();
1154
+ init_redeem_invite();
1155
+ toolName5 = "redeemInvite";
1273
1156
  registerRedeemInviteTool = ({
1274
1157
  server,
1275
1158
  account: { address },
1276
1159
  flags
1277
1160
  }) => {
1278
- const baseUrl = flags.dev ? "http://localhost:3000" : "https://x402scan.com";
1279
1161
  server.registerTool(
1280
- "redeem_invite",
1162
+ toolName5,
1281
1163
  {
1282
1164
  description: "Redeem an invite code to receive USDC.",
1283
1165
  inputSchema: z$1.object({
@@ -1285,18 +1167,20 @@ var init_redeem_invite2 = __esm({
1285
1167
  })
1286
1168
  },
1287
1169
  async ({ code }) => {
1288
- const res = await fetch(`${baseUrl}/api/invite/redeem`, {
1289
- method: "POST",
1290
- headers: { "Content-Type": "application/json" },
1291
- body: JSON.stringify({ code, recipientAddr: address })
1170
+ const result = await redeemInviteCode({
1171
+ code,
1172
+ dev: flags.dev,
1173
+ address,
1174
+ surface: toolName5
1292
1175
  });
1293
- const data = await res.json();
1294
- if (!data.success) {
1295
- return mcpError(data.error ?? "Failed to redeem invite code");
1176
+ if (result.isErr()) {
1177
+ return mcpError(result);
1296
1178
  }
1297
- return mcpSuccess({
1298
- amount: `${data.amount} USDC`,
1299
- txHash: data.txHash
1179
+ const { amount, txHash } = result.value;
1180
+ return mcpSuccessJson({
1181
+ redeemed: true,
1182
+ amount: `${amount} USDC`,
1183
+ txHash
1300
1184
  });
1301
1185
  }
1302
1186
  );
@@ -1305,135 +1189,252 @@ var init_redeem_invite2 = __esm({
1305
1189
  });
1306
1190
  function getVersion() {
1307
1191
  {
1308
- return "0.0.7-beta.0";
1192
+ return "0.0.7-beta.1";
1309
1193
  }
1310
1194
  }
1311
- var MCP_VERSION;
1195
+ var MCP_VERSION, DIST_TAG;
1312
1196
  var init_version = __esm({
1313
1197
  "src/server/lib/version.ts"() {
1314
1198
  MCP_VERSION = getVersion();
1199
+ DIST_TAG = MCP_VERSION.includes("-beta") ? "beta" : "latest";
1315
1200
  }
1316
1201
  });
1317
- var errorReportSchema, registerTelemetryTools;
1202
+ var toolName6, registerTelemetryTools;
1318
1203
  var init_telemetry = __esm({
1319
1204
  "src/server/tools/telemetry.ts"() {
1205
+ init_fetch();
1320
1206
  init_log();
1321
- init_response();
1207
+ init_utils();
1322
1208
  init_version();
1323
- errorReportSchema = z.object({
1324
- tool: z.string().describe("MCP tool name"),
1325
- resource: z.string().optional().describe("x402 resource URL"),
1326
- summary: z.string().describe("1-2 sentence summary"),
1327
- errorMessage: z.string().describe("Error message"),
1328
- stack: z.string().optional().describe("Stack trace"),
1329
- fullReport: z.string().optional().describe("Detailed report with context, logs, repro steps")
1330
- });
1209
+ init_response();
1210
+ toolName6 = "reportError";
1331
1211
  registerTelemetryTools = ({
1332
1212
  server,
1333
1213
  account: { address },
1334
1214
  flags
1335
1215
  }) => {
1336
- const baseUrl = flags.dev ? "http://localhost:3000" : "https://x402scan.com";
1337
1216
  server.registerTool(
1338
- "report_error",
1217
+ toolName6,
1339
1218
  {
1340
1219
  description: "EMERGENCY ONLY. Report critical MCP tool bugs. Do NOT use for normal errors (balance, network, 4xx) - those are recoverable.",
1341
- inputSchema: errorReportSchema
1220
+ inputSchema: z$1.object({
1221
+ tool: z$1.string().describe("MCP tool name"),
1222
+ resource: z$1.string().optional().describe("x402 resource URL"),
1223
+ summary: z$1.string().describe("1-2 sentence summary"),
1224
+ errorMessage: z$1.string().describe("Error message"),
1225
+ stack: z$1.string().optional().describe("Stack trace"),
1226
+ fullReport: z$1.string().optional().describe("Detailed report with context, logs, repro steps")
1227
+ })
1342
1228
  },
1343
1229
  async (input) => {
1344
- try {
1345
- log.info("Submitting error report", {
1346
- tool: input.tool,
1347
- resource: input.resource,
1348
- summary: input.summary
1349
- });
1350
- const report = {
1351
- ...input,
1352
- walletAddress: address,
1353
- mcpVersion: MCP_VERSION,
1354
- reportedAt: (/* @__PURE__ */ new Date()).toISOString()
1355
- };
1356
- const response = await fetch(`${baseUrl}/api/telemetry`, {
1230
+ log.info("Submitting error report", {
1231
+ tool: input.tool,
1232
+ resource: input.resource,
1233
+ summary: input.summary
1234
+ });
1235
+ const telemetryResult = await safeFetchJson(
1236
+ toolName6,
1237
+ new Request(`${getBaseUrl(flags.dev)}/api/telemetry`, {
1357
1238
  method: "POST",
1358
1239
  headers: {
1359
1240
  "Content-Type": "application/json"
1360
1241
  },
1361
- body: JSON.stringify(report)
1362
- });
1363
- if (!response.ok) {
1364
- const errorText = await response.text().catch(() => "Unknown error");
1365
- log.error("Failed to submit error report", {
1366
- status: response.status,
1367
- error: errorText
1368
- });
1369
- return mcpError(
1370
- `Failed to submit error report: ${response.status} ${errorText}`,
1371
- { tool: "report_error" }
1372
- );
1373
- }
1374
- const result = await response.json();
1375
- log.info("Error report submitted successfully", {
1376
- reportId: result.reportId
1377
- });
1378
- return mcpSuccess({
1379
- submitted: true,
1380
- reportId: result.reportId,
1381
- message: "Error report submitted successfully. The x402scan team will investigate."
1382
- });
1383
- } catch (err2) {
1384
- log.error("Failed to submit error report", { error: err2 });
1385
- return mcpError(err2, { tool: "report_error" });
1242
+ body: JSON.stringify({
1243
+ ...input,
1244
+ walletAddress: address,
1245
+ mcpVersion: MCP_VERSION,
1246
+ reportedAt: (/* @__PURE__ */ new Date()).toISOString()
1247
+ })
1248
+ })
1249
+ );
1250
+ if (telemetryResult.isErr()) {
1251
+ log.error("Failed to submit error report", telemetryResult.error);
1252
+ return mcpError(telemetryResult);
1386
1253
  }
1254
+ const { reportId } = telemetryResult.value;
1255
+ log.info("Error report submitted successfully", {
1256
+ reportId
1257
+ });
1258
+ return mcpSuccessJson({
1259
+ submitted: true,
1260
+ reportId,
1261
+ message: "Error report submitted successfully. The x402scan team will investigate."
1262
+ });
1387
1263
  }
1388
1264
  );
1389
1265
  };
1390
1266
  }
1391
1267
  });
1268
+ function registerDiscoveryTools(server) {
1269
+ server.registerTool(
1270
+ "discover_resources",
1271
+ {
1272
+ description: `Discover x402-protected resources on an origin. Returns a list of resource URLs.
1273
+ Use check_x402_endpoint separately to get detailed pricing/schema info for specific resources.
1392
1274
 
1393
- // src/server/resources/_lib.ts
1394
- var getWebPageMetadata;
1395
- var init_lib = __esm({
1396
- "src/server/resources/_lib.ts"() {
1397
- getWebPageMetadata = async (url) => {
1398
- try {
1399
- const response = await fetch(url);
1400
- if (!response.ok) {
1401
- return null;
1275
+ Known default origins with resource packs. Discover if more needed:
1276
+ - https://enrichx402.com ->
1277
+ People + Org search
1278
+ Google Maps (places + locations)
1279
+ Grok twitter search
1280
+ Exa web search
1281
+ Clado linkedin data
1282
+ Firecrawl web scrape
1283
+ - https://stablestudio.io -> generate images / videos
1284
+ `,
1285
+ inputSchema: {
1286
+ url: z.url().describe(
1287
+ "The origin URL or any URL on the origin to discover resources from"
1288
+ )
1289
+ }
1290
+ },
1291
+ async ({ url }) => {
1292
+ const origin = URL.canParse(url) ? new URL(url).origin : url;
1293
+ const hostname = URL.canParse(origin) ? new URL(origin).hostname : origin;
1294
+ log.info(`Discovering resources for origin: ${origin}`);
1295
+ const wellKnownUrl = `${origin}/.well-known/x402`;
1296
+ log.debug(`Fetching discovery document from: ${wellKnownUrl}`);
1297
+ const wellKnownResult = await safeFetchJson(
1298
+ toolName7,
1299
+ new Request(wellKnownUrl, { headers: { Accept: "application/json" } })
1300
+ );
1301
+ if (wellKnownResult.isOk()) {
1302
+ const parsed = discoveryDocumentSchema.safeParse(wellKnownResult.value);
1303
+ if (parsed.success) {
1304
+ return mcpSuccessJson({
1305
+ found: true,
1306
+ origin,
1307
+ source: "well-known",
1308
+ data: parsed.data
1309
+ });
1402
1310
  }
1403
- const html = await response.text();
1404
- const titleMatch = /<title[^>]*>([\s\S]*?)<\/title>/i.exec(html);
1405
- const title = titleMatch ? titleMatch[1].trim().replace(/\s+/g, " ") : null;
1406
- let descriptionMatch = /<meta\s+name=["']description["']\s+content=["']([^"']*)["']/i.exec(html);
1407
- descriptionMatch ??= /<meta\s+property=["']og:description["']\s+content=["']([^"']*)["']/i.exec(
1408
- html
1409
- );
1410
- descriptionMatch ??= /<meta\s+content=["']([^"']*)["']\s+name=["']description["']/i.exec(html);
1411
- descriptionMatch ??= /<meta\s+content=["']([^"']*)["']\s+property=["']og:description["']/i.exec(
1412
- html
1413
- );
1414
- const description = descriptionMatch ? descriptionMatch[1].trim().replace(/\s+/g, " ") : null;
1415
- return {
1416
- title,
1417
- description
1418
- };
1419
- } catch (error) {
1420
- throw new Error(
1421
- `Failed to fetch web page metadata: ${error instanceof Error ? error.message : String(error)}`
1311
+ } else {
1312
+ log.info(
1313
+ `No well-known x402 discovery document found at ${wellKnownUrl}`
1422
1314
  );
1423
1315
  }
1316
+ const dnsQuery = `_x402.${hostname}`;
1317
+ log.debug(`Looking up DNS TXT record: ${dnsQuery}`);
1318
+ const dnsResult = await safeFetchJson(
1319
+ toolName7,
1320
+ new Request(
1321
+ `https://cloudflare-dns.com/dns-query?name=${encodeURIComponent(dnsQuery)}&type=TXT`,
1322
+ { headers: { Accept: "application/dns-json" } }
1323
+ )
1324
+ );
1325
+ if (dnsResult.isOk() && dnsResult.value.Answer && dnsResult.value.Answer.length > 0) {
1326
+ const dnsUrl = dnsResult.value.Answer[0].data.replace(/^"|"$/g, "");
1327
+ if (URL.canParse(dnsUrl)) {
1328
+ const dnsDocResult = await safeFetchJson(
1329
+ toolName7,
1330
+ new Request(dnsUrl, { headers: { Accept: "application/json" } })
1331
+ );
1332
+ if (dnsDocResult.isOk()) {
1333
+ const parsed = discoveryDocumentSchema.safeParse(
1334
+ dnsDocResult.value
1335
+ );
1336
+ if (parsed.success) {
1337
+ return mcpSuccessJson({
1338
+ found: true,
1339
+ origin,
1340
+ source: "dns-txt",
1341
+ data: parsed.data
1342
+ });
1343
+ }
1344
+ }
1345
+ } else {
1346
+ log.debug(`DNS TXT value is not a valid URL: ${dnsUrl}`);
1347
+ }
1348
+ } else {
1349
+ log.info(`No DNS TXT record found for ${dnsQuery}`);
1350
+ }
1351
+ const llmsTxtUrl = `${origin}/llms.txt`;
1352
+ log.debug(`Fetching llms.txt from: ${llmsTxtUrl}`);
1353
+ const llmsResult = await safeFetch(
1354
+ toolName7,
1355
+ new Request(llmsTxtUrl, { headers: { Accept: "text/plain" } })
1356
+ );
1357
+ if (llmsResult.isOk()) {
1358
+ const parseResult = await safeParseResponse(toolName7, llmsResult.value);
1359
+ if (parseResult.isOk() && parseResult.value.type === "text") {
1360
+ return mcpSuccessJson({
1361
+ found: true,
1362
+ origin,
1363
+ source: "llms-txt",
1364
+ usage: "Found llms.txt but no structured x402 discovery document. The content below may contain information about x402 resources. Parse it to find relevant endpoints.",
1365
+ data: parseResult.value
1366
+ });
1367
+ }
1368
+ }
1369
+ return mcpErrorJson({
1370
+ found: false,
1371
+ origin,
1372
+ error: "No discovery document found. Tried: .well-known/x402, DNS TXT record, llms.txt"
1373
+ });
1374
+ }
1375
+ );
1376
+ }
1377
+ var toolName7, discoveryDocumentSchema;
1378
+ var init_discover_resources = __esm({
1379
+ "src/server/tools/discover-resources.ts"() {
1380
+ init_log();
1381
+ init_fetch();
1382
+ init_response();
1383
+ toolName7 = "discoverResources";
1384
+ discoveryDocumentSchema = z.object({
1385
+ version: z.number().refine((v) => v === 1, { message: "version must be 1" }),
1386
+ resources: z.array(z.url()),
1387
+ ownershipProofs: z.array(z.string()).optional(),
1388
+ instructions: z.string().optional()
1389
+ });
1390
+ }
1391
+ });
1392
+ var surface, getWebPageMetadata, parseMetadataFromResponse;
1393
+ var init_lib2 = __esm({
1394
+ "src/server/resources/_lib.ts"() {
1395
+ init_fetch();
1396
+ surface = "getWebPageMetadata";
1397
+ getWebPageMetadata = (url) => {
1398
+ return safeFetch(surface, new Request(url)).andThen((response) => safeParseResponse(surface, response)).andThen((parsedResponse) => {
1399
+ if (parsedResponse.type === "text") {
1400
+ return ok(parseMetadataFromResponse(parsedResponse.data));
1401
+ }
1402
+ return err("user", surface, {
1403
+ cause: "invalid_response_type",
1404
+ message: "Invalid response type"
1405
+ });
1406
+ });
1407
+ };
1408
+ parseMetadataFromResponse = (html) => {
1409
+ const titleMatch = /<title[^>]*>([\s\S]*?)<\/title>/i.exec(html);
1410
+ const title = titleMatch ? titleMatch[1].trim().replace(/\s+/g, " ") : null;
1411
+ let descriptionMatch = /<meta\s+name=["']description["']\s+content=["']([^"']*)["']/i.exec(html);
1412
+ descriptionMatch ??= /<meta\s+property=["']og:description["']\s+content=["']([^"']*)["']/i.exec(
1413
+ html
1414
+ );
1415
+ descriptionMatch ??= /<meta\s+content=["']([^"']*)["']\s+name=["']description["']/i.exec(html);
1416
+ descriptionMatch ??= /<meta\s+content=["']([^"']*)["']\s+property=["']og:description["']/i.exec(
1417
+ html
1418
+ );
1419
+ const description = descriptionMatch ? descriptionMatch[1].trim().replace(/\s+/g, " ") : null;
1420
+ return {
1421
+ title,
1422
+ description
1423
+ };
1424
1424
  };
1425
1425
  }
1426
1426
  });
1427
1427
  var origins, registerOrigins, getResourceResponse;
1428
1428
  var init_origins = __esm({
1429
1429
  "src/server/resources/origins.ts"() {
1430
- init_lib();
1431
- init_get_route_details();
1430
+ init_lib2();
1431
+ init_x402_extensions();
1432
1432
  origins = ["enrichx402.com"];
1433
1433
  registerOrigins = async ({ server }) => {
1434
1434
  await Promise.all(
1435
1435
  origins.map(async (origin) => {
1436
- const metadata = await getWebPageMetadata(`https://${origin}`);
1436
+ const metadataResult = await getWebPageMetadata(`https://${origin}`);
1437
+ const metadata = metadataResult.isOk() ? metadataResult.value : null;
1437
1438
  server.registerResource(
1438
1439
  origin,
1439
1440
  `api://${origin}`,
@@ -1483,7 +1484,7 @@ var init_origins = __esm({
1483
1484
  description: metadata?.description,
1484
1485
  resources: resources.filter(Boolean).map((resource) => {
1485
1486
  if (!resource) return null;
1486
- const schema = getSchema(
1487
+ const schema = getInputSchema(
1487
1488
  resource.paymentRequired?.extensions
1488
1489
  );
1489
1490
  return {
@@ -1518,362 +1519,95 @@ var init_origins = __esm({
1518
1519
  };
1519
1520
  }
1520
1521
  });
1522
+ var type2, parseErr, safeParse;
1523
+ var init_parse = __esm({
1524
+ "src/shared/neverthrow/parse/index.ts"() {
1525
+ type2 = "json";
1526
+ parseErr = (surface3, error) => err(type2, surface3, error);
1527
+ safeParse = (surface3, schema, value) => {
1528
+ const parseResult = schema.safeParse(value);
1529
+ if (!parseResult.success) {
1530
+ return parseErr(surface3, {
1531
+ cause: "invalid_data",
1532
+ message: JSON.stringify(z$1.treeifyError(parseResult.error), null, 2),
1533
+ error: parseResult.error
1534
+ });
1535
+ }
1536
+ return ok(parseResult.data);
1537
+ };
1538
+ }
1539
+ });
1521
1540
  async function getWallet() {
1522
1541
  if (process.env.X402_PRIVATE_KEY) {
1523
1542
  const account2 = privateKeyToAccount(process.env.X402_PRIVATE_KEY);
1524
1543
  log.info(`Using wallet from env: ${account2.address}`);
1525
- return { account: account2, isNew: false };
1526
- }
1527
- try {
1528
- const data = await fs3.readFile(WALLET_FILE, "utf-8");
1529
- const stored2 = storedWalletSchema.parse(JSON.parse(data));
1530
- const account2 = privateKeyToAccount(stored2.privateKey);
1531
- log.info(`Loaded wallet: ${account2.address}`);
1532
- return { account: account2, isNew: false };
1533
- } catch {
1534
- }
1535
- const privateKey = `0x${randomBytes(32).toString("hex")}`;
1536
- const account = privateKeyToAccount(privateKey);
1537
- const stored = {
1538
- privateKey,
1539
- address: account.address,
1540
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
1541
- };
1542
- await fs3.writeFile(WALLET_FILE, JSON.stringify(stored, null, 2));
1543
- try {
1544
- await fs3.chmod(WALLET_FILE, 384);
1545
- } catch {
1546
- }
1547
- log.info(`Created wallet: ${account.address}`);
1548
- log.info(`Saved to: ${WALLET_FILE}`);
1549
- return { account, isNew: true };
1550
- }
1551
- var WALLET_FILE, storedWalletSchema;
1552
- var init_wallet2 = __esm({
1553
- "src/lib/wallet.ts"() {
1554
- init_log();
1555
- init_schemas();
1556
- init_fs();
1557
- WALLET_FILE = configFile("wallet.json");
1558
- storedWalletSchema = z$1.object({
1559
- privateKey: ethereumPrivateKeySchema,
1560
- address: ethereumAddressSchema,
1561
- createdAt: z$1.string()
1562
- });
1563
- }
1564
- });
1565
- async function lookupDnsTxtRecord(hostname) {
1566
- const dnsQuery = `_x402.${hostname}`;
1567
- log.debug(`Looking up DNS TXT record: ${dnsQuery}`);
1568
- try {
1569
- const response = await fetch(
1570
- `https://cloudflare-dns.com/dns-query?name=${encodeURIComponent(
1571
- dnsQuery
1572
- )}&type=TXT`,
1573
- {
1574
- headers: { Accept: "application/dns-json" }
1575
- }
1576
- );
1577
- if (!response.ok) {
1578
- log.debug(`DNS lookup failed: HTTP ${response.status}`);
1579
- return null;
1580
- }
1581
- const data = await response.json();
1582
- if (!data.Answer || data.Answer.length === 0) {
1583
- log.debug("No DNS TXT record found");
1584
- return null;
1585
- }
1586
- const txtValue = data.Answer[0].data.replace(/^"|"$/g, "");
1587
- log.debug(`Found DNS TXT record: ${txtValue}`);
1588
- try {
1589
- new URL(txtValue);
1590
- return txtValue;
1591
- } catch {
1592
- log.debug(`DNS TXT value is not a valid URL: ${txtValue}`);
1593
- return null;
1594
- }
1595
- } catch (err2) {
1596
- log.debug(
1597
- `DNS lookup error: ${err2 instanceof Error ? err2.message : String(err2)}`
1598
- );
1599
- return null;
1600
- }
1601
- }
1602
- async function fetchLlmsTxt(origin) {
1603
- const llmsTxtUrl = `${origin}/llms.txt`;
1604
- log.debug(`Fetching llms.txt from: ${llmsTxtUrl}`);
1605
- try {
1606
- const response = await fetch(llmsTxtUrl, {
1607
- headers: { Accept: "text/plain" }
1608
- });
1609
- if (!response.ok) {
1610
- if (response.status === 404) {
1611
- return { found: false, error: "No llms.txt found" };
1612
- }
1613
- return { found: false, error: `HTTP ${response.status}` };
1614
- }
1615
- const content = await response.text();
1616
- if (!content || content.trim().length === 0) {
1617
- return { found: false, error: "llms.txt is empty" };
1618
- }
1619
- return { found: true, content };
1620
- } catch (err2) {
1621
- return {
1622
- found: false,
1623
- error: `Network error: ${err2 instanceof Error ? err2.message : String(err2)}`
1624
- };
1625
- }
1626
- }
1627
- async function fetchDiscoveryFromUrl(url) {
1628
- log.debug(`Fetching discovery document from: ${url}`);
1629
- try {
1630
- const response = await fetch(url, {
1631
- headers: { Accept: "application/json" }
1632
- });
1633
- if (!response.ok) {
1634
- if (response.status === 404) {
1635
- return { found: false, error: `Not found at ${url}` };
1636
- }
1637
- return {
1638
- found: false,
1639
- error: `HTTP ${response.status}: ${await response.text()}`
1640
- };
1641
- }
1642
- let rawData;
1643
- try {
1644
- rawData = await response.json();
1645
- } catch {
1646
- return {
1647
- found: false,
1648
- error: "Failed to parse discovery document as JSON"
1649
- };
1650
- }
1651
- const parsed = DiscoveryDocumentSchema.safeParse(rawData);
1652
- if (!parsed.success) {
1653
- return {
1654
- found: false,
1655
- error: `Invalid discovery document: ${parsed.error.issues.map((e) => e.message).join(", ")}`,
1656
- rawResponse: rawData
1657
- };
1658
- }
1659
- return { found: true, document: parsed.data };
1660
- } catch (err2) {
1661
- return {
1662
- found: false,
1663
- error: `Network error: ${err2 instanceof Error ? err2.message : String(err2)}`
1664
- };
1665
- }
1666
- }
1667
- async function fetchDiscoveryDocument(origin) {
1668
- const attemptedSources = [];
1669
- const hostname = getHostname(origin);
1670
- const wellKnownUrl = `${origin}/.well-known/x402`;
1671
- attemptedSources.push(wellKnownUrl);
1672
- const wellKnownResult = await fetchDiscoveryFromUrl(wellKnownUrl);
1673
- if (wellKnownResult.found && wellKnownResult.document) {
1674
- return {
1675
- found: true,
1676
- source: "well-known",
1677
- document: wellKnownResult.document,
1678
- attemptedSources
1679
- };
1680
- }
1681
- attemptedSources.push(`DNS TXT _x402.${hostname}`);
1682
- const dnsUrl = await lookupDnsTxtRecord(hostname);
1683
- if (dnsUrl) {
1684
- attemptedSources.push(dnsUrl);
1685
- const dnsResult = await fetchDiscoveryFromUrl(dnsUrl);
1686
- if (dnsResult.found && dnsResult.document) {
1687
- return {
1688
- found: true,
1689
- source: "dns-txt",
1690
- document: dnsResult.document,
1691
- attemptedSources
1692
- };
1693
- }
1694
- }
1695
- attemptedSources.push(`${origin}/llms.txt`);
1696
- const llmsResult = await fetchLlmsTxt(origin);
1697
- if (llmsResult.found && llmsResult.content) {
1698
- return {
1699
- found: true,
1700
- source: "llms-txt",
1701
- llmsTxtContent: llmsResult.content,
1702
- attemptedSources
1703
- };
1704
- }
1705
- return {
1706
- found: false,
1707
- error: "No discovery document found. Tried: .well-known/x402, DNS TXT record, llms.txt",
1708
- attemptedSources
1709
- };
1710
- }
1711
- async function queryResource(url) {
1712
- log.debug(`Querying resource: ${url}`);
1713
- try {
1714
- const result = await fetch(url, { method: "GET" });
1715
- if (!result.ok) {
1716
- return {
1717
- url,
1718
- isX402Endpoint: false,
1719
- error: result.statusText ?? "Failed to query endpoint"
1720
- };
1721
- }
1722
- if (result.status !== 402) {
1723
- return {
1724
- url,
1725
- isX402Endpoint: false
1726
- };
1727
- }
1728
- const pr = new x402HTTPClient(new x402Client()).getPaymentRequiredResponse(
1729
- (name) => result.headers.get(name),
1730
- JSON.parse(await result.text())
1731
- );
1732
- const firstReq = pr.accepts[0];
1733
- const resource = {
1734
- url,
1735
- isX402Endpoint: true,
1736
- x402Version: pr.x402Version,
1737
- price: tokenStringToNumber(firstReq.amount),
1738
- priceRaw: firstReq.amount,
1739
- network: firstReq.network,
1740
- networkName: getChainName(firstReq.network)
1741
- };
1742
- if (pr.extensions?.bazaar) {
1743
- const bazaar = pr.extensions.bazaar;
1744
- resource.bazaar = { info: bazaar.info, schema: bazaar.schema };
1745
- const info = bazaar.info;
1746
- if (info?.description) {
1747
- resource.description = info.description;
1748
- }
1749
- }
1750
- if (pr.extensions?.["sign-in-with-x"]) {
1751
- const siwx = pr.extensions["sign-in-with-x"];
1752
- resource.signInWithX = { required: true, info: siwx.info };
1544
+ return ok({ account: account2, isNew: false });
1545
+ }
1546
+ const readFileResult = await safeReadFile(walletSurface, WALLET_FILE);
1547
+ if (!readFileResult.isOk()) {
1548
+ const fileExistsResult = safeFileExists(walletSurface, WALLET_FILE);
1549
+ if (fileExistsResult.isOk()) {
1550
+ return fsErr(walletSurface, {
1551
+ cause: "file_not_readable",
1552
+ message: `The file exists but is not readable. Fix corrupted state file: ${WALLET_FILE}`
1553
+ });
1753
1554
  }
1754
- return resource;
1755
- } catch (err2) {
1756
- return {
1757
- url,
1758
- isX402Endpoint: false,
1759
- error: err2 instanceof Error ? err2.message : String(err2)
1760
- };
1761
1555
  }
1762
- }
1763
- function registerDiscoveryTools(server) {
1764
- server.registerTool(
1765
- "discover_resources",
1766
- {
1767
- description: `Discover x402-protected resources on an origin.
1768
- NEVER use 'fanOut = true' on the first try.
1769
- NEVER use 'fanOut = true' when there are more than 8 resources.
1770
-
1771
- Known default origins with resource packs. Discover if more needed:
1772
- - https://enrichx402.com ->
1773
- People + Org search
1774
- Google Maps (places + locations)
1775
- Grok twitter search
1776
- Exa web search
1777
- Clado linkedin data
1778
- Firecrawl web scrape
1779
- - https://stablestudio.io -> generate images / videos
1780
- `,
1781
- inputSchema: {
1782
- url: z.url().describe(
1783
- "The origin URL or any URL on the origin to discover resources from"
1784
- ),
1785
- fanOut: z.boolean().default(false).describe(
1786
- "Whether to query each discovered resource for full pricing/schema info. NEVER use on first try."
1787
- ),
1788
- concurrency: z.number().int().min(1).max(10).default(5).describe(
1789
- "Max concurrent requests when querying resources (default: 5)"
1790
- )
1791
- }
1792
- },
1793
- async ({ url, fanOut, concurrency }) => {
1794
- try {
1795
- const origin = getOrigin(url);
1796
- log.info(`Discovering resources for origin: ${origin}`);
1797
- const discoveryResult = await fetchDiscoveryDocument(origin);
1798
- if (discoveryResult.found && discoveryResult.source === "llms-txt") {
1799
- return mcpSuccess({
1800
- found: true,
1801
- origin,
1802
- source: "llms-txt",
1803
- usage: "Found llms.txt but no structured x402 discovery document. The content below may contain information about x402 resources. Parse it to find relevant endpoints.",
1804
- llmsTxtContent: discoveryResult.llmsTxtContent,
1805
- attemptedSources: discoveryResult.attemptedSources,
1806
- resources: []
1807
- });
1808
- }
1809
- if (!discoveryResult.found || !discoveryResult.document) {
1810
- return mcpSuccess({
1811
- found: false,
1812
- origin,
1813
- error: discoveryResult.error,
1814
- attemptedSources: discoveryResult.attemptedSources,
1815
- rawResponse: discoveryResult.rawResponse
1816
- });
1817
- }
1818
- const doc = discoveryResult.document;
1819
- const result = {
1820
- found: true,
1821
- origin,
1822
- source: discoveryResult.source,
1823
- instructions: doc.instructions,
1824
- usage: "Use query_endpoint to get full pricing/requirements for a resource. Use execute_call (for payment) or authed_call (for SIWX auth) to call it.",
1825
- resources: []
1826
- };
1827
- if (!fanOut) {
1828
- result.resources = doc.resources.map((resourceUrl) => ({
1829
- url: resourceUrl
1830
- }));
1831
- return mcpSuccess(result);
1832
- }
1833
- const resourceUrls = doc.resources;
1834
- const allResources = [];
1835
- for (let i = 0; i < resourceUrls.length; i += concurrency) {
1836
- const batch = resourceUrls.slice(i, i + concurrency);
1837
- const batchResults = await Promise.all(
1838
- batch.map((resourceUrl) => queryResource(resourceUrl))
1839
- );
1840
- allResources.push(...batchResults);
1841
- }
1842
- result.resources = allResources;
1843
- return mcpSuccess(result);
1844
- } catch (err2) {
1845
- return mcpError(err2, { tool: "discover_resources", url });
1846
- }
1556
+ if (readFileResult.isOk()) {
1557
+ const data = readFileResult.value;
1558
+ const jsonParseResult = safeParseJson(walletSurface, data);
1559
+ if (jsonParseResult.isErr()) {
1560
+ return jsonErr(walletSurface, {
1561
+ cause: "parse",
1562
+ message: `The data in ${WALLET_FILE} is not valid JSON`
1563
+ });
1847
1564
  }
1848
- );
1849
- }
1850
- function getOrigin(urlString) {
1851
- try {
1852
- return new URL(urlString).origin;
1853
- } catch {
1854
- return urlString;
1565
+ const parseResult = safeParse(
1566
+ walletSurface,
1567
+ storedWalletSchema,
1568
+ jsonParseResult.value
1569
+ );
1570
+ if (parseResult.isErr()) {
1571
+ return parseResult;
1572
+ }
1573
+ const account2 = privateKeyToAccount(parseResult.value.privateKey);
1574
+ log.info(`Loaded wallet: ${account2.address}`);
1575
+ return ok({ account: account2, isNew: false });
1855
1576
  }
1856
- }
1857
- function getHostname(origin) {
1858
- try {
1859
- return new URL(origin).hostname;
1860
- } catch {
1861
- return origin;
1577
+ const privateKey = generatePrivateKey();
1578
+ const account = privateKeyToAccount(privateKey);
1579
+ const stored = {
1580
+ privateKey,
1581
+ address: account.address,
1582
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1583
+ };
1584
+ const saveResult = await safeWriteFile(
1585
+ walletSurface,
1586
+ WALLET_FILE,
1587
+ JSON.stringify(stored, null, 2)
1588
+ ).andThen(() => safeChmod(walletSurface, WALLET_FILE, 384));
1589
+ if (saveResult.isErr()) {
1590
+ return saveResult;
1862
1591
  }
1592
+ log.info(`Created wallet: ${account.address}`);
1593
+ log.info(`Saved to: ${WALLET_FILE}`);
1594
+ return ok({ account, isNew: true });
1863
1595
  }
1864
- var DiscoveryDocumentSchema;
1865
- var init_discover_resources = __esm({
1866
- "src/server/tools/discover-resources.ts"() {
1596
+ var WALLET_FILE, storedWalletSchema, walletSurface;
1597
+ var init_wallet2 = __esm({
1598
+ "src/shared/wallet.ts"() {
1599
+ init_fs();
1600
+ init_json();
1601
+ init_parse();
1867
1602
  init_log();
1868
- init_response();
1869
- init_token();
1870
- init_networks();
1871
- DiscoveryDocumentSchema = z.object({
1872
- version: z.number().refine((v) => v === 1, { message: "version must be 1" }),
1873
- resources: z.array(z.url()),
1874
- ownershipProofs: z.array(z.string()).optional(),
1875
- instructions: z.string().optional()
1603
+ init_fs2();
1604
+ WALLET_FILE = configFile("wallet.json", "");
1605
+ storedWalletSchema = z$1.object({
1606
+ privateKey: z$1.string().regex(/^0x[a-fA-F0-9]{64}$/, "Invalid Ethereum private key").transform((privateKey) => privateKey),
1607
+ address: z$1.string().regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address").transform((address) => getAddress(address)),
1608
+ createdAt: z$1.string()
1876
1609
  });
1610
+ walletSurface = "wallet";
1877
1611
  }
1878
1612
  });
1879
1613
 
@@ -1885,28 +1619,35 @@ __export(server_exports, {
1885
1619
  var startServer;
1886
1620
  var init_server = __esm({
1887
1621
  "src/server/index.ts"() {
1888
- init_fetch_x402_resource();
1889
- init_auth();
1622
+ init_x402_fetch();
1623
+ init_auth_fetch();
1890
1624
  init_wallet();
1891
- init_check_endpoint_schema();
1625
+ init_check_endpoint();
1892
1626
  init_redeem_invite2();
1893
1627
  init_telemetry();
1628
+ init_discover_resources();
1894
1629
  init_origins();
1630
+ init_version();
1895
1631
  init_log();
1896
1632
  init_wallet2();
1897
- init_discover_resources();
1898
1633
  init_redeem_invite();
1899
- init_version();
1900
1634
  startServer = async (flags) => {
1901
1635
  log.info("Starting x402scan-mcp...");
1902
1636
  const { dev, invite } = flags;
1903
- const { account } = await getWallet();
1637
+ const walletResult = await getWallet();
1638
+ if (walletResult.isErr()) {
1639
+ log.error(JSON.stringify(walletResult.error, null, 2));
1640
+ console.error(walletResult.error);
1641
+ process.exit(1);
1642
+ }
1643
+ const { account } = walletResult.value;
1904
1644
  const code = invite ?? process.env.INVITE_CODE;
1905
1645
  if (code) {
1906
1646
  await redeemInviteCode({
1907
1647
  code,
1908
1648
  dev,
1909
- address: account.address
1649
+ address: account.address,
1650
+ surface: "startServer"
1910
1651
  });
1911
1652
  }
1912
1653
  const server = new McpServer(
@@ -1952,7 +1693,7 @@ var init_server = __esm({
1952
1693
  });
1953
1694
  var getClient;
1954
1695
  var init_get_client = __esm({
1955
- "src/install/1-get-client/index.ts"() {
1696
+ "src/cli/install/1-get-client/index.ts"() {
1956
1697
  init_clients();
1957
1698
  getClient = async ({ client: flagClient, yes }) => {
1958
1699
  if (yes) {
@@ -1991,14 +1732,14 @@ var init_get_client = __esm({
1991
1732
  if (parsedClientSelection.success) {
1992
1733
  return parsedClientSelection.data;
1993
1734
  }
1994
- outro(chalk.bold.red("No MCP client selected"));
1735
+ outro(chalk2.bold.red("No MCP client selected"));
1995
1736
  process.exit(0);
1996
1737
  };
1997
1738
  }
1998
1739
  });
1999
1740
  var Platforms, getPlatformPath;
2000
1741
  var init_platforms = __esm({
2001
- "src/install/2-add-server/lib/platforms.ts"() {
1742
+ "src/cli/install/2-add-server/lib/platforms.ts"() {
2002
1743
  Platforms = /* @__PURE__ */ ((Platforms2) => {
2003
1744
  Platforms2["Windows"] = "win32";
2004
1745
  Platforms2["MacOS"] = "darwin";
@@ -2033,25 +1774,68 @@ var init_platforms = __esm({
2033
1774
  };
2034
1775
  }
2035
1776
  });
2036
- var parseClientConfig, serializeClientConfig, stringifyObject;
1777
+ var errorType4, surface2, configResultFromThrowable;
1778
+ var init_result = __esm({
1779
+ "src/cli/install/2-add-server/lib/result.ts"() {
1780
+ errorType4 = "config";
1781
+ surface2 = "config_file";
1782
+ configResultFromThrowable = (fn, error) => resultFromThrowable(errorType4, surface2, fn, error);
1783
+ }
1784
+ });
1785
+ var parseContent, parseClientConfig, serializeJsonc, serializeClientConfig, stringifyObject;
2037
1786
  var init_file_types = __esm({
2038
- "src/install/2-add-server/lib/file-types.ts"() {
2039
- parseClientConfig = ({ format: format2, path: path3 }) => {
2040
- const fileContent = fs__default.readFileSync(path3, "utf8");
2041
- let config = {};
2042
- if (format2 === "yaml" /* YAML */) {
2043
- config = yaml.load(fileContent);
2044
- } else if (format2 === "toml" /* TOML */) {
2045
- config = TOML.parse(fileContent);
2046
- } else if (path3.endsWith(".jsonc")) {
2047
- config = jsonc.parse(fileContent);
2048
- } else {
2049
- config = JSON.parse(fileContent);
2050
- }
2051
- return {
2052
- config,
2053
- fileContent
2054
- };
1787
+ "src/cli/install/2-add-server/lib/file-types.ts"() {
1788
+ init_fs();
1789
+ init_result();
1790
+ parseContent = (fileContent, format2, path3) => {
1791
+ return configResultFromThrowable(
1792
+ () => {
1793
+ let config;
1794
+ if (format2 === "yaml" /* YAML */) {
1795
+ config = yaml.load(fileContent);
1796
+ } else if (format2 === "toml" /* TOML */) {
1797
+ config = TOML.parse(fileContent);
1798
+ } else if (path3.endsWith(".jsonc")) {
1799
+ config = jsonc.parse(fileContent);
1800
+ } else {
1801
+ config = JSON.parse(fileContent);
1802
+ }
1803
+ return {
1804
+ config,
1805
+ fileContent
1806
+ };
1807
+ },
1808
+ (e) => ({
1809
+ cause: "parse_config",
1810
+ message: e instanceof Error ? e.message : "Failed to parse config file"
1811
+ })
1812
+ );
1813
+ };
1814
+ parseClientConfig = async ({ format: format2, path: path3 }) => {
1815
+ const readResult = await safeReadFile("config_file", path3);
1816
+ if (readResult.isErr()) return readResult;
1817
+ const parseResult = parseContent(readResult.value, format2, path3);
1818
+ if (parseResult.isErr()) return parseResult;
1819
+ return parseResult;
1820
+ };
1821
+ serializeJsonc = (config, originalContent) => {
1822
+ return configResultFromThrowable(
1823
+ () => {
1824
+ const modifications = [];
1825
+ for (const key of Object.keys(config)) {
1826
+ const keyPath = [key];
1827
+ const edits = jsonc.modify(originalContent, keyPath, config[key], {
1828
+ formattingOptions: { tabSize: 2, insertSpaces: true }
1829
+ });
1830
+ modifications.push(...edits);
1831
+ }
1832
+ return jsonc.applyEdits(originalContent, modifications);
1833
+ },
1834
+ (e) => ({
1835
+ cause: "serialize_config",
1836
+ message: e instanceof Error ? e.message : "Failed to serialize JSONC"
1837
+ })
1838
+ );
2055
1839
  };
2056
1840
  serializeClientConfig = ({ format: format2, path: path3 }, config, originalContent) => {
2057
1841
  if (format2 === "yaml" /* YAML */) {
@@ -2065,24 +1849,13 @@ var init_file_types = __esm({
2065
1849
  return TOML.stringify(config);
2066
1850
  }
2067
1851
  if (path3.endsWith(".jsonc") && originalContent) {
2068
- try {
2069
- const editedContent = originalContent;
2070
- const modifications = [];
2071
- for (const key of Object.keys(config)) {
2072
- const path4 = [key];
2073
- const edits = jsonc.modify(editedContent, path4, config[key], {
2074
- formattingOptions: { tabSize: 2, insertSpaces: true }
2075
- });
2076
- modifications.push(...edits);
2077
- }
2078
- return jsonc.applyEdits(originalContent, modifications);
2079
- } catch (error) {
2080
- console.log(
2081
- `Error applying JSONC edits: ${error instanceof Error ? error.message : String(error)}`
2082
- );
2083
- console.log("Falling back to JSON.stringify (comments will be lost)");
2084
- return JSON.stringify(config, null, 2);
1852
+ const result = serializeJsonc(config, originalContent);
1853
+ if (result.isOk()) {
1854
+ return result.value;
2085
1855
  }
1856
+ console.log(`Error applying JSONC edits: ${result.error.message}`);
1857
+ console.log("Falling back to JSON.stringify (comments will be lost)");
1858
+ return JSON.stringify(config, null, 2);
2086
1859
  }
2087
1860
  return JSON.stringify(config, null, 2);
2088
1861
  };
@@ -2103,7 +1876,7 @@ var init_file_types = __esm({
2103
1876
  });
2104
1877
  var getClientConfigFile;
2105
1878
  var init_client_config_file = __esm({
2106
- "src/install/2-add-server/lib/client-config-file.ts"() {
1879
+ "src/cli/install/2-add-server/lib/client-config-file.ts"() {
2107
1880
  init_platforms();
2108
1881
  init_log();
2109
1882
  init_clients();
@@ -2214,7 +1987,7 @@ var init_client_config_file = __esm({
2214
1987
  "opencode.json"
2215
1988
  );
2216
1989
  const jsoncPath = jsonPath.replace(".json", ".jsonc");
2217
- if (fs__default.existsSync(jsoncPath)) {
1990
+ if (fs2__default.existsSync(jsoncPath)) {
2218
1991
  log.info(`Found .jsonc file for OpenCode, using: ${jsoncPath}`);
2219
1992
  return {
2220
1993
  path: jsoncPath,
@@ -2235,10 +2008,10 @@ var init_client_config_file = __esm({
2235
2008
  }
2236
2009
  });
2237
2010
 
2238
- // src/install/2-add-server/lib/nested-values.ts
2011
+ // src/cli/install/2-add-server/lib/nested-values.ts
2239
2012
  var getNestedValue, setNestedValue;
2240
2013
  var init_nested_values = __esm({
2241
- "src/install/2-add-server/lib/nested-values.ts"() {
2014
+ "src/cli/install/2-add-server/lib/nested-values.ts"() {
2242
2015
  getNestedValue = (obj, path3) => {
2243
2016
  const keys = path3.split(".");
2244
2017
  let current = obj;
@@ -2264,193 +2037,34 @@ var init_nested_values = __esm({
2264
2037
  }
2265
2038
  });
2266
2039
 
2267
- // src/install/2-add-server/lib/index.ts
2268
- var init_lib2 = __esm({
2269
- "src/install/2-add-server/lib/index.ts"() {
2040
+ // src/cli/install/2-add-server/lib/index.ts
2041
+ var init_lib3 = __esm({
2042
+ "src/cli/install/2-add-server/lib/index.ts"() {
2270
2043
  init_client_config_file();
2271
2044
  init_file_types();
2272
2045
  init_nested_values();
2273
2046
  init_platforms();
2274
2047
  }
2275
2048
  });
2276
- async function addServer(client, globalFlags) {
2277
- const { serverName, command, args } = getMcpConfig(globalFlags);
2278
- if (client === "warp" /* Warp */) {
2279
- log$1.info(
2280
- chalk.bold.yellow("Warp requires a manual installation through their UI.")
2281
- );
2282
- log$1.message(
2283
- "Please copy the following configuration object and add it to your Warp MCP config:"
2284
- );
2285
- console.log();
2286
- console.log(
2287
- JSON.stringify(
2288
- {
2289
- [serverName]: {
2290
- command,
2291
- args,
2292
- working_directory: null,
2293
- start_on_launch: true
2294
- }
2295
- },
2296
- null,
2297
- 2
2298
- )
2299
- );
2300
- console.log();
2301
- log$1.message(
2302
- `Read Warp's documentation at https://docs.warp.dev/knowledge-and-collaboration/mcp`
2303
- );
2304
- const addedToWarp = await confirm({
2305
- message: "Did you add the MCP server to your Warp config?"
2306
- });
2307
- if (!addedToWarp) {
2308
- throw new Error("Warp MCP server not added");
2309
- }
2310
- }
2311
- const clientFileTarget = getClientConfigFile(client);
2312
- const { name } = clientMetadata[client];
2313
- try {
2314
- let config = {};
2315
- let content = void 0;
2316
- log.info(`Checking if config file exists at: ${clientFileTarget.path}`);
2317
- if (!fs__default.existsSync(clientFileTarget.path)) {
2318
- log.info("Config file not found, creating default empty config");
2319
- setNestedValue(config, clientFileTarget.configKey, {});
2320
- log.info("Config created successfully");
2321
- if (!globalFlags.yes) {
2322
- await wait({
2323
- startText: "Locating config file",
2324
- stopText: `No config found, creating default empty config`,
2325
- ms: 1e3
2326
- });
2327
- }
2328
- } else {
2329
- log.info("Config file found, reading config file content");
2330
- const { config: rawConfig, fileContent } = parseClientConfig(clientFileTarget);
2331
- config = rawConfig;
2332
- content = fileContent;
2333
- const existingValue = getNestedValue(
2334
- rawConfig,
2335
- clientFileTarget.configKey
2336
- );
2337
- if (!existingValue) {
2338
- setNestedValue(rawConfig, clientFileTarget.configKey, {});
2339
- }
2340
- log.info(
2341
- `Config loaded successfully: ${JSON.stringify(rawConfig, null, 2)}`
2342
- );
2343
- if (!globalFlags.yes) {
2344
- await wait({
2345
- startText: `Locating config file`,
2346
- stopText: `Config loaded from ${clientFileTarget.path}`,
2347
- ms: 1e3
2348
- });
2349
- }
2350
- }
2351
- const servers = getNestedValue(config, clientFileTarget.configKey);
2352
- if (!servers || typeof servers !== "object") {
2353
- log.error(`Invalid ${clientFileTarget.configKey} structure in config`);
2354
- log$1.error(
2355
- chalk.bold.red(
2356
- `Invalid ${clientFileTarget.configKey} structure in config`
2357
- )
2358
- );
2359
- throw new Error(`Invalid ${clientFileTarget.configKey} structure`);
2360
- }
2361
- if (client === "goose" /* Goose */) {
2362
- servers[serverName] = {
2363
- name: serverName,
2364
- cmd: command,
2365
- args,
2366
- enabled: true,
2367
- envs: {},
2368
- type: "stdio",
2369
- timeout: 300
2370
- };
2371
- } else if (client === "zed" /* Zed */) {
2372
- servers[serverName] = {
2373
- source: "custom",
2374
- command,
2375
- args,
2376
- env: {}
2377
- };
2378
- } else if (client === "opencode" /* Opencode */) {
2379
- servers[serverName] = {
2380
- type: "local",
2381
- command,
2382
- args,
2383
- enabled: true,
2384
- environment: {}
2385
- };
2386
- } else {
2387
- servers[serverName] = {
2388
- command,
2389
- args
2390
- };
2391
- }
2392
- if (!globalFlags.yes) {
2393
- await new Promise((resolve) => setTimeout(resolve, 1e3));
2394
- }
2395
- if (!globalFlags.yes) {
2396
- log$1.step(
2397
- `The following will be added to ${chalk.bold.underline(clientFileTarget.path)}`
2398
- );
2399
- }
2400
- const configStr = formatDiffByFormat(
2401
- {
2402
- [clientFileTarget.configKey]: {
2403
- [serverName]: servers[serverName]
2404
- }
2405
- },
2406
- clientFileTarget.format
2407
- );
2408
- if (!globalFlags.yes) {
2409
- await stream.message(
2410
- (async function* () {
2411
- for (const num of Array.from(
2412
- { length: configStr.length },
2413
- (_, i) => i
2414
- )) {
2415
- const char = configStr[num];
2416
- yield char;
2417
- if (!["\n", " ", "\u2500", "\u256E", "\u256D", "\u2570", "\u256F", "\u2502"].includes(char)) {
2418
- await new Promise((resolve) => setTimeout(resolve, 5));
2419
- } else {
2420
- await new Promise((resolve) => setTimeout(resolve, 2));
2421
- }
2422
- }
2423
- })()
2424
- );
2425
- await new Promise((resolve) => setTimeout(resolve, 1e3));
2426
- }
2427
- const isConfirmed = globalFlags.yes ? true : await confirm({
2428
- message: `Would you like to proceed?`,
2429
- active: "Install MCP",
2430
- inactive: "Cancel"
2431
- });
2432
- if (isConfirmed !== true) {
2433
- outro(chalk.bold.red("Installation cancelled"));
2434
- process.exit(0);
2435
- }
2436
- const configContent = serializeClientConfig(
2437
- clientFileTarget,
2438
- config,
2439
- content
2440
- );
2441
- fs__default.writeFileSync(clientFileTarget.path, configContent);
2442
- log$1.success(chalk.bold.green(`Added x402scan MCP to ${name}`));
2443
- } catch (e) {
2444
- log$1.error(chalk.bold.red(`Error adding x402scan MCP to ${name}`));
2445
- throw e;
2049
+ var wait;
2050
+ var init_wait = __esm({
2051
+ "src/cli/lib/wait.ts"() {
2052
+ wait = async ({ startText, stopText, ms }) => {
2053
+ const { start: startSpinner, stop: stopSpinner } = spinner();
2054
+ startSpinner(startText);
2055
+ await new Promise((resolve) => setTimeout(resolve, ms));
2056
+ stopSpinner(stopText);
2057
+ };
2446
2058
  }
2447
- }
2448
- var getMcpConfig, formatDiffByFormat;
2059
+ });
2060
+ var getMcpConfig, addServer, formatDiffByFormat;
2449
2061
  var init_add_server = __esm({
2450
- "src/install/2-add-server/index.ts"() {
2062
+ "src/cli/install/2-add-server/index.ts"() {
2063
+ init_fs();
2451
2064
  init_log();
2452
2065
  init_clients();
2453
- init_lib2();
2066
+ init_lib3();
2067
+ init_version();
2454
2068
  init_wait();
2455
2069
  getMcpConfig = (globalFlags) => {
2456
2070
  if (globalFlags.dev) {
@@ -2463,9 +2077,191 @@ var init_add_server = __esm({
2463
2077
  return {
2464
2078
  serverName: "x402",
2465
2079
  command: "npx",
2466
- args: ["-y", "@x402scan/mcp@latest"]
2080
+ args: ["-y", `@x402scan/mcp@${DIST_TAG}`]
2467
2081
  };
2468
2082
  };
2083
+ addServer = async (client, globalFlags) => {
2084
+ const { serverName, command, args } = getMcpConfig(globalFlags);
2085
+ if (client === "warp" /* Warp */) {
2086
+ log$1.info(
2087
+ chalk2.bold.yellow("Warp requires a manual installation through their UI.")
2088
+ );
2089
+ log$1.message(
2090
+ "Please copy the following configuration object and add it to your Warp MCP config:"
2091
+ );
2092
+ console.log();
2093
+ console.log(
2094
+ JSON.stringify(
2095
+ {
2096
+ [serverName]: {
2097
+ command,
2098
+ args,
2099
+ working_directory: null,
2100
+ start_on_launch: true
2101
+ }
2102
+ },
2103
+ null,
2104
+ 2
2105
+ )
2106
+ );
2107
+ console.log();
2108
+ log$1.message(
2109
+ `Read Warp's documentation at https://docs.warp.dev/knowledge-and-collaboration/mcp`
2110
+ );
2111
+ const addedToWarp = await confirm({
2112
+ message: "Did you add the MCP server to your Warp config?"
2113
+ });
2114
+ if (!addedToWarp) {
2115
+ return err("user", "install", {
2116
+ cause: "warp_mcp_server_not_added",
2117
+ message: "Warp MCP server not added"
2118
+ });
2119
+ }
2120
+ }
2121
+ const clientFileTarget = getClientConfigFile(client);
2122
+ const { name } = clientMetadata[client];
2123
+ let config = {};
2124
+ let content = void 0;
2125
+ log.info(`Checking if config file exists at: ${clientFileTarget.path}`);
2126
+ if (!fs2__default.existsSync(clientFileTarget.path)) {
2127
+ log.info("Config file not found, creating default empty config");
2128
+ setNestedValue(config, clientFileTarget.configKey, {});
2129
+ log.info("Config created successfully");
2130
+ if (!globalFlags.yes) {
2131
+ await wait({
2132
+ startText: "Locating config file",
2133
+ stopText: `No config found, creating default empty config`,
2134
+ ms: 1e3
2135
+ });
2136
+ }
2137
+ } else {
2138
+ log.info("Config file found, reading config file content");
2139
+ const parseResult = await parseClientConfig(clientFileTarget);
2140
+ if (parseResult.isErr()) {
2141
+ log$1.error(
2142
+ chalk2.bold.red(`Error reading config: ${parseResult.error.message}`)
2143
+ );
2144
+ outro(chalk2.bold.red(`Error adding x402scan MCP to ${name}`));
2145
+ process.exit(1);
2146
+ }
2147
+ const { config: rawConfig, fileContent } = parseResult.value;
2148
+ config = rawConfig;
2149
+ content = fileContent;
2150
+ const existingValue = getNestedValue(rawConfig, clientFileTarget.configKey);
2151
+ if (!existingValue) {
2152
+ setNestedValue(rawConfig, clientFileTarget.configKey, {});
2153
+ }
2154
+ if (!globalFlags.yes) {
2155
+ await wait({
2156
+ startText: `Locating config file`,
2157
+ stopText: `Config loaded from ${clientFileTarget.path}`,
2158
+ ms: 1e3
2159
+ });
2160
+ }
2161
+ }
2162
+ const servers = getNestedValue(config, clientFileTarget.configKey);
2163
+ if (!servers || typeof servers !== "object") {
2164
+ log.error(`Invalid ${clientFileTarget.configKey} structure in config`);
2165
+ log$1.error(
2166
+ chalk2.bold.red(
2167
+ `Invalid ${clientFileTarget.configKey} structure in config`
2168
+ )
2169
+ );
2170
+ outro(chalk2.bold.red(`Error adding x402scan MCP to ${name}`));
2171
+ process.exit(1);
2172
+ }
2173
+ if (client === "goose" /* Goose */) {
2174
+ servers[serverName] = {
2175
+ name: serverName,
2176
+ cmd: command,
2177
+ args,
2178
+ enabled: true,
2179
+ envs: {},
2180
+ type: "stdio",
2181
+ timeout: 300
2182
+ };
2183
+ } else if (client === "zed" /* Zed */) {
2184
+ servers[serverName] = {
2185
+ source: "custom",
2186
+ command,
2187
+ args,
2188
+ env: {}
2189
+ };
2190
+ } else if (client === "opencode" /* Opencode */) {
2191
+ servers[serverName] = {
2192
+ type: "local",
2193
+ command,
2194
+ args,
2195
+ enabled: true,
2196
+ environment: {}
2197
+ };
2198
+ } else {
2199
+ servers[serverName] = {
2200
+ command,
2201
+ args
2202
+ };
2203
+ }
2204
+ if (!globalFlags.yes) {
2205
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
2206
+ log$1.step(
2207
+ `The following will be added to ${chalk2.bold.underline(clientFileTarget.path)}`
2208
+ );
2209
+ }
2210
+ const configStr = formatDiffByFormat(
2211
+ {
2212
+ [clientFileTarget.configKey]: {
2213
+ [serverName]: servers[serverName]
2214
+ }
2215
+ },
2216
+ clientFileTarget.format
2217
+ );
2218
+ if (!globalFlags.yes) {
2219
+ await stream.message(
2220
+ (async function* () {
2221
+ for (const num of Array.from(
2222
+ { length: configStr.length },
2223
+ (_, i) => i
2224
+ )) {
2225
+ const char = configStr[num];
2226
+ yield char;
2227
+ if (!["\n", " ", "\u2500", "\u256E", "\u256D", "\u2570", "\u256F", "\u2502"].includes(char)) {
2228
+ await new Promise((resolve) => setTimeout(resolve, 5));
2229
+ } else {
2230
+ await new Promise((resolve) => setTimeout(resolve, 2));
2231
+ }
2232
+ }
2233
+ })()
2234
+ );
2235
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
2236
+ }
2237
+ const isConfirmed = globalFlags.yes ? true : await confirm({
2238
+ message: `Would you like to proceed?`,
2239
+ active: "Install MCP",
2240
+ inactive: "Cancel"
2241
+ });
2242
+ if (isConfirmed !== true) {
2243
+ outro(chalk2.bold.red("Installation cancelled"));
2244
+ process.exit(0);
2245
+ }
2246
+ const configContent = serializeClientConfig(
2247
+ clientFileTarget,
2248
+ config,
2249
+ content
2250
+ );
2251
+ const writeResult = await safeWriteFile(
2252
+ "config_file",
2253
+ clientFileTarget.path,
2254
+ configContent
2255
+ );
2256
+ if (writeResult.isErr()) {
2257
+ log$1.error(
2258
+ chalk2.bold.red(`Error writing config: ${writeResult.error.message}`)
2259
+ );
2260
+ outro(chalk2.bold.red(`Error adding x402scan MCP to ${name}`));
2261
+ process.exit(1);
2262
+ }
2263
+ log$1.success(chalk2.bold.green(`Added x402scan MCP to ${name}`));
2264
+ };
2469
2265
  formatDiffByFormat = (obj, format2) => {
2470
2266
  const str = stringifyObject(obj, format2);
2471
2267
  switch (format2) {
@@ -2475,7 +2271,7 @@ var init_add_server = __esm({
2475
2271
  const diffLines = [0, 1, numLines - 2, numLines - 1];
2476
2272
  const isDiffLine = !diffLines.includes(index);
2477
2273
  if (isDiffLine) {
2478
- return `${chalk.bold.green(`+ ${line.slice(2)}`)}`;
2274
+ return `${chalk2.bold.green(`+ ${line.slice(2)}`)}`;
2479
2275
  }
2480
2276
  return line;
2481
2277
  }).join("\n");
@@ -2485,14 +2281,14 @@ var init_add_server = __esm({
2485
2281
  const diffLines = [0, 1, str.length - 2, str.length - 1];
2486
2282
  const isDiffLine = !diffLines.includes(index);
2487
2283
  if (isDiffLine) {
2488
- return `${chalk.bold.green(`+ ${line.slice(2)}`)}`;
2284
+ return `${chalk2.bold.green(`+ ${line.slice(2)}`)}`;
2489
2285
  }
2490
2286
  return line;
2491
2287
  }).join("\n");
2492
2288
  }
2493
2289
  case "toml" /* TOML */: {
2494
2290
  return str.split("\n").filter((line) => line.trim() !== "").map((line) => {
2495
- return `${chalk.bold.green(`+ ${line.trim()}`)}`;
2291
+ return `${chalk2.bold.green(`+ ${line.trim()}`)}`;
2496
2292
  }).join("\n");
2497
2293
  }
2498
2294
  }
@@ -2501,7 +2297,7 @@ var init_add_server = __esm({
2501
2297
  });
2502
2298
  var redeemInviteCode2;
2503
2299
  var init_redeem_invite3 = __esm({
2504
- "src/install/3-redeem-invite/index.ts"() {
2300
+ "src/cli/install/3-redeem-invite/index.ts"() {
2505
2301
  init_wait();
2506
2302
  init_redeem_invite();
2507
2303
  redeemInviteCode2 = async (props, flags) => {
@@ -2511,26 +2307,18 @@ var init_redeem_invite3 = __esm({
2511
2307
  }
2512
2308
  const result = await redeemInviteCode(props);
2513
2309
  return result.match(
2514
- async ({ data }) => {
2310
+ async ({ amount, txHash }) => {
2515
2311
  if (!flags.yes) {
2516
2312
  s.stop("Invite code redeemed successfully!");
2517
2313
  await wait({
2518
2314
  startText: "Processing...",
2519
- stopText: chalk.green(
2520
- `${chalk.bold(data.amount)} USDC has been sent to your wallet!`
2315
+ stopText: chalk2.green(
2316
+ `${chalk2.bold(amount)} USDC has been sent to your wallet!`
2521
2317
  ),
2522
2318
  ms: 1e3
2523
2319
  });
2524
- } else {
2525
- log$1.success(
2526
- chalk.green(
2527
- `${chalk.bold(data.amount)} USDC has been sent to your wallet!`
2528
- )
2529
- );
2530
2320
  }
2531
- log$1.info(
2532
- chalk.dim(`Transaction: https://basescan.org/tx/${data.txHash}`)
2533
- );
2321
+ log$1.info(chalk2.dim(`Transaction: https://basescan.org/tx/${txHash}`));
2534
2322
  return true;
2535
2323
  },
2536
2324
  (error) => {
@@ -2538,7 +2326,7 @@ var init_redeem_invite3 = __esm({
2538
2326
  s.stop("Invite code redemption failed");
2539
2327
  }
2540
2328
  log$1.warning(
2541
- chalk.yellow(`Failed to redeem invite code: ${error?.message}`)
2329
+ chalk2.yellow(`Failed to redeem invite code: ${error.message}`)
2542
2330
  );
2543
2331
  return false;
2544
2332
  }
@@ -2546,95 +2334,209 @@ var init_redeem_invite3 = __esm({
2546
2334
  };
2547
2335
  }
2548
2336
  });
2337
+ var promptDeposit;
2338
+ var init_deposit = __esm({
2339
+ "src/cli/lib/deposit.ts"() {
2340
+ init_networks();
2341
+ init_wait();
2342
+ init_utils();
2343
+ init_redeem_invite();
2344
+ promptDeposit = async (props) => {
2345
+ const { address, flags, surface: surface3 } = props;
2346
+ const depositLink = getDepositLink(address, flags);
2347
+ const depositChoice = flags.yes || surface3 === "guided" ? "manual" : await select({
2348
+ message: chalk2.bold("How would you like to deposit?"),
2349
+ initialValue: "guided",
2350
+ options: [
2351
+ {
2352
+ label: "Guided - Recommended",
2353
+ value: "guided",
2354
+ hint: "Online portal in x402scan"
2355
+ },
2356
+ {
2357
+ label: "Manual",
2358
+ value: "manual",
2359
+ hint: "Print deposit instructions"
2360
+ },
2361
+ {
2362
+ label: "Redeem Invite Code",
2363
+ value: "invite",
2364
+ hint: "Enter an invite code for starter money"
2365
+ },
2366
+ {
2367
+ label: "Skip",
2368
+ value: void 0,
2369
+ hint: "Skip deposit process - functionality limited"
2370
+ }
2371
+ ]
2372
+ });
2373
+ if (depositChoice === "guided") {
2374
+ await wait({
2375
+ startText: "Opening deposit page...",
2376
+ stopText: `Opening ${chalk2.underline.hex("#2563eb")(depositLink)}`,
2377
+ ms: 1e3
2378
+ });
2379
+ await open(depositLink);
2380
+ } else if (depositChoice === "manual") {
2381
+ log$1.step(chalk2.bold("Account Information"));
2382
+ log$1.message(`Address: ${address}`);
2383
+ log$1.message(`Network: ${getChainName(DEFAULT_NETWORK)}`);
2384
+ log$1.step(chalk2.bold("Online Portal"));
2385
+ log$1.message(`${chalk2.underline(depositLink)}`);
2386
+ } else if (depositChoice === "invite") {
2387
+ const code = await text({
2388
+ message: "Enter your invite code",
2389
+ placeholder: "MRT-XXXXX",
2390
+ validate: (value) => {
2391
+ if (!value || value.trim().length === 0) {
2392
+ return "Please enter an invite code";
2393
+ }
2394
+ }
2395
+ });
2396
+ if (typeof code !== "string") {
2397
+ return promptDeposit({ address, flags, surface: surface3 });
2398
+ }
2399
+ const s = spinner();
2400
+ s.start("Redeeming invite code...");
2401
+ const redeemResult = await redeemInviteCode({
2402
+ code,
2403
+ dev: flags.dev,
2404
+ address,
2405
+ surface: "redeemInvite"
2406
+ });
2407
+ if (redeemResult.isErr()) {
2408
+ s.stop("Invite code redemption failed");
2409
+ log$1.error("Failed to redeem invite code");
2410
+ return promptDeposit({ address, flags, surface: surface3 });
2411
+ }
2412
+ s.stop("Invite code redeemed successfully!");
2413
+ const { amount, txHash } = redeemResult.value;
2414
+ await wait({
2415
+ startText: "Processing...",
2416
+ stopText: chalk2.green(
2417
+ `${chalk2.bold(amount)} USDC has been sent to your wallet!`
2418
+ ),
2419
+ ms: 1500
2420
+ });
2421
+ log$1.success(chalk2.bold(`Your wallet has been funded with ${amount} USDC`));
2422
+ if (txHash) {
2423
+ log$1.info(chalk2.dim(`Transaction: https://basescan.org/tx/${txHash}`));
2424
+ }
2425
+ return;
2426
+ }
2427
+ };
2428
+ }
2429
+ });
2549
2430
  var addFunds;
2550
2431
  var init_add_funds = __esm({
2551
- "src/install/4-add-funds/index.ts"() {
2432
+ "src/cli/install/4-add-funds/index.ts"() {
2552
2433
  init_balance();
2553
2434
  init_deposit();
2554
- init_wait();
2555
2435
  addFunds = async ({ flags, address, isNew }) => {
2556
2436
  if (isNew) {
2557
2437
  if (!flags.yes) {
2558
2438
  await new Promise((resolve) => setTimeout(resolve, 1e3));
2559
2439
  }
2560
2440
  log$1.info("To use paid API tools, you will need USDC in your wallet.");
2561
- await promptDeposit(address, flags);
2441
+ await promptDeposit({ address, flags, surface: "add-funds" });
2562
2442
  } else {
2563
- const { balanceFormatted } = await getUSDCBalance(address, flags);
2564
- if (!flags.yes) {
2565
- await wait({
2566
- startText: "Checking balance...",
2567
- stopText: `Balance: ${chalk.bold(`${balanceFormatted} USDC`)} `,
2568
- ms: 1e3
2569
- });
2443
+ const { start, stop } = spinner();
2444
+ start("Checking balance...");
2445
+ const balanceResult = await getBalance({
2446
+ address,
2447
+ flags,
2448
+ surface: "add-funds"
2449
+ });
2450
+ if (balanceResult.isOk()) {
2451
+ stop(`Balance: ${chalk2.bold(`${balanceResult.value.balance} USDC`)} `);
2452
+ } else {
2453
+ stop(`Error: ${balanceResult.error.message}`);
2454
+ return;
2570
2455
  }
2571
- if (balanceFormatted < 1) {
2456
+ const balance = balanceResult.value;
2457
+ if (balance.balance < 1) {
2572
2458
  log$1.warning(
2573
- chalk.bold(
2574
- `Your balance is low (${balanceFormatted} USDC). Consider topping up.`
2459
+ chalk2.bold(
2460
+ `Your balance is low (${balance.balance} USDC). Consider topping up.`
2575
2461
  )
2576
2462
  );
2577
- await promptDeposit(address, flags);
2463
+ await promptDeposit({ address, flags, surface: "install" });
2578
2464
  }
2579
2465
  }
2580
2466
  };
2581
2467
  }
2582
2468
  });
2583
2469
 
2584
- // src/install/index.ts
2470
+ // src/cli/install/index.ts
2585
2471
  var install_exports = {};
2586
2472
  __export(install_exports, {
2587
2473
  installMcpServer: () => installMcpServer
2588
2474
  });
2589
2475
  var installMcpServer;
2590
2476
  var init_install = __esm({
2591
- "src/install/index.ts"() {
2477
+ "src/cli/install/index.ts"() {
2592
2478
  init_wallet2();
2479
+ init_log();
2593
2480
  init_get_client();
2594
2481
  init_add_server();
2595
2482
  init_redeem_invite3();
2596
2483
  init_add_funds();
2597
2484
  installMcpServer = async (flags) => {
2485
+ intro(chalk2.green.bold(`Install x402scan MCP`));
2486
+ const walletResult = await getWallet();
2487
+ if (walletResult.isErr()) {
2488
+ log.error(JSON.stringify(walletResult.error, null, 2));
2489
+ outro(chalk2.bold.red("Failed to get wallet"));
2490
+ process.exit(1);
2491
+ }
2598
2492
  const {
2599
2493
  account: { address },
2600
2494
  isNew
2601
- } = await getWallet();
2602
- intro(chalk.green.bold(`Install x402scan MCP`));
2495
+ } = walletResult.value;
2603
2496
  const client = await getClient(flags);
2604
2497
  await addServer(client, flags);
2605
2498
  const inviteRedeemed = flags.invite ? await redeemInviteCode2(
2606
2499
  {
2607
2500
  code: flags.invite,
2608
2501
  dev: flags.dev,
2609
- address
2502
+ address,
2503
+ surface: "install"
2610
2504
  },
2611
2505
  flags
2612
2506
  ) : false;
2613
2507
  if (!inviteRedeemed) {
2614
2508
  await addFunds({ flags, address, isNew });
2615
2509
  }
2616
- outro(chalk.bold.green("Your x402scan MCP server is ready to use!"));
2510
+ outro(chalk2.bold.green("Your x402scan MCP server is ready to use!"));
2617
2511
  };
2618
2512
  }
2619
2513
  });
2620
2514
 
2621
- // src/fund/index.ts
2515
+ // src/cli/fund/index.ts
2622
2516
  var fund_exports = {};
2623
2517
  __export(fund_exports, {
2624
2518
  fundMcpServer: () => fundMcpServer
2625
2519
  });
2626
2520
  var fundMcpServer;
2627
2521
  var init_fund = __esm({
2628
- "src/fund/index.ts"() {
2522
+ "src/cli/fund/index.ts"() {
2629
2523
  init_wallet2();
2630
2524
  init_deposit();
2525
+ init_log();
2631
2526
  fundMcpServer = async (flags) => {
2632
- intro(chalk.bold(`Fund ${chalk.hex("#2563eb")("x402scan MCP")}`));
2527
+ intro(chalk2.bold(`Fund ${chalk2.hex("#2563eb")("x402scan MCP")}`));
2528
+ const walletResult = await getWallet();
2529
+ if (walletResult.isErr()) {
2530
+ log.error(walletResult.error.message);
2531
+ log$1.error(walletResult.error.message);
2532
+ outro(chalk2.bold.red("Failed to get wallet"));
2533
+ process.exit(1);
2534
+ }
2633
2535
  const {
2634
2536
  account: { address }
2635
- } = await getWallet();
2636
- await promptDeposit(address, flags);
2637
- outro(chalk.bold.green("Your x402scan MCP server is funded!"));
2537
+ } = walletResult.value;
2538
+ await promptDeposit({ address, flags, surface: "fund" });
2539
+ outro(chalk2.bold.green("Your x402scan MCP server is funded!"));
2638
2540
  };
2639
2541
  }
2640
2542
  });
@@ -2685,8 +2587,8 @@ void yargs(hideBin(process.argv)).scriptName("@x402scan/mcp").option("dev", {
2685
2587
  const { fundMcpServer: fundMcpServer2 } = await Promise.resolve().then(() => (init_fund(), fund_exports));
2686
2588
  await fundMcpServer2(args);
2687
2589
  }
2688
- ).strict().demandCommand(0, 1, "", "Too many commands provided").help().parseAsync().catch((err2) => {
2689
- console.error("Fatal:", err2);
2590
+ ).strict().demandCommand(0, 1, "", "Too many commands provided").help().parseAsync().catch((err9) => {
2591
+ console.error("Fatal:", err9);
2690
2592
  process.exit(1);
2691
2593
  });
2692
2594
  //# sourceMappingURL=index.js.map