opentool 0.7.2 → 0.7.3

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/cli/index.js CHANGED
@@ -8,10 +8,13 @@ import { z } from 'zod';
8
8
  import { createRequire } from 'module';
9
9
  import { fileURLToPath, pathToFileURL } from 'url';
10
10
  import { zodToJsonSchema } from '@alcyone-labs/zod-to-json-schema';
11
+ import 'viem';
12
+ import 'viem/accounts';
13
+ import 'viem/chains';
11
14
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
12
15
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
13
16
  import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
14
- import * as http from 'http';
17
+ import * as http2 from 'http';
15
18
 
16
19
  function resolveTsconfig(projectRoot) {
17
20
  const candidate = path5.join(projectRoot, "tsconfig.json");
@@ -88,23 +91,45 @@ var McpAnnotationsSchema = z.object({
88
91
  openWorldHint: z.boolean().optional(),
89
92
  requiresPayment: z.boolean().optional()
90
93
  }).strict();
91
- var PaymentConfigSchema = z.object({
92
- amountUSDC: z.number().nonnegative().optional(),
93
- description: z.string().optional(),
94
- x402: z.boolean().optional(),
95
- plain402: z.boolean().optional(),
96
- acceptedMethods: z.array(z.union([z.literal("x402"), z.literal("402")])).optional(),
97
- acceptedCurrencies: z.array(z.string()).optional(),
98
- chains: z.array(z.union([z.string(), z.number()])).optional(),
99
- facilitator: z.string().optional()
100
- }).strict();
94
+ var X402PaymentSchema = z.object({
95
+ definition: z.object({
96
+ amount: z.string(),
97
+ currency: z.object({
98
+ code: z.string(),
99
+ symbol: z.string(),
100
+ decimals: z.number()
101
+ }),
102
+ asset: z.object({
103
+ symbol: z.string(),
104
+ network: z.string(),
105
+ address: z.string(),
106
+ decimals: z.number()
107
+ }),
108
+ payTo: z.string(),
109
+ resource: z.string().optional(),
110
+ description: z.string().optional(),
111
+ scheme: z.string(),
112
+ network: z.string(),
113
+ facilitator: z.object({
114
+ url: z.string(),
115
+ verifyPath: z.string().optional(),
116
+ settlePath: z.string().optional(),
117
+ apiKeyHeader: z.string().optional()
118
+ }),
119
+ metadata: z.record(z.string(), z.unknown()).optional()
120
+ }),
121
+ metadata: z.record(z.string(), z.unknown()).optional()
122
+ }).passthrough();
123
+ var PaymentConfigSchema = z.union([
124
+ X402PaymentSchema,
125
+ z.record(z.string(), z.unknown())
126
+ ]);
101
127
  var DiscoveryMetadataSchema = z.object({
102
128
  keywords: z.array(z.string()).optional(),
103
129
  category: z.string().optional(),
104
130
  useCases: z.array(z.string()).optional(),
105
131
  capabilities: z.array(z.string()).optional(),
106
132
  requirements: z.record(z.string(), z.any()).optional(),
107
- pricing: z.record(z.string(), z.any()).optional(),
108
133
  compatibility: z.record(z.string(), z.any()).optional(),
109
134
  documentation: z.union([z.string(), z.array(z.string())]).optional()
110
135
  }).catchall(z.any());
@@ -125,7 +150,7 @@ var ToolSchema = z.object({
125
150
  discovery: DiscoveryMetadataSchema.optional(),
126
151
  chains: z.array(z.union([z.string(), z.number()])).optional()
127
152
  }).strict();
128
- var AuthoredMetadataSchema = z.object({
153
+ var MetadataSchema = z.object({
129
154
  metadataSpecVersion: z.string().optional(),
130
155
  name: z.string().optional(),
131
156
  displayName: z.string().optional(),
@@ -149,11 +174,10 @@ var AuthoredMetadataSchema = z.object({
149
174
  useCases: z.array(z.string()).optional(),
150
175
  capabilities: z.array(z.string()).optional(),
151
176
  requirements: z.record(z.string(), z.any()).optional(),
152
- pricing: z.record(z.string(), z.any()).optional(),
153
177
  compatibility: z.record(z.string(), z.any()).optional(),
154
178
  chains: z.array(z.union([z.string(), z.number()])).optional()
155
179
  }).catchall(z.any());
156
- var MetadataSchema = z.object({
180
+ var BuildMetadataSchema = z.object({
157
181
  metadataSpecVersion: z.string().default(METADATA_SPEC_VERSION),
158
182
  name: z.string(),
159
183
  displayName: z.string(),
@@ -189,7 +213,7 @@ async function importFresh(modulePath) {
189
213
 
190
214
  // src/cli/shared/metadata.ts
191
215
  var METADATA_ENTRY = "metadata.ts";
192
- async function loadAuthoredMetadata(projectRoot) {
216
+ async function loadMetadata(projectRoot) {
193
217
  const absPath = path5.join(projectRoot, METADATA_ENTRY);
194
218
  if (!fs4.existsSync(absPath)) {
195
219
  throw new Error(
@@ -210,7 +234,7 @@ async function loadAuthoredMetadata(projectRoot) {
210
234
  const compiledPath = resolveCompiledPath(outDir, METADATA_ENTRY);
211
235
  const moduleExports = await importFresh(compiledPath);
212
236
  const metadataExport = extractMetadataExport(moduleExports);
213
- const parsed = AuthoredMetadataSchema.parse(metadataExport);
237
+ const parsed = MetadataSchema.parse(metadataExport);
214
238
  return { metadata: parsed, sourcePath: absPath };
215
239
  } finally {
216
240
  cleanup();
@@ -251,7 +275,7 @@ function readPackageJson(projectRoot) {
251
275
  async function buildMetadataArtifact(options) {
252
276
  const projectRoot = options.projectRoot;
253
277
  const packageInfo = readPackageJson(projectRoot);
254
- const { metadata: authored, sourcePath } = await loadAuthoredMetadata(projectRoot);
278
+ const { metadata: authored, sourcePath } = await loadMetadata(projectRoot);
255
279
  const defaultsApplied = [];
256
280
  const folderName = path5.basename(projectRoot);
257
281
  const name = resolveField(
@@ -296,7 +320,7 @@ async function buildMetadataArtifact(options) {
296
320
  if (!authored.website && packageInfo.homepage) {
297
321
  defaultsApplied.push("website \u2192 package.json homepage");
298
322
  }
299
- const payment = resolvePayment(authored, defaultsApplied);
323
+ const payment = resolvePayment(authored);
300
324
  const baseImage = authored.image ?? authored.iconPath;
301
325
  const animation = authored.animation_url ?? authored.videoPath;
302
326
  const discovery = buildDiscovery(authored);
@@ -329,7 +353,7 @@ async function buildMetadataArtifact(options) {
329
353
  }
330
354
  return toolDefinition;
331
355
  });
332
- const metadata = MetadataSchema.parse({
356
+ const metadata = BuildMetadataSchema.parse({
333
357
  metadataSpecVersion: authored.metadataSpecVersion ?? METADATA_SPEC_VERSION,
334
358
  name,
335
359
  displayName,
@@ -385,32 +409,8 @@ function extractRepository(repository) {
385
409
  }
386
410
  return repository.url;
387
411
  }
388
- function resolvePayment(authored, defaults) {
389
- if (authored.payment) {
390
- return authored.payment;
391
- }
392
- const discoveryPricing = authored.discovery?.pricing;
393
- const legacyPricing = authored.pricing;
394
- const pricing = discoveryPricing ?? legacyPricing;
395
- if (!pricing) {
396
- return void 0;
397
- }
398
- const amount = typeof pricing.defaultAmount === "number" ? pricing.defaultAmount : 0;
399
- const sourceLabel = discoveryPricing ? "discovery.pricing.defaultAmount" : "pricing.defaultAmount";
400
- defaults.push(`payment \u2192 synthesized from ${sourceLabel}`);
401
- const acceptedMethodsRaw = Array.isArray(pricing.acceptedMethods) ? pricing.acceptedMethods : ["402"];
402
- const acceptedMethods = acceptedMethodsRaw.map(
403
- (method) => method === "x402" ? "x402" : "402"
404
- );
405
- return {
406
- amountUSDC: amount,
407
- description: typeof pricing.description === "string" ? pricing.description : void 0,
408
- x402: acceptedMethods.includes("x402"),
409
- plain402: acceptedMethods.includes("402"),
410
- acceptedMethods,
411
- acceptedCurrencies: Array.isArray(pricing.acceptedCurrencies) ? pricing.acceptedCurrencies : ["USDC"],
412
- chains: Array.isArray(pricing.chains) ? pricing.chains : [8453]
413
- };
412
+ function resolvePayment(authored, _defaults) {
413
+ return authored.payment ?? void 0;
414
414
  }
415
415
  function buildDiscovery(authored) {
416
416
  const legacyDiscovery = {};
@@ -432,145 +432,14 @@ function buildDiscovery(authored) {
432
432
  if (Array.isArray(authored.categories) && authored.categories.length > 0) {
433
433
  legacyDiscovery.category = authored.categories[0];
434
434
  }
435
- if (authored.pricing) {
436
- legacyDiscovery.pricing = authored.pricing;
437
- }
438
435
  const merged = {
439
436
  ...legacyDiscovery,
440
437
  ...authored.discovery ?? {}
441
438
  };
442
439
  return Object.keys(merged).length > 0 ? merged : void 0;
443
440
  }
444
- var PAYMENT_SCHEMA_VERSION = 1;
445
- var paymentSchemaVersionSchema = z.literal(PAYMENT_SCHEMA_VERSION);
446
- var decimalStringSchema = z.string().regex(/^(?:0|[1-9]\d*)(?:\.\d+)?$/, "Value must be a positive decimal string");
447
- var currencySchema = z.object({
448
- code: z.string().min(2).max(12).transform((value) => value.toUpperCase()),
449
- symbol: z.string().min(1).max(6).optional(),
450
- decimals: z.number().int().min(0).max(36).optional(),
451
- kind: z.enum(["fiat", "crypto"]).default("crypto").optional(),
452
- description: z.string().optional()
453
- });
454
- var paymentAmountSchema = z.object({
455
- value: decimalStringSchema,
456
- currency: currencySchema,
457
- display: z.string().optional()
458
- });
459
- var cryptoAssetSchema = z.object({
460
- symbol: z.string().min(2).max(12),
461
- network: z.string().min(1).optional(),
462
- chainId: z.number().int().min(0).optional(),
463
- address: z.string().min(1).optional(),
464
- decimals: z.number().int().min(0).max(36).optional(),
465
- standard: z.enum(["erc20", "spl", "custom"]).default("erc20").optional(),
466
- description: z.string().optional()
467
- });
468
- var facilitatorConfigSchema = z.object({
469
- url: z.string().url(),
470
- vendor: z.string().optional(),
471
- verifyPath: z.string().default("/verify").optional(),
472
- settlePath: z.string().default("/settle").optional(),
473
- apiKey: z.string().optional(),
474
- apiKeyEnv: z.string().optional(),
475
- apiKeyHeader: z.string().default("Authorization").optional(),
476
- headers: z.record(z.string(), z.string()).optional(),
477
- timeoutMs: z.number().int().positive().optional()
478
- });
479
- var settlementTermsSchema = z.object({
480
- windowSeconds: z.number().int().positive().optional(),
481
- targetConfirmations: z.number().int().positive().optional(),
482
- finalityDescription: z.string().optional(),
483
- slaDescription: z.string().optional()
484
- });
485
- var paymentFieldSchema = z.object({
486
- key: z.string().min(1),
487
- label: z.string().min(1),
488
- required: z.boolean().default(true).optional(),
489
- description: z.string().optional(),
490
- example: z.string().optional()
491
- });
492
- var x402ProofSchema = z.object({
493
- mode: z.literal("x402"),
494
- scheme: z.string().min(1),
495
- network: z.string().min(1),
496
- version: z.number().int().min(1).optional(),
497
- facilitator: facilitatorConfigSchema.optional(),
498
- verifier: z.string().optional()
499
- });
500
- var directProofSchema = z.object({
501
- mode: z.literal("direct"),
502
- proofTypes: z.array(z.string().min(1)).nonempty(),
503
- verifier: z.string().optional(),
504
- instructions: z.string().optional(),
505
- fields: z.array(paymentFieldSchema).optional(),
506
- allowsManualReview: z.boolean().optional()
507
- });
508
- var paymentProofSchema = z.discriminatedUnion("mode", [
509
- x402ProofSchema,
510
- directProofSchema
511
- ]);
512
- var paymentOptionSchema = z.object({
513
- id: z.string().min(1),
514
- title: z.string().min(1),
515
- description: z.string().optional(),
516
- amount: paymentAmountSchema,
517
- asset: cryptoAssetSchema,
518
- payTo: z.string().min(1),
519
- resource: z.string().url().optional(),
520
- proof: paymentProofSchema,
521
- settlement: settlementTermsSchema.optional(),
522
- metadata: z.record(z.string(), z.unknown()).optional()
523
- });
524
- var paymentRequirementsSchema = z.object({
525
- schemaVersion: paymentSchemaVersionSchema,
526
- message: z.string().optional(),
527
- title: z.string().optional(),
528
- resource: z.string().url().optional(),
529
- accepts: z.array(paymentOptionSchema).nonempty(),
530
- metadata: z.record(z.string(), z.unknown()).optional(),
531
- fallbackText: z.string().optional()
532
- });
533
- var x402PaymentHeaderSchema = z.object({
534
- x402Version: z.number().int().min(1),
535
- scheme: z.string().min(1),
536
- network: z.string().min(1),
537
- payload: z.unknown(),
538
- correlationId: z.string().optional()
539
- });
540
- var directPaymentPayloadSchema = z.object({
541
- schemaVersion: z.literal(1),
542
- optionId: z.string().min(1),
543
- proofType: z.string().min(1),
544
- payload: z.unknown(),
545
- metadata: z.record(z.string(), z.unknown()).optional()
546
- });
547
- var paymentSuccessMetadataSchema = z.object({
548
- optionId: z.string().min(1),
549
- verifier: z.string().optional(),
550
- txHash: z.string().optional(),
551
- networkId: z.string().optional(),
552
- amount: paymentAmountSchema.optional(),
553
- settledAt: z.string().datetime().optional(),
554
- payload: z.unknown().optional()
555
- });
556
- var paymentFailureSchema = z.object({
557
- reason: z.string().min(1),
558
- code: z.enum([
559
- "verifier_not_found",
560
- "verification_failed",
561
- "invalid_payload",
562
- "unsupported_option",
563
- "missing_header",
564
- "unknown"
565
- ]).default("unknown").optional(),
566
- retryable: z.boolean().optional(),
567
- detail: z.unknown().optional()
568
- });
569
-
570
- // src/helpers/payment.ts
571
- var X402_VERSION_DEFAULT = 1;
441
+ var X402_VERSION = 1;
572
442
  var HEADER_X402 = "X-PAYMENT";
573
- var HEADER_DIRECT = "X-PAYMENT-PROOF";
574
443
  var HEADER_PAYMENT_RESPONSE = "X-PAYMENT-RESPONSE";
575
444
  var x402RequirementSchema = z.object({
576
445
  scheme: z.string().min(1),
@@ -585,272 +454,195 @@ var x402RequirementSchema = z.object({
585
454
  maxTimeoutSeconds: z.number().int().positive().optional(),
586
455
  extra: z.record(z.string(), z.unknown()).nullable().optional()
587
456
  });
588
- function createPaymentRequiredBody(definition) {
589
- const parsed = paymentRequirementsSchema.parse(definition);
590
- const x402Accepts = parsed.accepts.filter((option) => option.proof.mode === "x402").map(
591
- (option) => toX402Requirement(option, parsed.resource, option.settlement)
592
- ).filter((value) => Boolean(value));
593
- const x402Body = x402Accepts.length > 0 ? {
594
- x402Version: resolveX402Version(parsed.accepts),
595
- error: parsed.message ?? "Payment required",
596
- accepts: x402Accepts
597
- } : void 0;
598
- if (x402Body) {
599
- return {
600
- ...parsed,
601
- x402: x402Body
602
- };
603
- }
604
- return parsed;
605
- }
606
- function paymentRequiredResponse(definition, init) {
607
- const body = createPaymentRequiredBody(definition);
608
- const headers = new Headers(init?.headers);
609
- if (!headers.has("content-type")) {
610
- headers.set("content-type", "application/json; charset=utf-8");
611
- }
457
+ var x402PaymentHeaderSchema = z.object({
458
+ x402Version: z.number().int().positive(),
459
+ scheme: z.string().min(1),
460
+ network: z.string().min(1),
461
+ correlationId: z.string().optional(),
462
+ payload: z.unknown()
463
+ });
464
+
465
+ // src/x402/helpers.ts
466
+ function createX402PaymentRequired(definition) {
467
+ const requirement = toX402Requirement(definition);
468
+ const body = {
469
+ schemaVersion: 1,
470
+ message: definition.description ?? "Payment required",
471
+ resource: definition.resource,
472
+ accepts: [
473
+ {
474
+ id: "x402",
475
+ title: `Pay ${definition.amount} ${definition.currency.code}`,
476
+ description: definition.description,
477
+ amount: {
478
+ value: definition.amount,
479
+ currency: {
480
+ code: definition.currency.code,
481
+ symbol: definition.currency.symbol,
482
+ decimals: definition.currency.decimals,
483
+ kind: "crypto"
484
+ }
485
+ },
486
+ asset: {
487
+ symbol: definition.asset.symbol,
488
+ network: definition.asset.network,
489
+ address: definition.asset.address,
490
+ decimals: definition.asset.decimals,
491
+ standard: "erc20"
492
+ },
493
+ payTo: definition.payTo,
494
+ resource: definition.resource,
495
+ proof: {
496
+ mode: "x402",
497
+ scheme: definition.scheme,
498
+ network: definition.network,
499
+ version: X402_VERSION,
500
+ facilitator: definition.facilitator,
501
+ verifier: "x402:facilitator"
502
+ }
503
+ }
504
+ ],
505
+ metadata: definition.metadata ?? {},
506
+ x402: {
507
+ x402Version: X402_VERSION,
508
+ error: definition.description ?? "Payment required",
509
+ accepts: [requirement]
510
+ }
511
+ };
612
512
  return new Response(JSON.stringify(body), {
613
- ...init,
614
513
  status: 402,
615
- headers
514
+ headers: {
515
+ "Content-Type": "application/json"
516
+ }
616
517
  });
617
518
  }
618
- function extractPaymentAttempts(source) {
619
- const attempts = [];
620
- const failures = [];
621
- const x402Header = source.headers.get(HEADER_X402);
622
- if (x402Header) {
623
- const { attempt, failure } = parseX402Header(x402Header);
624
- if (attempt) {
625
- attempts.push(attempt);
626
- } else if (failure) {
627
- failures.push(failure);
628
- }
629
- }
630
- const directHeader = source.headers.get(HEADER_DIRECT);
631
- if (directHeader) {
632
- const { attempt, failure } = parseDirectHeader(directHeader);
633
- if (attempt) {
634
- attempts.push(attempt);
635
- } else if (failure) {
636
- failures.push(failure);
637
- }
638
- }
639
- if (attempts.length === 0 && failures.length === 0) {
640
- failures.push(
641
- paymentFailureSchema.parse({
642
- reason: "No payment headers present",
643
- code: "missing_header",
644
- retryable: false
645
- })
646
- );
519
+ function extractX402Attempt(request) {
520
+ const raw = request.headers.get(HEADER_X402);
521
+ if (!raw) {
522
+ return null;
647
523
  }
648
- return { attempts, failures };
649
- }
650
- async function verifyPayment(options) {
651
- const definition = paymentRequirementsSchema.parse(options.definition);
652
- const attempts = options.attempts ? options.attempts : options.request ? extractPaymentAttempts(options.request).attempts : [];
653
- if (attempts.length === 0) {
524
+ try {
525
+ const payload = decodeJson(raw, x402PaymentHeaderSchema);
654
526
  return {
655
- success: false,
656
- optionId: "",
657
- attemptType: "direct",
658
- failure: paymentFailureSchema.parse({
659
- reason: "No payment attempt found",
660
- code: "missing_header",
661
- retryable: false
662
- })
527
+ type: "x402",
528
+ headerName: HEADER_X402,
529
+ raw,
530
+ payload
663
531
  };
532
+ } catch {
533
+ return null;
664
534
  }
665
- for (const attempt of attempts) {
666
- const option = findMatchingOption(definition, attempt);
667
- if (!option) {
668
- continue;
535
+ }
536
+ async function verifyX402Payment(attempt, definition, options = {}) {
537
+ const fetchImpl = options.fetchImpl ?? fetch;
538
+ const facilitator = definition.facilitator;
539
+ const verifierUrl = new URL(
540
+ facilitator.verifyPath ?? "/verify",
541
+ ensureTrailingSlash(facilitator.url)
542
+ ).toString();
543
+ const requirement = toX402Requirement(definition);
544
+ const headers = buildFacilitatorHeaders(facilitator);
545
+ try {
546
+ const verifyResponse = await fetchImpl(verifierUrl, {
547
+ method: "POST",
548
+ headers,
549
+ body: JSON.stringify({
550
+ x402Version: attempt.payload.x402Version,
551
+ paymentPayload: attempt.payload,
552
+ paymentRequirements: requirement
553
+ })
554
+ });
555
+ if (!verifyResponse.ok) {
556
+ return {
557
+ success: false,
558
+ failure: {
559
+ reason: `Facilitator verify request failed: ${verifyResponse.status}`,
560
+ code: "verification_failed"
561
+ }
562
+ };
669
563
  }
670
- if (attempt.type === "x402") {
671
- const proof = option.proof;
672
- const verifierId = proof.verifier ?? (proof.facilitator ? "x402:facilitator" : void 0);
673
- if (verifierId === "x402:facilitator" && proof.facilitator) {
674
- const context2 = {
675
- attempt,
676
- option,
677
- definition
678
- };
679
- if (options.settle !== void 0) {
680
- context2.settle = options.settle;
564
+ const verifyPayload = await verifyResponse.json();
565
+ if (!verifyPayload.isValid) {
566
+ return {
567
+ success: false,
568
+ failure: {
569
+ reason: verifyPayload.invalidReason ?? "Facilitator verification failed",
570
+ code: "verification_failed"
681
571
  }
682
- return runFacilitatorVerifier({
683
- ...context2,
684
- fetchImpl: options.fetchImpl ?? fetch
685
- });
686
- }
687
- const verifier = verifierId ? options.verifiers?.[verifierId] : void 0;
688
- if (!verifier) {
689
- return {
690
- success: false,
691
- optionId: option.id,
692
- attemptType: attempt.type,
693
- failure: paymentFailureSchema.parse({
694
- reason: `No verifier registered for id: ${verifierId ?? "(missing)"}`,
695
- code: "verifier_not_found",
696
- retryable: false
697
- })
698
- };
699
- }
700
- const context = {
701
- attempt,
702
- option,
703
- definition
704
572
  };
705
- if (options.settle !== void 0) {
706
- context.settle = options.settle;
707
- }
708
- return verifier(context);
709
573
  }
710
- if (attempt.type === "direct") {
711
- const proof = option.proof;
712
- const verifierId = proof.verifier ?? `direct:${attempt.payload.proofType}`;
713
- const verifier = verifierId ? options.verifiers?.[verifierId] : void 0;
714
- if (!verifier) {
715
- return {
716
- success: false,
717
- optionId: option.id,
718
- attemptType: attempt.type,
719
- failure: paymentFailureSchema.parse({
720
- reason: `No verifier registered for id: ${verifierId}`,
721
- code: "verifier_not_found",
722
- retryable: false
574
+ const responseHeaders = {};
575
+ if (options.settle) {
576
+ const settleUrl = new URL(
577
+ facilitator.settlePath ?? "/settle",
578
+ ensureTrailingSlash(facilitator.url)
579
+ ).toString();
580
+ try {
581
+ const settleResponse = await fetchImpl(settleUrl, {
582
+ method: "POST",
583
+ headers,
584
+ body: JSON.stringify({
585
+ x402Version: attempt.payload.x402Version,
586
+ paymentPayload: attempt.payload,
587
+ paymentRequirements: requirement
723
588
  })
724
- };
725
- }
726
- const context = {
727
- attempt,
728
- option,
729
- definition
730
- };
731
- if (options.settle !== void 0) {
732
- context.settle = options.settle;
589
+ });
590
+ if (settleResponse.ok) {
591
+ const settlePayload = await settleResponse.json();
592
+ if (settlePayload.txHash) {
593
+ responseHeaders[HEADER_PAYMENT_RESPONSE] = JSON.stringify({
594
+ settled: true,
595
+ txHash: settlePayload.txHash
596
+ });
597
+ }
598
+ }
599
+ } catch {
733
600
  }
734
- return verifier(context);
735
601
  }
736
- }
737
- return {
738
- success: false,
739
- optionId: "",
740
- attemptType: attempts[0]?.type ?? "direct",
741
- failure: paymentFailureSchema.parse({
742
- reason: "No matching payment option for attempt",
743
- code: "unsupported_option",
744
- retryable: false
745
- })
746
- };
747
- }
748
- function createPaymentResponseHeader(metadata) {
749
- const parsed = paymentSuccessMetadataSchema.parse(metadata);
750
- return encodeJson(parsed);
751
- }
752
- function parseX402Header(value) {
753
- try {
754
- const payload = decodeJson(value, x402PaymentHeaderSchema);
755
- return {
756
- attempt: {
757
- type: "x402",
758
- headerName: HEADER_X402,
759
- raw: value,
760
- payload
602
+ const result = {
603
+ success: true,
604
+ metadata: {
605
+ optionId: "x402",
606
+ verifier: "x402:facilitator",
607
+ amount: definition.amount,
608
+ currency: definition.currency.code,
609
+ network: definition.network
761
610
  }
762
611
  };
612
+ if (Object.keys(responseHeaders).length > 0) {
613
+ result.responseHeaders = responseHeaders;
614
+ }
615
+ return result;
763
616
  } catch (error) {
764
617
  return {
765
- failure: paymentFailureSchema.parse({
766
- reason: `Invalid X-PAYMENT header: ${error.message}`,
767
- code: "invalid_payload",
768
- retryable: false
769
- })
770
- };
771
- }
772
- }
773
- function parseDirectHeader(value) {
774
- try {
775
- const payload = decodeJson(value, directPaymentPayloadSchema);
776
- return {
777
- attempt: {
778
- type: "direct",
779
- headerName: HEADER_DIRECT,
780
- raw: value,
781
- payload
618
+ success: false,
619
+ failure: {
620
+ reason: error instanceof Error ? error.message : "Unknown error",
621
+ code: "verification_failed"
782
622
  }
783
623
  };
784
- } catch (error) {
785
- return {
786
- failure: paymentFailureSchema.parse({
787
- reason: `Invalid X-PAYMENT-PROOF header: ${error.message}`,
788
- code: "invalid_payload",
789
- retryable: false
790
- })
791
- };
792
624
  }
793
625
  }
794
- function findMatchingOption(definition, attempt) {
795
- return definition.accepts.find((candidate) => {
796
- const option = paymentOptionSchema.parse(candidate);
797
- if (attempt.type === "x402" && option.proof.mode === "x402") {
798
- return option.proof.scheme === attempt.payload.scheme && option.proof.network === attempt.payload.network;
799
- }
800
- if (attempt.type === "direct" && option.proof.mode === "direct") {
801
- return option.id === attempt.payload.optionId;
802
- }
803
- return false;
804
- });
805
- }
806
- function resolveX402Version(options) {
807
- const versions = [];
808
- for (const option of options) {
809
- if (option.proof.mode === "x402" && option.proof.version) {
810
- versions.push(option.proof.version);
811
- }
812
- }
813
- return versions.length > 0 ? Math.max(...versions) : X402_VERSION_DEFAULT;
814
- }
815
- function toX402Requirement(option, fallbackResource, settlement) {
816
- if (option.proof.mode !== "x402") {
817
- return void 0;
818
- }
819
- const decimals = resolveDecimals(option);
820
- const assetAddress = option.asset.address;
821
- if (!assetAddress) {
822
- throw new Error(
823
- `x402 payment option '${option.id}' is missing asset.address`
824
- );
825
- }
826
- const units = decimalToBaseUnits(option.amount.value, decimals);
626
+ function toX402Requirement(definition) {
627
+ const decimals = definition.asset.decimals;
628
+ const units = decimalToBaseUnits(definition.amount, decimals);
827
629
  return x402RequirementSchema.parse({
828
- scheme: option.proof.scheme,
829
- network: option.proof.network,
630
+ scheme: definition.scheme,
631
+ network: definition.network,
830
632
  maxAmountRequired: units,
831
- asset: assetAddress,
832
- payTo: option.payTo,
833
- resource: option.resource ?? fallbackResource,
834
- description: option.description,
835
- maxTimeoutSeconds: settlement?.windowSeconds,
633
+ asset: definition.asset.address,
634
+ payTo: definition.payTo,
635
+ resource: definition.resource,
636
+ description: definition.description,
637
+ mimeType: "application/json",
638
+ maxTimeoutSeconds: 900,
836
639
  extra: {
837
- symbol: option.asset.symbol,
838
- currencyCode: option.amount.currency.code,
640
+ symbol: definition.asset.symbol,
641
+ currencyCode: definition.currency.code,
839
642
  decimals
840
643
  }
841
644
  });
842
645
  }
843
- function resolveDecimals(option) {
844
- if (typeof option.asset.decimals === "number") {
845
- return option.asset.decimals;
846
- }
847
- if (typeof option.amount.currency.decimals === "number") {
848
- return option.amount.currency.decimals;
849
- }
850
- throw new Error(
851
- `Payment option '${option.id}' must specify asset.decimals or currency.decimals`
852
- );
853
- }
854
646
  function decimalToBaseUnits(value, decimals) {
855
647
  const [whole, fraction = ""] = value.split(".");
856
648
  const sanitizedFraction = fraction.slice(0, decimals);
@@ -864,10 +656,6 @@ function decodeJson(value, schema) {
864
656
  const parsed = JSON.parse(json);
865
657
  return schema.parse(parsed);
866
658
  }
867
- function encodeJson(value) {
868
- const json = JSON.stringify(value);
869
- return Buffer.from(json, "utf-8").toString("base64");
870
- }
871
659
  function normalizeBase64(input) {
872
660
  if (/^[A-Za-z0-9+/=]+$/.test(input)) {
873
661
  return input;
@@ -876,200 +664,25 @@ function normalizeBase64(input) {
876
664
  const paddingNeeded = (4 - restored.length % 4) % 4;
877
665
  return restored + "=".repeat(paddingNeeded);
878
666
  }
879
- async function runFacilitatorVerifier({
880
- attempt,
881
- option,
882
- definition,
883
- settle,
884
- fetchImpl
885
- }) {
886
- if (option.proof.mode !== "x402" || attempt.type !== "x402" || !option.proof.facilitator) {
887
- return {
888
- success: false,
889
- optionId: option.id,
890
- attemptType: attempt.type,
891
- failure: paymentFailureSchema.parse({
892
- reason: "Facilitator verifier invoked for non-x402 option",
893
- code: "verification_failed",
894
- retryable: false
895
- })
896
- };
897
- }
898
- const facilitator = option.proof.facilitator;
899
- const verifierUrl = new URL(
900
- facilitator.verifyPath ?? "/verify",
901
- ensureTrailingSlash(facilitator.url)
902
- ).toString();
903
- const requirement = toX402Requirement(option, definition.resource, option.settlement);
904
- if (!requirement) {
905
- return {
906
- success: false,
907
- optionId: option.id,
908
- attemptType: attempt.type,
909
- failure: paymentFailureSchema.parse({
910
- reason: "Unable to derive x402 requirement for facilitator",
911
- code: "verification_failed",
912
- retryable: false
913
- })
914
- };
915
- }
916
- const headers = buildFacilitatorHeaders(facilitator);
917
- const controller = facilitator.timeoutMs ? new AbortController() : void 0;
918
- const timeout = facilitator.timeoutMs ? setTimeout(() => controller?.abort(), facilitator.timeoutMs) : void 0;
919
- try {
920
- const verifyResponse = await fetchImpl(verifierUrl, {
921
- method: "POST",
922
- headers,
923
- body: JSON.stringify({
924
- x402Version: attempt.payload.x402Version,
925
- paymentHeader: attempt.raw,
926
- paymentRequirements: requirement
927
- }),
928
- signal: controller?.signal ?? null
929
- });
930
- if (!verifyResponse.ok) {
931
- return {
932
- success: false,
933
- optionId: option.id,
934
- attemptType: attempt.type,
935
- failure: paymentFailureSchema.parse({
936
- reason: `Facilitator verify request failed: ${verifyResponse.status}`,
937
- code: "verification_failed",
938
- retryable: verifyResponse.status >= 500
939
- })
940
- };
941
- }
942
- const verifyPayload = await verifyResponse.json();
943
- if (!verifyPayload.isValid) {
944
- return {
945
- success: false,
946
- optionId: option.id,
947
- attemptType: attempt.type,
948
- failure: paymentFailureSchema.parse({
949
- reason: verifyPayload.invalidReason ?? "Facilitator verification failed",
950
- code: "verification_failed",
951
- retryable: false
952
- })
953
- };
954
- }
955
- if (!settle) {
956
- return {
957
- success: true,
958
- optionId: option.id,
959
- attemptType: attempt.type,
960
- metadata: paymentSuccessMetadataSchema.parse({
961
- optionId: option.id,
962
- verifier: facilitator.vendor ?? "facilitator"
963
- })
964
- };
965
- }
966
- const settleUrl = new URL(
967
- facilitator.settlePath ?? "/settle",
968
- ensureTrailingSlash(facilitator.url)
969
- ).toString();
970
- const settleResponse = await fetchImpl(settleUrl, {
971
- method: "POST",
972
- headers,
973
- body: JSON.stringify({
974
- x402Version: attempt.payload.x402Version,
975
- paymentHeader: attempt.raw,
976
- paymentRequirements: requirement
977
- }),
978
- signal: controller?.signal ?? null
979
- });
980
- if (!settleResponse.ok) {
981
- return {
982
- success: false,
983
- optionId: option.id,
984
- attemptType: attempt.type,
985
- failure: paymentFailureSchema.parse({
986
- reason: `Facilitator settle request failed: ${settleResponse.status}`,
987
- code: "verification_failed",
988
- retryable: settleResponse.status >= 500
989
- })
990
- };
991
- }
992
- const settlePayload = await settleResponse.json();
993
- if (!settlePayload.success) {
994
- return {
995
- success: false,
996
- optionId: option.id,
997
- attemptType: attempt.type,
998
- failure: paymentFailureSchema.parse({
999
- reason: settlePayload.error ?? "Facilitator settlement failed",
1000
- code: "verification_failed",
1001
- retryable: false
1002
- })
1003
- };
1004
- }
1005
- const metadata = paymentSuccessMetadataSchema.parse({
1006
- optionId: option.id,
1007
- verifier: facilitator.vendor ?? "facilitator",
1008
- txHash: settlePayload.txHash ?? void 0,
1009
- networkId: settlePayload.networkId ?? void 0
1010
- });
1011
- return {
1012
- success: true,
1013
- optionId: option.id,
1014
- attemptType: attempt.type,
1015
- metadata,
1016
- responseHeaders: {
1017
- [HEADER_PAYMENT_RESPONSE]: createPaymentResponseHeader(metadata)
1018
- }
1019
- };
1020
- } catch (error) {
1021
- return {
1022
- success: false,
1023
- optionId: option.id,
1024
- attemptType: attempt.type,
1025
- failure: paymentFailureSchema.parse({
1026
- reason: `Facilitator request error: ${error.message}`,
1027
- code: "verification_failed",
1028
- retryable: false
1029
- })
1030
- };
1031
- } finally {
1032
- if (timeout) {
1033
- clearTimeout(timeout);
1034
- }
1035
- }
1036
- }
1037
- function buildFacilitatorHeaders(config) {
667
+ function buildFacilitatorHeaders(facilitator) {
1038
668
  const headers = {
1039
- "content-type": "application/json"
669
+ "Content-Type": "application/json"
1040
670
  };
1041
- if (config?.headers) {
1042
- Object.assign(headers, config.headers);
1043
- }
1044
- const apiKey = resolveFacilitatorApiKey(config);
1045
- if (apiKey) {
1046
- const headerName = config?.apiKeyHeader ?? "Authorization";
1047
- headers[headerName] = apiKey;
671
+ if (facilitator.apiKeyHeader && process.env.X402_FACILITATOR_API_KEY) {
672
+ headers[facilitator.apiKeyHeader] = process.env.X402_FACILITATOR_API_KEY;
1048
673
  }
1049
674
  return headers;
1050
675
  }
1051
- function resolveFacilitatorApiKey(config) {
1052
- if (!config) {
1053
- return void 0;
1054
- }
1055
- if (config.apiKey) {
1056
- return config.apiKey;
1057
- }
1058
- if (config.apiKeyEnv && typeof process !== "undefined") {
1059
- return process.env?.[config.apiKeyEnv];
1060
- }
1061
- return void 0;
1062
- }
1063
- function ensureTrailingSlash(value) {
1064
- return value.endsWith("/") ? value : `${value}/`;
676
+ function ensureTrailingSlash(url) {
677
+ return url.endsWith("/") ? url : `${url}/`;
1065
678
  }
1066
679
 
1067
- // src/payment/index.ts
1068
- var PAYMENT_CONTEXT_SYMBOL = Symbol.for("opentool.payment.context");
1069
- var PaymentRequiredError = class extends Error {
680
+ // src/x402/index.ts
681
+ var PAYMENT_CONTEXT_SYMBOL = Symbol.for("opentool.x402.context");
682
+ var X402PaymentRequiredError = class extends Error {
1070
683
  constructor(response, verification) {
1071
- super("Payment required");
1072
- this.name = "PaymentRequiredError";
684
+ super("X402 Payment required");
685
+ this.name = "X402PaymentRequiredError";
1073
686
  this.response = response;
1074
687
  this.verification = verification;
1075
688
  }
@@ -1086,6 +699,45 @@ function setPaymentContext(request, context) {
1086
699
  request[PAYMENT_CONTEXT_SYMBOL] = context;
1087
700
  }
1088
701
  }
702
+ async function requireX402Payment(request, payment, options = {}) {
703
+ const definition = isX402Payment(payment) ? payment.definition : payment;
704
+ const attempt = extractX402Attempt(request);
705
+ if (!attempt) {
706
+ const response = createX402PaymentRequired(definition);
707
+ throw new X402PaymentRequiredError(response);
708
+ }
709
+ const verifyOptions = {};
710
+ if (options.settle !== void 0) {
711
+ verifyOptions.settle = options.settle;
712
+ }
713
+ if (options.fetchImpl !== void 0) {
714
+ verifyOptions.fetchImpl = options.fetchImpl;
715
+ }
716
+ const verification = await verifyX402Payment(attempt, definition, verifyOptions);
717
+ if (!verification.success || !verification.metadata) {
718
+ if (options.onFailure) {
719
+ return options.onFailure(verification);
720
+ }
721
+ const response = createX402PaymentRequired(definition);
722
+ throw new X402PaymentRequiredError(response, verification);
723
+ }
724
+ return {
725
+ payment: verification.metadata,
726
+ headers: verification.responseHeaders ?? {},
727
+ result: verification
728
+ };
729
+ }
730
+ function withX402Payment(handler, payment, options = {}) {
731
+ return async (request) => {
732
+ const verification = await requireX402Payment(request, payment, options);
733
+ if (verification instanceof Response) {
734
+ return verification;
735
+ }
736
+ setPaymentContext(request, verification);
737
+ const response = await Promise.resolve(handler(request));
738
+ return applyPaymentHeaders(response, verification.headers);
739
+ };
740
+ }
1089
741
  function applyPaymentHeaders(response, headers) {
1090
742
  const entries = Object.entries(headers ?? {});
1091
743
  if (entries.length === 0) {
@@ -1108,64 +760,7 @@ function applyPaymentHeaders(response, headers) {
1108
760
  headers: merged
1109
761
  });
1110
762
  }
1111
- function withPaymentRequirement(handler, payment, options = {}) {
1112
- return async (request) => {
1113
- const verification = await requirePayment(request, payment, options);
1114
- if (verification instanceof Response) {
1115
- return verification;
1116
- }
1117
- setPaymentContext(request, verification);
1118
- const response = await Promise.resolve(handler(request));
1119
- return applyPaymentHeaders(response, verification.headers);
1120
- };
1121
- }
1122
- async function requirePayment(request, payment, options = {}) {
1123
- const { definition, verifiers } = normalizePayment(payment);
1124
- const mergedVerifiers = {
1125
- ...verifiers,
1126
- ...options.verifiers ?? {}
1127
- };
1128
- const verifyOptions = {
1129
- definition,
1130
- request
1131
- };
1132
- if (Object.keys(mergedVerifiers).length > 0) {
1133
- verifyOptions.verifiers = mergedVerifiers;
1134
- }
1135
- if (options.settle !== void 0) {
1136
- verifyOptions.settle = options.settle;
1137
- }
1138
- if (options.fetchImpl) {
1139
- verifyOptions.fetchImpl = options.fetchImpl;
1140
- }
1141
- const verification = await verifyPayment(verifyOptions);
1142
- if (!verification.success || !verification.metadata) {
1143
- if (options.onFailure) {
1144
- return options.onFailure(verification);
1145
- }
1146
- const response = paymentRequiredResponse(definition);
1147
- throw new PaymentRequiredError(response, verification);
1148
- }
1149
- return {
1150
- payment: verification.metadata,
1151
- headers: verification.responseHeaders ?? {},
1152
- optionId: verification.optionId,
1153
- result: verification
1154
- };
1155
- }
1156
- function normalizePayment(payment) {
1157
- if (isDefinedPayment(payment)) {
1158
- return {
1159
- definition: payment.definition,
1160
- verifiers: payment.verifiers ?? {}
1161
- };
1162
- }
1163
- return {
1164
- definition: payment,
1165
- verifiers: {}
1166
- };
1167
- }
1168
- function isDefinedPayment(value) {
763
+ function isX402Payment(value) {
1169
764
  return !!value && typeof value === "object" && "definition" in value && value.definition !== void 0;
1170
765
  }
1171
766
 
@@ -1186,7 +781,7 @@ function createMcpAdapter(options) {
1186
781
  const response = await Promise.resolve(httpHandler(request));
1187
782
  return await responseToToolResponse(response);
1188
783
  } catch (error) {
1189
- if (error instanceof PaymentRequiredError) {
784
+ if (error instanceof X402PaymentRequiredError) {
1190
785
  return await responseToToolResponse(error.response);
1191
786
  }
1192
787
  throw error;
@@ -1406,7 +1001,7 @@ async function loadAndValidateTools(toolsDir, options = {}) {
1406
1001
  const entry = httpHandlers[index];
1407
1002
  httpHandlers[index] = {
1408
1003
  ...entry,
1409
- handler: withPaymentRequirement(entry.handler, paymentExport)
1004
+ handler: withX402Payment(entry.handler, paymentExport)
1410
1005
  };
1411
1006
  }
1412
1007
  }
@@ -1419,11 +1014,11 @@ async function loadAndValidateTools(toolsDir, options = {}) {
1419
1014
  ...schema ? { schema } : {}
1420
1015
  });
1421
1016
  let metadataOverrides = toolModule.metadata ?? null;
1422
- if (paymentExport?.metadata) {
1017
+ if (paymentExport) {
1423
1018
  if (metadataOverrides) {
1424
1019
  metadataOverrides = {
1425
1020
  ...metadataOverrides,
1426
- payment: metadataOverrides.payment ?? paymentExport.metadata,
1021
+ payment: metadataOverrides.payment ?? paymentExport,
1427
1022
  annotations: {
1428
1023
  ...metadataOverrides.annotations ?? {},
1429
1024
  requiresPayment: metadataOverrides.annotations?.requiresPayment ?? true
@@ -1431,7 +1026,7 @@ async function loadAndValidateTools(toolsDir, options = {}) {
1431
1026
  };
1432
1027
  } else {
1433
1028
  metadataOverrides = {
1434
- payment: paymentExport.metadata,
1029
+ payment: paymentExport,
1435
1030
  annotations: { requiresPayment: true }
1436
1031
  };
1437
1032
  }
@@ -2119,7 +1714,7 @@ Detected change in ${changedPath ?? "tools directory"}, reloading...${reset}`
2119
1714
  });
2120
1715
  }
2121
1716
  }
2122
- const server = http.createServer(async (req, res) => {
1717
+ const server = http2.createServer(async (req, res) => {
2123
1718
  const method = (req.method || "GET").toUpperCase();
2124
1719
  const url = new URL(req.url || "/", `http://localhost:${port}`);
2125
1720
  const routePath = url.pathname;
@@ -2309,7 +1904,7 @@ async function handleRequest(params) {
2309
1904
  try {
2310
1905
  response = await route.handler(request);
2311
1906
  } catch (error) {
2312
- if (error instanceof PaymentRequiredError) {
1907
+ if (error instanceof X402PaymentRequiredError) {
2313
1908
  response = error.response;
2314
1909
  } else {
2315
1910
  throw error;