@x402scan/mcp 0.0.7-beta.3 → 0.0.7-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,976 @@
1
+ import {
2
+ MCP_VERSION,
3
+ getBalance
4
+ } from "./chunk-2UP5W5MC.js";
5
+ import {
6
+ DEFAULT_NETWORK,
7
+ err,
8
+ fetchHttpErr,
9
+ getBaseUrl,
10
+ getChainName,
11
+ getDepositLink,
12
+ getWallet,
13
+ isFetchError,
14
+ log,
15
+ ok,
16
+ openDepositLink,
17
+ redeemInviteCode,
18
+ resultFromPromise,
19
+ resultFromThrowable,
20
+ safeFetch,
21
+ safeFetchJson,
22
+ safeParseResponse,
23
+ safeStringifyJson
24
+ } from "./chunk-JXXC6FYE.js";
25
+
26
+ // src/server/index.ts
27
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
28
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
29
+
30
+ // src/server/tools/x402-fetch.ts
31
+ import { x402Client, x402HTTPClient } from "@x402/core/client";
32
+ import { ExactEvmScheme } from "@x402/evm/exact/client";
33
+
34
+ // src/server/tools/response/lib.ts
35
+ var parsedResponseToToolContentPart = (data) => {
36
+ switch (data.type) {
37
+ case "json":
38
+ return {
39
+ type: "text",
40
+ text: JSON.stringify(data.data, null, 2)
41
+ };
42
+ case "image":
43
+ return {
44
+ type: "image",
45
+ mimeType: data.mimeType,
46
+ data: Buffer.from(data.data).toString("base64")
47
+ };
48
+ case "audio":
49
+ return {
50
+ type: "audio",
51
+ mimeType: data.mimeType,
52
+ data: Buffer.from(data.data).toString("base64")
53
+ };
54
+ case "text":
55
+ return { type: "text", text: data.data };
56
+ default:
57
+ return {
58
+ type: "text",
59
+ text: `Unsupported response type: ${data.type}`
60
+ };
61
+ }
62
+ };
63
+
64
+ // src/server/tools/response/error.ts
65
+ var buildMcpError = (content) => {
66
+ return {
67
+ content,
68
+ isError: true
69
+ };
70
+ };
71
+ var mcpErrorJson = (error) => {
72
+ return safeStringifyJson("mcp-error-json", error).match(
73
+ (success) => buildMcpError([{ type: "text", text: success }]),
74
+ (error2) => buildMcpError([
75
+ { type: "text", text: JSON.stringify(error2, null, 2) }
76
+ ])
77
+ );
78
+ };
79
+ var mcpError = async (err2) => {
80
+ const { error } = err2;
81
+ if (isFetchError(error)) {
82
+ switch (error.cause) {
83
+ case "network":
84
+ case "parse":
85
+ return mcpErrorJson({ ...error });
86
+ case "http":
87
+ const { response, ...rest } = error;
88
+ const parseResponseResult = await safeParseResponse(
89
+ "mcp-error-fetch-parse-response",
90
+ response
91
+ );
92
+ return buildMcpError([
93
+ { type: "text", text: JSON.stringify(rest, null, 2) },
94
+ ...parseResponseResult.match(
95
+ (success) => [parsedResponseToToolContentPart(success)],
96
+ () => []
97
+ )
98
+ ]);
99
+ }
100
+ }
101
+ return mcpErrorJson({ ...error });
102
+ };
103
+ var mcpErrorFetch = async (surface2, response) => {
104
+ return mcpError(fetchHttpErr(surface2, response));
105
+ };
106
+
107
+ // src/server/tools/response/success.ts
108
+ var buildMcpSuccess = (content) => {
109
+ return {
110
+ content
111
+ };
112
+ };
113
+ var mcpSuccessJson = (data) => {
114
+ return safeStringifyJson("mcp-success-text", data).match(
115
+ (success) => buildMcpSuccess([{ type: "text", text: success }]),
116
+ (error) => mcpErrorJson(error)
117
+ );
118
+ };
119
+ var mcpSuccessResponse = (data, extra) => {
120
+ const parsedExtra = extra ? safeStringifyJson("mcp-success-extra", extra).match(
121
+ (success) => success,
122
+ () => void 0
123
+ ) : void 0;
124
+ return buildMcpSuccess([
125
+ parsedResponseToToolContentPart(data),
126
+ ...parsedExtra ? [{ type: "text", text: parsedExtra }] : []
127
+ ]);
128
+ };
129
+
130
+ // src/server/tools/lib/request.ts
131
+ import z from "zod";
132
+ var requestSchema = z.object({
133
+ url: z.url().describe("The endpoint URL"),
134
+ method: z.enum(["GET", "POST", "PUT", "DELETE", "PATCH"]).default("GET").describe("HTTP method"),
135
+ body: z.unknown().optional().describe("Request body for POST/PUT/PATCH methods"),
136
+ headers: z.record(z.string(), z.string()).optional().describe("Additional headers to include").default({})
137
+ });
138
+ var buildRequest = (input) => {
139
+ return new Request(input.url, {
140
+ method: input.method,
141
+ body: input.body ? typeof input.body === "string" ? input.body : JSON.stringify(input.body) : void 0,
142
+ headers: {
143
+ ...input.body ? { "Content-Type": "application/json" } : {},
144
+ ...input.headers
145
+ }
146
+ });
147
+ };
148
+
149
+ // src/server/tools/lib/check-balance.ts
150
+ var checkBalance = async ({
151
+ server,
152
+ address,
153
+ amountNeeded,
154
+ message,
155
+ flags,
156
+ surface: surface2
157
+ }) => {
158
+ const balanceResult = await getBalance({ address, flags, surface: surface2 });
159
+ if (balanceResult.isErr()) {
160
+ log.error(JSON.stringify(balanceResult.error, null, 2));
161
+ return;
162
+ }
163
+ const balance = balanceResult.value;
164
+ if (balance.balance < amountNeeded) {
165
+ const capabilities = server.server.getClientCapabilities();
166
+ if (!capabilities?.elicitation) {
167
+ throw new Error(
168
+ `${message(balance.balance)}
169
+
170
+ You can deposit USDC at ${getDepositLink(address, flags)}`
171
+ );
172
+ }
173
+ const result = await server.server.elicitInput({
174
+ mode: "form",
175
+ message: message(balance.balance),
176
+ requestedSchema: {
177
+ type: "object",
178
+ properties: {}
179
+ }
180
+ });
181
+ if (result.action === "accept") {
182
+ await openDepositLink(address, flags);
183
+ }
184
+ }
185
+ return balance.balance;
186
+ };
187
+
188
+ // src/shared/token.ts
189
+ import { formatUnits } from "viem";
190
+ var tokenStringToNumber = (amount, decimals = 6) => {
191
+ return Number(formatUnits(BigInt(amount), decimals));
192
+ };
193
+
194
+ // src/shared/neverthrow/x402/index.ts
195
+ import { createSIWxPayload } from "@x402scan/siwx";
196
+ var errorType = "x402";
197
+ var x402Err = (cause, error) => err(errorType, cause, error);
198
+ var x402ResultFromPromise = (surface2, promise, error) => resultFromPromise(errorType, surface2, promise, error);
199
+ var x402ResultFromThrowable = (surface2, fn, error) => resultFromThrowable(errorType, surface2, fn, error);
200
+ var safeGetPaymentRequired = (surface2, client, response) => {
201
+ return x402ResultFromPromise(
202
+ surface2,
203
+ response.json().then(
204
+ (json) => client.getPaymentRequiredResponse(
205
+ (name) => response.headers.get(name),
206
+ json
207
+ ),
208
+ () => client.getPaymentRequiredResponse((name) => response.headers.get(name))
209
+ ),
210
+ (error) => ({
211
+ cause: "parse_payment_required",
212
+ message: error instanceof Error ? error.message : "Failed to parse payment required"
213
+ })
214
+ );
215
+ };
216
+ var safeCreatePaymentPayload = (surface2, client, paymentRequired) => {
217
+ return x402ResultFromPromise(
218
+ surface2,
219
+ client.createPaymentPayload(paymentRequired),
220
+ (error) => ({
221
+ cause: "create_payment_payload",
222
+ message: error instanceof Error ? error.message : "Failed to create payment payload"
223
+ })
224
+ );
225
+ };
226
+ var safeGetPaymentSettlement = (surface2, client, response) => {
227
+ return x402ResultFromThrowable(
228
+ surface2,
229
+ () => client.getPaymentSettleResponse((name) => response.headers.get(name)),
230
+ (error) => ({
231
+ cause: "get_payment_settlement",
232
+ message: error instanceof Error ? error.message : "Failed to get payment settlement"
233
+ })
234
+ );
235
+ };
236
+ var safeCreateSIWxPayload = (surface2, serverInfo, signer) => {
237
+ return x402ResultFromPromise(
238
+ surface2,
239
+ createSIWxPayload(serverInfo, signer),
240
+ (error) => ({
241
+ cause: "create_siwx_payload",
242
+ message: error instanceof Error ? error.message : "Failed to create SIWX payload"
243
+ })
244
+ );
245
+ };
246
+
247
+ // src/server/tools/x402-fetch.ts
248
+ var toolName = "fetch";
249
+ var registerFetchX402ResourceTool = ({
250
+ server,
251
+ account,
252
+ flags
253
+ }) => {
254
+ server.registerTool(
255
+ toolName,
256
+ {
257
+ description: "Makes an http fetch request. If the request is to an x402-protected resource, it will handle payment automatically.",
258
+ inputSchema: requestSchema
259
+ },
260
+ async (input) => {
261
+ const coreClient = x402Client.fromConfig({
262
+ schemes: [
263
+ { network: DEFAULT_NETWORK, client: new ExactEvmScheme(account) }
264
+ ]
265
+ });
266
+ coreClient.onBeforePaymentCreation(async ({ selectedRequirements }) => {
267
+ const amount = tokenStringToNumber(selectedRequirements.amount);
268
+ await checkBalance({
269
+ surface: toolName,
270
+ server,
271
+ address: account.address,
272
+ amountNeeded: amount,
273
+ message: (balance) => `This request costs ${amount} USDC. Your current balance is ${balance} USDC.`,
274
+ flags
275
+ });
276
+ });
277
+ const client = new x402HTTPClient(coreClient);
278
+ const fetchWithPay = safeWrapFetchWithPayment(client);
279
+ const fetchResult = await fetchWithPay(buildRequest(input));
280
+ if (fetchResult.isErr()) {
281
+ return mcpError(fetchResult);
282
+ }
283
+ const response = fetchResult.value;
284
+ if (!response.ok) {
285
+ return mcpErrorFetch(toolName, response);
286
+ }
287
+ const parseResponseResult = await safeParseResponse(toolName, response);
288
+ if (parseResponseResult.isErr()) {
289
+ return mcpError(parseResponseResult);
290
+ }
291
+ const settlementResult = safeGetPaymentSettlement(
292
+ toolName,
293
+ client,
294
+ response
295
+ );
296
+ return mcpSuccessResponse(
297
+ parseResponseResult.value,
298
+ settlementResult.isOk() ? { payment: settlementResult.value } : void 0
299
+ );
300
+ }
301
+ );
302
+ };
303
+ function safeWrapFetchWithPayment(client) {
304
+ return async (input, init) => {
305
+ const request = new Request(input, init);
306
+ const clonedRequest = request.clone();
307
+ const probeResult = await safeFetch(toolName, request);
308
+ if (probeResult.isErr()) {
309
+ return probeResult;
310
+ }
311
+ if (probeResult.value.status !== 402) {
312
+ return probeResult;
313
+ }
314
+ const response = probeResult.value;
315
+ const paymentRequiredResult = await safeGetPaymentRequired(
316
+ toolName,
317
+ client,
318
+ response
319
+ );
320
+ if (paymentRequiredResult.isErr()) {
321
+ return paymentRequiredResult;
322
+ }
323
+ const paymentRequired = paymentRequiredResult.value;
324
+ const paymentPayloadResult = await safeCreatePaymentPayload(
325
+ toolName,
326
+ client,
327
+ paymentRequired
328
+ );
329
+ if (paymentPayloadResult.isErr()) {
330
+ return paymentPayloadResult;
331
+ }
332
+ const paymentPayload = paymentPayloadResult.value;
333
+ const paymentHeaders = client.encodePaymentSignatureHeader(paymentPayload);
334
+ if (clonedRequest.headers.has("PAYMENT-SIGNATURE") || clonedRequest.headers.has("X-PAYMENT")) {
335
+ return x402Err(toolName, {
336
+ cause: "payment_already_attempted",
337
+ message: "Payment already attempted"
338
+ });
339
+ }
340
+ for (const [key, value] of Object.entries(paymentHeaders)) {
341
+ clonedRequest.headers.set(key, value);
342
+ }
343
+ clonedRequest.headers.set(
344
+ "Access-Control-Expose-Headers",
345
+ "PAYMENT-RESPONSE,X-PAYMENT-RESPONSE"
346
+ );
347
+ return await safeFetch(toolName, clonedRequest);
348
+ };
349
+ }
350
+
351
+ // src/server/tools/auth-fetch.ts
352
+ import { x402Client as x402Client2, x402HTTPClient as x402HTTPClient2 } from "@x402/core/client";
353
+ import { encodeSIWxHeader } from "@x402scan/siwx";
354
+
355
+ // src/server/lib/x402-extensions.ts
356
+ var getBazaarExtension = (extensions) => {
357
+ const { bazaar } = extensions ?? {};
358
+ if (!bazaar) {
359
+ return void 0;
360
+ }
361
+ return bazaar;
362
+ };
363
+ var getInputSchema = (extensions) => getBazaarExtension(extensions)?.schema.properties.input;
364
+ var getSiwxExtension = (extensions) => {
365
+ const siwx = extensions?.["sign-in-with-x"];
366
+ if (!siwx?.info) {
367
+ return void 0;
368
+ }
369
+ return siwx.info;
370
+ };
371
+
372
+ // src/server/tools/auth-fetch.ts
373
+ var toolName2 = "fetchWithAuth";
374
+ var registerAuthTools = ({ server, account }) => {
375
+ server.registerTool(
376
+ toolName2,
377
+ {
378
+ 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.",
379
+ inputSchema: requestSchema
380
+ },
381
+ async (input) => {
382
+ const httpClient = new x402HTTPClient2(new x402Client2());
383
+ const firstResult = await safeFetch(toolName2, buildRequest(input));
384
+ if (firstResult.isErr()) {
385
+ return mcpError(firstResult);
386
+ }
387
+ const firstResponse = firstResult.value;
388
+ if (firstResponse.status !== 402) {
389
+ if (!firstResponse.ok) {
390
+ return mcpErrorFetch(toolName2, firstResponse);
391
+ }
392
+ const parseResponseResult2 = await safeParseResponse(
393
+ toolName2,
394
+ firstResponse
395
+ );
396
+ if (parseResponseResult2.isErr()) {
397
+ return mcpError(parseResponseResult2);
398
+ }
399
+ return mcpSuccessResponse(parseResponseResult2.value);
400
+ }
401
+ const getPaymentRequiredResult = await safeGetPaymentRequired(
402
+ toolName2,
403
+ httpClient,
404
+ firstResponse
405
+ );
406
+ if (getPaymentRequiredResult.isErr()) {
407
+ return mcpError(getPaymentRequiredResult);
408
+ }
409
+ const paymentRequired = getPaymentRequiredResult.value;
410
+ const siwxExtension = getSiwxExtension(paymentRequired.extensions);
411
+ if (!siwxExtension) {
412
+ return mcpErrorJson({
413
+ message: "Endpoint returned 402 but no sign-in-with-x extension found",
414
+ statusCode: 402,
415
+ extensions: Object.keys(paymentRequired.extensions ?? {}),
416
+ hint: "This endpoint may require payment instead of authentication. Use execute_call for paid requests."
417
+ });
418
+ }
419
+ const serverInfo = siwxExtension;
420
+ const requiredFields = [
421
+ "domain",
422
+ "uri",
423
+ "version",
424
+ "chainId",
425
+ "nonce",
426
+ "issuedAt"
427
+ ];
428
+ const missingFields = requiredFields.filter(
429
+ (f) => !serverInfo[f]
430
+ );
431
+ if (missingFields.length > 0) {
432
+ return mcpErrorJson({
433
+ message: "Invalid sign-in-with-x extension: missing required fields",
434
+ missingFields,
435
+ receivedInfo: { ...serverInfo }
436
+ });
437
+ }
438
+ if (serverInfo.chainId.startsWith("solana:")) {
439
+ return mcpErrorJson({
440
+ message: "Solana authentication not supported",
441
+ chainId: serverInfo.chainId,
442
+ hint: "This endpoint requires a Solana wallet. The MCP server currently only supports EVM wallets."
443
+ });
444
+ }
445
+ const payloadResult = await safeCreateSIWxPayload(
446
+ toolName2,
447
+ serverInfo,
448
+ account
449
+ );
450
+ if (payloadResult.isErr()) {
451
+ return mcpError(payloadResult);
452
+ }
453
+ const siwxHeader = encodeSIWxHeader(payloadResult.value);
454
+ const authedRequest = buildRequest(input);
455
+ authedRequest.headers.set("SIGN-IN-WITH-X", siwxHeader);
456
+ const authedResult = await safeFetch(toolName2, authedRequest);
457
+ if (authedResult.isErr()) {
458
+ return mcpError(authedResult);
459
+ }
460
+ const authedResponse = authedResult.value;
461
+ if (!authedResponse.ok) {
462
+ return mcpErrorFetch(toolName2, authedResponse);
463
+ }
464
+ const parseResponseResult = await safeParseResponse(
465
+ toolName2,
466
+ authedResponse
467
+ );
468
+ if (parseResponseResult.isErr()) {
469
+ return mcpError(parseResponseResult);
470
+ }
471
+ return mcpSuccessResponse(parseResponseResult.value, {
472
+ authentication: {
473
+ address: account.address,
474
+ domain: serverInfo.domain,
475
+ chainId: serverInfo.chainId
476
+ }
477
+ });
478
+ }
479
+ );
480
+ };
481
+
482
+ // src/server/tools/wallet.ts
483
+ var toolName3 = "getWalletInfo";
484
+ var registerWalletTools = ({
485
+ server,
486
+ account: { address },
487
+ flags
488
+ }) => {
489
+ server.registerTool(
490
+ toolName3,
491
+ {
492
+ description: "Check wallet address and USDC balance. Creates wallet if needed."
493
+ },
494
+ async () => {
495
+ const balanceResult = await getBalance({
496
+ address,
497
+ flags,
498
+ surface: toolName3
499
+ });
500
+ if (balanceResult.isErr()) {
501
+ return mcpError(balanceResult);
502
+ }
503
+ const { balance } = balanceResult.value;
504
+ return mcpSuccessJson({
505
+ address,
506
+ network: DEFAULT_NETWORK,
507
+ networkName: getChainName(DEFAULT_NETWORK),
508
+ usdcBalance: balance,
509
+ isNewWallet: balance === 0,
510
+ depositLink: getDepositLink(address, flags),
511
+ ...balance < 2.5 ? {
512
+ message: `Your balance is low. Consider topping it up`
513
+ } : {}
514
+ });
515
+ }
516
+ );
517
+ };
518
+
519
+ // src/server/tools/check-endpoint.ts
520
+ import { x402Client as x402Client3, x402HTTPClient as x402HTTPClient3 } from "@x402/core/client";
521
+ var toolName4 = "checkEndpointSchema";
522
+ var registerCheckX402EndpointTool = ({ server }) => {
523
+ server.registerTool(
524
+ toolName4,
525
+ {
526
+ description: "Check if an endpoint is x402-protected and get pricing options, schema, and auth requirements (if applicable).",
527
+ inputSchema: requestSchema
528
+ },
529
+ async (input) => {
530
+ log.info("Querying endpoint", input);
531
+ const responseResult = await safeFetch(toolName4, buildRequest(input));
532
+ if (responseResult.isErr()) {
533
+ return mcpError(responseResult);
534
+ }
535
+ const response = responseResult.value;
536
+ if (response.status !== 402) {
537
+ if (!response.ok) {
538
+ return mcpErrorFetch(toolName4, response);
539
+ }
540
+ const parseResponseResult = await safeParseResponse(toolName4, response);
541
+ if (parseResponseResult.isErr()) {
542
+ return mcpError(parseResponseResult);
543
+ }
544
+ return mcpSuccessResponse(parseResponseResult.value, {
545
+ requiresPayment: false
546
+ });
547
+ }
548
+ const client = new x402HTTPClient3(new x402Client3());
549
+ const paymentRequiredResult = await safeGetPaymentRequired(
550
+ toolName4,
551
+ client,
552
+ response
553
+ );
554
+ if (paymentRequiredResult.isErr()) {
555
+ return mcpError(paymentRequiredResult);
556
+ }
557
+ const { resource, extensions, accepts } = paymentRequiredResult.value;
558
+ return mcpSuccessJson({
559
+ requiresPayment: true,
560
+ statusCode: response.status,
561
+ routeDetails: {
562
+ ...resource,
563
+ schema: getInputSchema(extensions),
564
+ paymentMethods: accepts.map((accept) => ({
565
+ price: tokenStringToNumber(accept.amount),
566
+ network: accept.network,
567
+ asset: accept.asset
568
+ }))
569
+ }
570
+ });
571
+ }
572
+ );
573
+ };
574
+
575
+ // src/server/tools/redeem-invite.ts
576
+ import z2 from "zod";
577
+ var toolName5 = "redeemInvite";
578
+ var registerRedeemInviteTool = ({
579
+ server,
580
+ account: { address },
581
+ flags
582
+ }) => {
583
+ server.registerTool(
584
+ toolName5,
585
+ {
586
+ description: "Redeem an invite code to receive USDC.",
587
+ inputSchema: z2.object({
588
+ code: z2.string().min(1).describe("The invite code")
589
+ })
590
+ },
591
+ async ({ code }) => {
592
+ const result = await redeemInviteCode({
593
+ code,
594
+ dev: flags.dev,
595
+ address,
596
+ surface: toolName5
597
+ });
598
+ if (result.isErr()) {
599
+ return mcpError(result);
600
+ }
601
+ const { amount, txHash } = result.value;
602
+ return mcpSuccessJson({
603
+ redeemed: true,
604
+ amount: `${amount} USDC`,
605
+ txHash
606
+ });
607
+ }
608
+ );
609
+ };
610
+
611
+ // src/server/tools/telemetry.ts
612
+ import z3 from "zod";
613
+ var toolName6 = "reportError";
614
+ var registerTelemetryTools = ({
615
+ server,
616
+ account: { address },
617
+ flags
618
+ }) => {
619
+ server.registerTool(
620
+ toolName6,
621
+ {
622
+ description: "EMERGENCY ONLY. Report critical MCP tool bugs. Do NOT use for normal errors (balance, network, 4xx) - those are recoverable.",
623
+ inputSchema: z3.object({
624
+ tool: z3.string().describe("MCP tool name"),
625
+ resource: z3.string().optional().describe("x402 resource URL"),
626
+ summary: z3.string().describe("1-2 sentence summary"),
627
+ errorMessage: z3.string().describe("Error message"),
628
+ stack: z3.string().optional().describe("Stack trace"),
629
+ fullReport: z3.string().optional().describe("Detailed report with context, logs, repro steps")
630
+ })
631
+ },
632
+ async (input) => {
633
+ log.info("Submitting error report", {
634
+ tool: input.tool,
635
+ resource: input.resource,
636
+ summary: input.summary
637
+ });
638
+ const telemetryResult = await safeFetchJson(
639
+ toolName6,
640
+ new Request(`${getBaseUrl(flags.dev)}/api/telemetry`, {
641
+ method: "POST",
642
+ headers: {
643
+ "Content-Type": "application/json"
644
+ },
645
+ body: JSON.stringify({
646
+ ...input,
647
+ walletAddress: address,
648
+ mcpVersion: MCP_VERSION,
649
+ reportedAt: (/* @__PURE__ */ new Date()).toISOString()
650
+ })
651
+ })
652
+ );
653
+ if (telemetryResult.isErr()) {
654
+ log.error("Failed to submit error report", telemetryResult.error);
655
+ return mcpError(telemetryResult);
656
+ }
657
+ const { reportId } = telemetryResult.value;
658
+ log.info("Error report submitted successfully", {
659
+ reportId
660
+ });
661
+ return mcpSuccessJson({
662
+ submitted: true,
663
+ reportId,
664
+ message: "Error report submitted successfully. The x402scan team will investigate."
665
+ });
666
+ }
667
+ );
668
+ };
669
+
670
+ // src/server/tools/discover-resources.ts
671
+ import { z as z4 } from "zod";
672
+ var toolName7 = "discoverResources";
673
+ var discoveryDocumentSchema = z4.object({
674
+ version: z4.number().refine((v) => v === 1, { message: "version must be 1" }),
675
+ resources: z4.array(z4.url()),
676
+ ownershipProofs: z4.array(z4.string()).optional(),
677
+ instructions: z4.string().optional()
678
+ });
679
+ function registerDiscoveryTools(server) {
680
+ server.registerTool(
681
+ "discover_resources",
682
+ {
683
+ description: `Discover x402-protected resources on an origin. Returns a list of resource URLs.
684
+ Use check_x402_endpoint separately to get detailed pricing/schema info for specific resources.
685
+
686
+ Known default origins with resource packs. Discover if more needed:
687
+ - https://enrichx402.com ->
688
+ People + Org search
689
+ Google Maps (places + locations)
690
+ Grok twitter search
691
+ Exa web search
692
+ Clado linkedin data
693
+ Firecrawl web scrape
694
+ - https://stablestudio.io -> generate images / videos
695
+ `,
696
+ inputSchema: {
697
+ url: z4.url().describe(
698
+ "The origin URL or any URL on the origin to discover resources from"
699
+ )
700
+ }
701
+ },
702
+ async ({ url }) => {
703
+ const origin = URL.canParse(url) ? new URL(url).origin : url;
704
+ const hostname = URL.canParse(origin) ? new URL(origin).hostname : origin;
705
+ log.info(`Discovering resources for origin: ${origin}`);
706
+ const wellKnownUrl = `${origin}/.well-known/x402`;
707
+ log.debug(`Fetching discovery document from: ${wellKnownUrl}`);
708
+ const wellKnownResult = await safeFetchJson(
709
+ toolName7,
710
+ new Request(wellKnownUrl, { headers: { Accept: "application/json" } })
711
+ );
712
+ if (wellKnownResult.isOk()) {
713
+ const parsed = discoveryDocumentSchema.safeParse(wellKnownResult.value);
714
+ if (parsed.success) {
715
+ return mcpSuccessJson({
716
+ found: true,
717
+ origin,
718
+ source: "well-known",
719
+ data: parsed.data
720
+ });
721
+ }
722
+ } else {
723
+ log.info(
724
+ `No well-known x402 discovery document found at ${wellKnownUrl}`
725
+ );
726
+ }
727
+ const dnsQuery = `_x402.${hostname}`;
728
+ log.debug(`Looking up DNS TXT record: ${dnsQuery}`);
729
+ const dnsResult = await safeFetchJson(
730
+ toolName7,
731
+ new Request(
732
+ `https://cloudflare-dns.com/dns-query?name=${encodeURIComponent(dnsQuery)}&type=TXT`,
733
+ { headers: { Accept: "application/dns-json" } }
734
+ )
735
+ );
736
+ if (dnsResult.isOk() && dnsResult.value.Answer && dnsResult.value.Answer.length > 0) {
737
+ const dnsUrl = dnsResult.value.Answer[0].data.replace(/^"|"$/g, "");
738
+ if (URL.canParse(dnsUrl)) {
739
+ const dnsDocResult = await safeFetchJson(
740
+ toolName7,
741
+ new Request(dnsUrl, { headers: { Accept: "application/json" } })
742
+ );
743
+ if (dnsDocResult.isOk()) {
744
+ const parsed = discoveryDocumentSchema.safeParse(
745
+ dnsDocResult.value
746
+ );
747
+ if (parsed.success) {
748
+ return mcpSuccessJson({
749
+ found: true,
750
+ origin,
751
+ source: "dns-txt",
752
+ data: parsed.data
753
+ });
754
+ }
755
+ }
756
+ } else {
757
+ log.debug(`DNS TXT value is not a valid URL: ${dnsUrl}`);
758
+ }
759
+ } else {
760
+ log.info(`No DNS TXT record found for ${dnsQuery}`);
761
+ }
762
+ const llmsTxtUrl = `${origin}/llms.txt`;
763
+ log.debug(`Fetching llms.txt from: ${llmsTxtUrl}`);
764
+ const llmsResult = await safeFetch(
765
+ toolName7,
766
+ new Request(llmsTxtUrl, { headers: { Accept: "text/plain" } })
767
+ );
768
+ if (llmsResult.isOk()) {
769
+ const parseResult = await safeParseResponse(toolName7, llmsResult.value);
770
+ if (parseResult.isOk() && parseResult.value.type === "text") {
771
+ return mcpSuccessJson({
772
+ found: true,
773
+ origin,
774
+ source: "llms-txt",
775
+ 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.",
776
+ data: parseResult.value
777
+ });
778
+ }
779
+ }
780
+ return mcpErrorJson({
781
+ found: false,
782
+ origin,
783
+ error: "No discovery document found. Tried: .well-known/x402, DNS TXT record, llms.txt"
784
+ });
785
+ }
786
+ );
787
+ }
788
+
789
+ // src/server/resources/origins.ts
790
+ import { x402HTTPClient as x402HTTPClient4 } from "@x402/core/client";
791
+ import { x402Client as x402Client4 } from "@x402/core/client";
792
+
793
+ // src/server/resources/_lib.ts
794
+ var surface = "getWebPageMetadata";
795
+ var getWebPageMetadata = (url) => {
796
+ return safeFetch(surface, new Request(url)).andThen((response) => safeParseResponse(surface, response)).andThen((parsedResponse) => {
797
+ if (parsedResponse.type === "text") {
798
+ return ok(parseMetadataFromResponse(parsedResponse.data));
799
+ }
800
+ return err("user", surface, {
801
+ cause: "invalid_response_type",
802
+ message: "Invalid response type"
803
+ });
804
+ });
805
+ };
806
+ var parseMetadataFromResponse = (html) => {
807
+ const titleMatch = /<title[^>]*>([\s\S]*?)<\/title>/i.exec(html);
808
+ const title = titleMatch ? titleMatch[1].trim().replace(/\s+/g, " ") : null;
809
+ let descriptionMatch = /<meta\s+name=["']description["']\s+content=["']([^"']*)["']/i.exec(html);
810
+ descriptionMatch ??= /<meta\s+property=["']og:description["']\s+content=["']([^"']*)["']/i.exec(
811
+ html
812
+ );
813
+ descriptionMatch ??= /<meta\s+content=["']([^"']*)["']\s+name=["']description["']/i.exec(html);
814
+ descriptionMatch ??= /<meta\s+content=["']([^"']*)["']\s+property=["']og:description["']/i.exec(
815
+ html
816
+ );
817
+ const description = descriptionMatch ? descriptionMatch[1].trim().replace(/\s+/g, " ") : null;
818
+ return {
819
+ title,
820
+ description
821
+ };
822
+ };
823
+
824
+ // src/server/resources/origins.ts
825
+ var origins = ["enrichx402.com"];
826
+ var registerOrigins = async ({ server }) => {
827
+ await Promise.all(
828
+ origins.map(async (origin) => {
829
+ const metadataResult = await getWebPageMetadata(`https://${origin}`);
830
+ const metadata = metadataResult.isOk() ? metadataResult.value : null;
831
+ server.registerResource(
832
+ origin,
833
+ `api://${origin}`,
834
+ {
835
+ title: metadata?.title ?? origin,
836
+ description: metadata?.description ?? "",
837
+ mimeType: "application/json"
838
+ },
839
+ async (uri) => {
840
+ const response = await fetch(
841
+ `${uri.toString().replace("api://", "https://")}/.well-known/x402`
842
+ ).then((response2) => response2.json());
843
+ const resources = await Promise.all(
844
+ response.resources.map(async (resource) => {
845
+ const resourceResponse = await getResourceResponse(
846
+ resource,
847
+ await fetch(resource, {
848
+ method: "POST",
849
+ headers: {
850
+ "Content-Type": "application/json"
851
+ }
852
+ })
853
+ );
854
+ if (resourceResponse) {
855
+ return resourceResponse;
856
+ }
857
+ const getResponse = await getResourceResponse(
858
+ resource,
859
+ await fetch(resource, {
860
+ method: "GET"
861
+ })
862
+ );
863
+ if (getResponse) {
864
+ return getResponse;
865
+ }
866
+ console.error(`Failed to get resource response for ${resource}`);
867
+ return null;
868
+ })
869
+ );
870
+ return {
871
+ contents: [
872
+ {
873
+ uri: origin,
874
+ text: JSON.stringify({
875
+ server: origin,
876
+ name: metadata?.title,
877
+ description: metadata?.description,
878
+ resources: resources.filter(Boolean).map((resource) => {
879
+ if (!resource) return null;
880
+ const schema = getInputSchema(
881
+ resource.paymentRequired?.extensions
882
+ );
883
+ return {
884
+ url: resource.resource,
885
+ schema,
886
+ mimeType: resource.paymentRequired.resource.mimeType
887
+ };
888
+ })
889
+ }),
890
+ mimeType: "application/json"
891
+ }
892
+ ]
893
+ };
894
+ }
895
+ );
896
+ })
897
+ );
898
+ };
899
+ var getResourceResponse = async (resource, response) => {
900
+ const client = new x402HTTPClient4(new x402Client4());
901
+ if (response.status === 402) {
902
+ const paymentRequired = client.getPaymentRequiredResponse(
903
+ (name) => response.headers.get(name),
904
+ JSON.parse(await response.text())
905
+ );
906
+ return {
907
+ paymentRequired,
908
+ resource
909
+ };
910
+ }
911
+ return null;
912
+ };
913
+
914
+ // src/server/index.ts
915
+ var startServer = async (flags) => {
916
+ log.info("Starting x402scan-mcp...");
917
+ const { dev, invite } = flags;
918
+ const walletResult = await getWallet();
919
+ if (walletResult.isErr()) {
920
+ log.error(JSON.stringify(walletResult.error, null, 2));
921
+ console.error(walletResult.error);
922
+ process.exit(1);
923
+ }
924
+ const { account } = walletResult.value;
925
+ const code = invite ?? process.env.INVITE_CODE;
926
+ if (code) {
927
+ await redeemInviteCode({
928
+ code,
929
+ dev,
930
+ address: account.address,
931
+ surface: "startServer"
932
+ });
933
+ }
934
+ const server = new McpServer(
935
+ {
936
+ name: "@x402scan/mcp",
937
+ version: MCP_VERSION,
938
+ websiteUrl: "https://x402scan.com/mcp",
939
+ icons: [{ src: "https://x402scan.com/logo.svg" }]
940
+ },
941
+ {
942
+ capabilities: {
943
+ resources: {
944
+ subscribe: true,
945
+ listChanged: true
946
+ }
947
+ }
948
+ }
949
+ );
950
+ const props = {
951
+ server,
952
+ account,
953
+ flags
954
+ };
955
+ registerFetchX402ResourceTool(props);
956
+ registerAuthTools(props);
957
+ registerWalletTools(props);
958
+ registerCheckX402EndpointTool(props);
959
+ registerRedeemInviteTool(props);
960
+ registerDiscoveryTools(server);
961
+ registerTelemetryTools(props);
962
+ await registerOrigins({ server, flags });
963
+ const transport = new StdioServerTransport();
964
+ await server.connect(transport);
965
+ const shutdown = async () => {
966
+ log.info("Shutting down...");
967
+ await server.close();
968
+ process.exit(0);
969
+ };
970
+ process.on("SIGINT", () => void shutdown());
971
+ process.on("SIGTERM", () => void shutdown());
972
+ };
973
+ export {
974
+ startServer
975
+ };
976
+ //# sourceMappingURL=server-IOVNYPTB.js.map