@x402scan/mcp 0.1.2 → 0.2.1-beta.0

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