@agentcash/discovery 0.1.1 → 0.1.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/index.cjs CHANGED
@@ -24,14 +24,19 @@ __export(index_exports, {
24
24
  LEGACY_SUNSET_DATE: () => LEGACY_SUNSET_DATE,
25
25
  STRICT_ESCALATION_CODES: () => STRICT_ESCALATION_CODES,
26
26
  UPGRADE_WARNING_CODES: () => UPGRADE_WARNING_CODES,
27
+ VALIDATION_CODES: () => VALIDATION_CODES,
27
28
  auditContextHarness: () => auditContextHarness,
28
29
  buildContextHarnessReport: () => buildContextHarnessReport,
29
30
  computeUpgradeSignal: () => computeUpgradeSignal,
30
31
  defaultHarnessProbeCandidates: () => defaultHarnessProbeCandidates,
31
32
  discover: () => discover,
32
33
  discoverDetailed: () => discoverDetailed,
34
+ discoverForMcp: () => discoverForMcp,
35
+ evaluateMetadataCompleteness: () => evaluateMetadataCompleteness,
33
36
  getHarnessClientProfile: () => getHarnessClientProfile,
34
- listHarnessClientProfiles: () => listHarnessClientProfiles
37
+ inspectEndpointForMcp: () => inspectEndpointForMcp,
38
+ listHarnessClientProfiles: () => listHarnessClientProfiles,
39
+ validatePaymentRequiredDetailed: () => validatePaymentRequiredDetailed
35
40
  });
36
41
  module.exports = __toCommonJS(index_exports);
37
42
 
@@ -49,7 +54,7 @@ var STRICT_ESCALATION_CODES = [
49
54
  ];
50
55
 
51
56
  // src/mmm-enabled.ts
52
- var isMmmEnabled = () => "0.1.1".includes("-mmm");
57
+ var isMmmEnabled = () => "0.1.3".includes("-mmm");
53
58
 
54
59
  // src/core/constants.ts
55
60
  var OPENAPI_PATH_CANDIDATES = ["/openapi.json", "/.well-known/openapi.json"];
@@ -1437,6 +1442,861 @@ async function runDiscovery({
1437
1442
  };
1438
1443
  }
1439
1444
 
1445
+ // src/validation/codes.ts
1446
+ var VALIDATION_CODES = {
1447
+ COINBASE_SCHEMA_INVALID: "COINBASE_SCHEMA_INVALID",
1448
+ X402_NOT_OBJECT: "X402_NOT_OBJECT",
1449
+ X402_VERSION_MISSING: "X402_VERSION_MISSING",
1450
+ X402_VERSION_UNSUPPORTED: "X402_VERSION_UNSUPPORTED",
1451
+ X402_ACCEPTS_MISSING: "X402_ACCEPTS_MISSING",
1452
+ X402_ACCEPTS_INVALID: "X402_ACCEPTS_INVALID",
1453
+ X402_ACCEPTS_EMPTY: "X402_ACCEPTS_EMPTY",
1454
+ X402_ACCEPT_ENTRY_INVALID: "X402_ACCEPT_ENTRY_INVALID",
1455
+ NETWORK_CAIP2_INVALID: "NETWORK_CAIP2_INVALID",
1456
+ NETWORK_EIP155_REFERENCE_INVALID: "NETWORK_EIP155_REFERENCE_INVALID",
1457
+ NETWORK_SOLANA_ALIAS_INVALID: "NETWORK_SOLANA_ALIAS_INVALID",
1458
+ NETWORK_SOLANA_ALIAS_COMPAT: "NETWORK_SOLANA_ALIAS_COMPAT",
1459
+ NETWORK_REFERENCE_UNKNOWN: "NETWORK_REFERENCE_UNKNOWN",
1460
+ SCHEMA_INPUT_MISSING: "SCHEMA_INPUT_MISSING",
1461
+ SCHEMA_OUTPUT_MISSING: "SCHEMA_OUTPUT_MISSING",
1462
+ METADATA_TITLE_MISSING: "METADATA_TITLE_MISSING",
1463
+ METADATA_DESCRIPTION_MISSING: "METADATA_DESCRIPTION_MISSING",
1464
+ METADATA_FAVICON_MISSING: "METADATA_FAVICON_MISSING",
1465
+ METADATA_OG_IMAGE_MISSING: "METADATA_OG_IMAGE_MISSING"
1466
+ };
1467
+
1468
+ // src/validation/metadata.ts
1469
+ function hasText(value) {
1470
+ return typeof value === "string" && value.trim().length > 0;
1471
+ }
1472
+ function hasOgImage(metadata) {
1473
+ const images = metadata.ogImages;
1474
+ if (!Array.isArray(images) || images.length === 0) return false;
1475
+ return images.some((entry) => {
1476
+ if (typeof entry === "string") {
1477
+ return entry.trim().length > 0;
1478
+ }
1479
+ if (!entry || typeof entry !== "object") return false;
1480
+ return hasText(entry.url);
1481
+ });
1482
+ }
1483
+ function evaluateMetadataCompleteness(metadata) {
1484
+ const issues = [];
1485
+ if (!hasText(metadata.title)) {
1486
+ issues.push({
1487
+ code: VALIDATION_CODES.METADATA_TITLE_MISSING,
1488
+ severity: "warn",
1489
+ message: "Metadata title is missing",
1490
+ hint: "Provide a page title to improve discovery quality and UX.",
1491
+ path: "metadata.title",
1492
+ stage: "metadata"
1493
+ });
1494
+ }
1495
+ if (!hasText(metadata.description)) {
1496
+ issues.push({
1497
+ code: VALIDATION_CODES.METADATA_DESCRIPTION_MISSING,
1498
+ severity: "warn",
1499
+ message: "Metadata description is missing",
1500
+ hint: "Provide a concise description for integrators and search previews.",
1501
+ path: "metadata.description",
1502
+ stage: "metadata"
1503
+ });
1504
+ }
1505
+ if (!hasText(metadata.favicon)) {
1506
+ issues.push({
1507
+ code: VALIDATION_CODES.METADATA_FAVICON_MISSING,
1508
+ severity: "warn",
1509
+ message: "Metadata favicon is missing",
1510
+ hint: "Expose a favicon URL for stronger brand attribution.",
1511
+ path: "metadata.favicon",
1512
+ stage: "metadata"
1513
+ });
1514
+ }
1515
+ if (!hasOgImage(metadata)) {
1516
+ issues.push({
1517
+ code: VALIDATION_CODES.METADATA_OG_IMAGE_MISSING,
1518
+ severity: "warn",
1519
+ message: "Metadata OG image is missing",
1520
+ hint: "Provide an Open Graph image to improve sharing previews.",
1521
+ path: "metadata.ogImages",
1522
+ stage: "metadata"
1523
+ });
1524
+ }
1525
+ return issues;
1526
+ }
1527
+
1528
+ // src/validation/payment-required.ts
1529
+ var import_schemas = require("@x402/core/schemas");
1530
+ var SOLANA_CANONICAL_BY_REF = {
1531
+ "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp": "solana",
1532
+ EtWTRABZaYq6iMfeYKouRu166VU2xqa1: "solana-devnet",
1533
+ "4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z": "solana-testnet"
1534
+ };
1535
+ var SOLANA_ALIAS_BY_REF = {
1536
+ mainnet: "solana",
1537
+ devnet: "solana-devnet",
1538
+ testnet: "solana-testnet"
1539
+ };
1540
+ function isRecord4(value) {
1541
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1542
+ }
1543
+ function asNonEmptyString(value) {
1544
+ if (typeof value !== "string") return void 0;
1545
+ const trimmed = value.trim();
1546
+ return trimmed.length > 0 ? trimmed : void 0;
1547
+ }
1548
+ function asPositiveNumber(value) {
1549
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
1550
+ }
1551
+ function pushIssue(issues, issue) {
1552
+ issues.push({
1553
+ stage: issue.stage ?? "payment_required",
1554
+ ...issue
1555
+ });
1556
+ }
1557
+ function summarizeIssues(issues) {
1558
+ const summary = {
1559
+ errorCount: 0,
1560
+ warnCount: 0,
1561
+ infoCount: 0,
1562
+ byCode: {}
1563
+ };
1564
+ for (const issue of issues) {
1565
+ if (issue.severity === "error") summary.errorCount += 1;
1566
+ else if (issue.severity === "warn") summary.warnCount += 1;
1567
+ else summary.infoCount += 1;
1568
+ summary.byCode[issue.code] = (summary.byCode[issue.code] ?? 0) + 1;
1569
+ }
1570
+ return summary;
1571
+ }
1572
+ function outputSchemaMissingSeverity(compatMode) {
1573
+ return compatMode === "strict" ? "error" : "warn";
1574
+ }
1575
+ function zodPathToString(path) {
1576
+ if (path.length === 0) return "$";
1577
+ let out = "";
1578
+ for (const segment of path) {
1579
+ if (typeof segment === "number") out += `[${segment}]`;
1580
+ else out += out.length === 0 ? segment : `.${segment}`;
1581
+ }
1582
+ return out;
1583
+ }
1584
+ function parseWithCoinbaseStructuralGate(payload, issues) {
1585
+ const baseParsed = (0, import_schemas.parsePaymentRequired)(payload);
1586
+ if (baseParsed.success) {
1587
+ return baseParsed.data;
1588
+ }
1589
+ for (const issue of baseParsed.error.issues) {
1590
+ pushIssue(issues, {
1591
+ code: VALIDATION_CODES.COINBASE_SCHEMA_INVALID,
1592
+ severity: "error",
1593
+ message: `Coinbase schema validation failed: ${issue.message}`,
1594
+ path: zodPathToString(issue.path),
1595
+ actual: issue.code
1596
+ });
1597
+ }
1598
+ return void 0;
1599
+ }
1600
+ function validateV2Network(networkRaw, index, issues) {
1601
+ if (!/^[^:\s]+:[^:\s]+$/.test(networkRaw)) {
1602
+ pushIssue(issues, {
1603
+ code: VALIDATION_CODES.NETWORK_CAIP2_INVALID,
1604
+ severity: "error",
1605
+ message: `Accept at index ${index} has invalid CAIP-2 network format`,
1606
+ hint: "Expected '<namespace>:<reference>', e.g. 'eip155:8453' or 'solana:5eykt4...'.",
1607
+ path: `accepts[${index}].network`,
1608
+ expected: "<namespace>:<reference>",
1609
+ actual: networkRaw
1610
+ });
1611
+ return void 0;
1612
+ }
1613
+ const [namespace, reference] = networkRaw.split(":");
1614
+ if (namespace === "eip155") {
1615
+ if (!/^\d+$/.test(reference)) {
1616
+ pushIssue(issues, {
1617
+ code: VALIDATION_CODES.NETWORK_EIP155_REFERENCE_INVALID,
1618
+ severity: "error",
1619
+ message: `Accept at index ${index} has invalid eip155 reference`,
1620
+ hint: "Use a numeric chain id, e.g. 'eip155:8453'.",
1621
+ path: `accepts[${index}].network`,
1622
+ expected: "eip155:<numeric-chain-id>",
1623
+ actual: networkRaw
1624
+ });
1625
+ return void 0;
1626
+ }
1627
+ return networkRaw;
1628
+ }
1629
+ if (namespace === "solana") {
1630
+ const canonical = SOLANA_CANONICAL_BY_REF[reference];
1631
+ if (canonical) return canonical;
1632
+ const alias = SOLANA_ALIAS_BY_REF[reference];
1633
+ if (alias) {
1634
+ pushIssue(issues, {
1635
+ code: VALIDATION_CODES.NETWORK_SOLANA_ALIAS_COMPAT,
1636
+ severity: "warn",
1637
+ message: `Accept at index ${index} uses compatibility Solana reference '${reference}'`,
1638
+ hint: "Use canonical Solana CAIP references for deterministic behavior.",
1639
+ path: `accepts[${index}].network`,
1640
+ actual: networkRaw
1641
+ });
1642
+ return alias;
1643
+ }
1644
+ pushIssue(issues, {
1645
+ code: VALIDATION_CODES.NETWORK_REFERENCE_UNKNOWN,
1646
+ severity: "error",
1647
+ message: `Accept at index ${index} uses unknown Solana reference '${reference}'`,
1648
+ hint: "Use canonical references for mainnet/devnet/testnet.",
1649
+ path: `accepts[${index}].network`,
1650
+ actual: networkRaw
1651
+ });
1652
+ return void 0;
1653
+ }
1654
+ return networkRaw;
1655
+ }
1656
+ function validateV1Network(networkRaw, index, issues) {
1657
+ if (networkRaw === "solana-mainnet-beta") {
1658
+ pushIssue(issues, {
1659
+ code: VALIDATION_CODES.NETWORK_SOLANA_ALIAS_INVALID,
1660
+ severity: "error",
1661
+ message: `Accept at index ${index} uses invalid Solana network alias`,
1662
+ hint: "Use 'solana' (v1) or canonical Solana CAIP reference (v2).",
1663
+ path: `accepts[${index}].network`,
1664
+ expected: "solana",
1665
+ actual: networkRaw
1666
+ });
1667
+ return void 0;
1668
+ }
1669
+ if (networkRaw.startsWith("solana-") && networkRaw !== "solana-devnet") {
1670
+ pushIssue(issues, {
1671
+ code: VALIDATION_CODES.NETWORK_SOLANA_ALIAS_INVALID,
1672
+ severity: "error",
1673
+ message: `Accept at index ${index} uses unsupported Solana network alias`,
1674
+ hint: "Use 'solana' or 'solana-devnet' for v1 payloads.",
1675
+ path: `accepts[${index}].network`,
1676
+ actual: networkRaw
1677
+ });
1678
+ return void 0;
1679
+ }
1680
+ if (networkRaw.includes(":")) {
1681
+ return validateV2Network(networkRaw, index, issues);
1682
+ }
1683
+ return networkRaw;
1684
+ }
1685
+ function validateAccept(acceptRaw, index, version, issues) {
1686
+ if (!isRecord4(acceptRaw)) {
1687
+ pushIssue(issues, {
1688
+ code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1689
+ severity: "error",
1690
+ message: `Accept at index ${index} must be an object`,
1691
+ path: `accepts[${index}]`
1692
+ });
1693
+ return null;
1694
+ }
1695
+ const scheme = asNonEmptyString(acceptRaw.scheme);
1696
+ const networkRaw = asNonEmptyString(acceptRaw.network);
1697
+ const payTo = asNonEmptyString(acceptRaw.payTo);
1698
+ const asset = asNonEmptyString(acceptRaw.asset);
1699
+ const maxTimeoutSeconds = asPositiveNumber(acceptRaw.maxTimeoutSeconds);
1700
+ const amount = version === 2 ? asNonEmptyString(acceptRaw.amount) : asNonEmptyString(acceptRaw.maxAmountRequired);
1701
+ if (!scheme) {
1702
+ pushIssue(issues, {
1703
+ code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1704
+ severity: "error",
1705
+ message: `Accept at index ${index} is missing scheme`,
1706
+ path: `accepts[${index}].scheme`
1707
+ });
1708
+ }
1709
+ if (!networkRaw) {
1710
+ pushIssue(issues, {
1711
+ code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1712
+ severity: "error",
1713
+ message: `Accept at index ${index} is missing network`,
1714
+ path: `accepts[${index}].network`
1715
+ });
1716
+ }
1717
+ if (!amount) {
1718
+ pushIssue(issues, {
1719
+ code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1720
+ severity: "error",
1721
+ message: `Accept at index ${index} is missing ${version === 2 ? "amount" : "maxAmountRequired"}`,
1722
+ path: `accepts[${index}].${version === 2 ? "amount" : "maxAmountRequired"}`
1723
+ });
1724
+ }
1725
+ if (!payTo) {
1726
+ pushIssue(issues, {
1727
+ code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1728
+ severity: "error",
1729
+ message: `Accept at index ${index} is missing payTo`,
1730
+ path: `accepts[${index}].payTo`
1731
+ });
1732
+ }
1733
+ if (!asset) {
1734
+ pushIssue(issues, {
1735
+ code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1736
+ severity: "error",
1737
+ message: `Accept at index ${index} is missing asset`,
1738
+ path: `accepts[${index}].asset`
1739
+ });
1740
+ }
1741
+ if (!maxTimeoutSeconds) {
1742
+ pushIssue(issues, {
1743
+ code: VALIDATION_CODES.X402_ACCEPT_ENTRY_INVALID,
1744
+ severity: "error",
1745
+ message: `Accept at index ${index} is missing or has invalid maxTimeoutSeconds`,
1746
+ path: `accepts[${index}].maxTimeoutSeconds`
1747
+ });
1748
+ }
1749
+ let normalizedNetwork;
1750
+ if (networkRaw) {
1751
+ normalizedNetwork = version === 2 ? validateV2Network(networkRaw, index, issues) : validateV1Network(networkRaw, index, issues);
1752
+ }
1753
+ return {
1754
+ index,
1755
+ network: normalizedNetwork ?? networkRaw ?? "unknown",
1756
+ networkRaw: networkRaw ?? "unknown",
1757
+ scheme,
1758
+ asset,
1759
+ payTo,
1760
+ amount,
1761
+ maxTimeoutSeconds
1762
+ };
1763
+ }
1764
+ function getSchemaPresence(payload, accepts, version) {
1765
+ if (version === 1) {
1766
+ const first = accepts[0];
1767
+ if (!isRecord4(first)) {
1768
+ return { hasInputSchema: false, hasOutputSchema: false };
1769
+ }
1770
+ const outputSchema = isRecord4(first.outputSchema) ? first.outputSchema : void 0;
1771
+ return {
1772
+ hasInputSchema: outputSchema?.input !== void 0 && outputSchema.input !== null,
1773
+ hasOutputSchema: outputSchema?.output !== void 0 && outputSchema.output !== null
1774
+ };
1775
+ }
1776
+ const extensions = isRecord4(payload.extensions) ? payload.extensions : void 0;
1777
+ const bazaar = extensions && isRecord4(extensions.bazaar) ? extensions.bazaar : void 0;
1778
+ const info = bazaar && isRecord4(bazaar.info) ? bazaar.info : void 0;
1779
+ return {
1780
+ hasInputSchema: info?.input !== void 0 && info.input !== null,
1781
+ hasOutputSchema: info?.output !== void 0 && info.output !== null
1782
+ };
1783
+ }
1784
+ function validatePaymentRequiredDetailed(payload, options = {}) {
1785
+ const issues = [];
1786
+ const compatMode = options.compatMode ?? DEFAULT_COMPAT_MODE;
1787
+ const requireInputSchema = options.requireInputSchema ?? true;
1788
+ const requireOutputSchema = options.requireOutputSchema ?? true;
1789
+ if (!isRecord4(payload)) {
1790
+ pushIssue(issues, {
1791
+ code: VALIDATION_CODES.X402_NOT_OBJECT,
1792
+ severity: "error",
1793
+ message: "Payment required payload must be a JSON object",
1794
+ path: "$"
1795
+ });
1796
+ if (options.metadata) {
1797
+ issues.push(...evaluateMetadataCompleteness(options.metadata));
1798
+ }
1799
+ return {
1800
+ valid: false,
1801
+ issues,
1802
+ summary: summarizeIssues(issues)
1803
+ };
1804
+ }
1805
+ const coinbaseParsed = parseWithCoinbaseStructuralGate(payload, issues);
1806
+ const versionRaw = payload.x402Version;
1807
+ let version;
1808
+ if (versionRaw === 1 || versionRaw === 2) {
1809
+ version = versionRaw;
1810
+ } else if (versionRaw === void 0) {
1811
+ pushIssue(issues, {
1812
+ code: VALIDATION_CODES.X402_VERSION_MISSING,
1813
+ severity: "error",
1814
+ message: "x402Version is required",
1815
+ path: "x402Version"
1816
+ });
1817
+ } else {
1818
+ pushIssue(issues, {
1819
+ code: VALIDATION_CODES.X402_VERSION_UNSUPPORTED,
1820
+ severity: "error",
1821
+ message: `Unsupported x402Version '${String(versionRaw)}'`,
1822
+ path: "x402Version",
1823
+ expected: "1 | 2",
1824
+ actual: String(versionRaw)
1825
+ });
1826
+ }
1827
+ const acceptsRaw = payload.accepts;
1828
+ let accepts = [];
1829
+ if (acceptsRaw === void 0) {
1830
+ pushIssue(issues, {
1831
+ code: VALIDATION_CODES.X402_ACCEPTS_MISSING,
1832
+ severity: "error",
1833
+ message: "accepts is required",
1834
+ path: "accepts"
1835
+ });
1836
+ } else if (!Array.isArray(acceptsRaw)) {
1837
+ pushIssue(issues, {
1838
+ code: VALIDATION_CODES.X402_ACCEPTS_INVALID,
1839
+ severity: "error",
1840
+ message: "accepts must be an array",
1841
+ path: "accepts"
1842
+ });
1843
+ } else {
1844
+ accepts = acceptsRaw;
1845
+ if (accepts.length === 0) {
1846
+ pushIssue(issues, {
1847
+ code: VALIDATION_CODES.X402_ACCEPTS_EMPTY,
1848
+ severity: "error",
1849
+ message: "accepts must contain at least one payment requirement",
1850
+ path: "accepts"
1851
+ });
1852
+ }
1853
+ }
1854
+ const normalizedAccepts = [];
1855
+ if (version && Array.isArray(accepts)) {
1856
+ accepts.forEach((accept, index) => {
1857
+ const normalized = validateAccept(accept, index, version, issues);
1858
+ if (normalized) normalizedAccepts.push(normalized);
1859
+ });
1860
+ const schemaPresence = getSchemaPresence(payload, accepts, version);
1861
+ if (requireInputSchema && !schemaPresence.hasInputSchema) {
1862
+ pushIssue(issues, {
1863
+ code: VALIDATION_CODES.SCHEMA_INPUT_MISSING,
1864
+ severity: "error",
1865
+ message: "Input schema is missing",
1866
+ hint: "Include input schema details so clients can invoke the endpoint correctly.",
1867
+ path: version === 1 ? "accepts[0].outputSchema.input" : "extensions.bazaar.info.input"
1868
+ });
1869
+ }
1870
+ if (requireOutputSchema && !schemaPresence.hasOutputSchema) {
1871
+ pushIssue(issues, {
1872
+ code: VALIDATION_CODES.SCHEMA_OUTPUT_MISSING,
1873
+ severity: outputSchemaMissingSeverity(compatMode),
1874
+ message: "Output schema is missing",
1875
+ hint: "Include output schema details so clients can validate responses.",
1876
+ path: version === 1 ? "accepts[0].outputSchema.output" : "extensions.bazaar.info.output"
1877
+ });
1878
+ }
1879
+ if (options.metadata) {
1880
+ issues.push(...evaluateMetadataCompleteness(options.metadata));
1881
+ }
1882
+ const summary2 = summarizeIssues(issues);
1883
+ return {
1884
+ valid: summary2.errorCount === 0,
1885
+ version,
1886
+ parsed: coinbaseParsed ?? payload,
1887
+ normalized: {
1888
+ version,
1889
+ accepts: normalizedAccepts,
1890
+ hasInputSchema: schemaPresence.hasInputSchema,
1891
+ hasOutputSchema: schemaPresence.hasOutputSchema
1892
+ },
1893
+ issues,
1894
+ summary: summary2
1895
+ };
1896
+ }
1897
+ if (options.metadata) {
1898
+ issues.push(...evaluateMetadataCompleteness(options.metadata));
1899
+ }
1900
+ const summary = summarizeIssues(issues);
1901
+ return {
1902
+ valid: summary.errorCount === 0,
1903
+ version,
1904
+ parsed: coinbaseParsed ?? payload,
1905
+ issues,
1906
+ summary
1907
+ };
1908
+ }
1909
+
1910
+ // src/adapters/mcp.ts
1911
+ var DEFAULT_GUIDANCE_AUTO_INCLUDE_MAX_TOKENS = 1e3;
1912
+ var OPENAPI_METHODS = [
1913
+ "GET",
1914
+ "POST",
1915
+ "PUT",
1916
+ "DELETE",
1917
+ "PATCH",
1918
+ "HEAD",
1919
+ "OPTIONS",
1920
+ "TRACE"
1921
+ ];
1922
+ function isRecord5(value) {
1923
+ return value != null && typeof value === "object" && !Array.isArray(value);
1924
+ }
1925
+ function toFiniteNumber(value) {
1926
+ if (typeof value === "number" && Number.isFinite(value)) return value;
1927
+ if (typeof value === "string" && value.trim().length > 0) {
1928
+ const parsed = Number(value);
1929
+ if (Number.isFinite(parsed)) return parsed;
1930
+ }
1931
+ return void 0;
1932
+ }
1933
+ function formatPriceHintFromResource(resource) {
1934
+ const pricing = resource.pricing;
1935
+ if (pricing) {
1936
+ if (pricing.pricingMode === "fixed" && pricing.price) {
1937
+ return pricing.price.startsWith("$") ? pricing.price : `$${pricing.price}`;
1938
+ }
1939
+ if (pricing.pricingMode === "range" && pricing.minPrice && pricing.maxPrice) {
1940
+ const min = pricing.minPrice.startsWith("$") ? pricing.minPrice : `$${pricing.minPrice}`;
1941
+ const max = pricing.maxPrice.startsWith("$") ? pricing.maxPrice : `$${pricing.maxPrice}`;
1942
+ return `${min}-${max}`;
1943
+ }
1944
+ }
1945
+ if (!resource.priceHint) return void 0;
1946
+ const hint = resource.priceHint.trim();
1947
+ if (hint.length === 0) return void 0;
1948
+ if (hint.startsWith("$")) return hint;
1949
+ if (hint.includes("-")) {
1950
+ const [min, max] = hint.split("-", 2).map((part) => part.trim());
1951
+ if (min && max) return `$${min}-$${max}`;
1952
+ }
1953
+ return `$${hint}`;
1954
+ }
1955
+ function deriveMcpAuthMode(resource) {
1956
+ if (!resource) return void 0;
1957
+ const hint = resource.authHint;
1958
+ if (hint === "paid" || hint === "siwx" || hint === "apiKey" || hint === "unprotected") {
1959
+ return hint;
1960
+ }
1961
+ return void 0;
1962
+ }
1963
+ function inferFailureCause(warnings) {
1964
+ const openapiWarnings = warnings.filter((w) => w.stage === "openapi");
1965
+ const parseWarning = openapiWarnings.find(
1966
+ (w) => w.code === "PARSE_FAILED" || w.code === "OPENAPI_TOP_LEVEL_INVALID" || w.code === "OPENAPI_OPERATION_INVALID" || w.code === "OPENAPI_PRICING_INVALID"
1967
+ );
1968
+ if (parseWarning) {
1969
+ return { cause: "parse", message: parseWarning.message };
1970
+ }
1971
+ const fetchWarning = openapiWarnings.find((w) => w.code === "FETCH_FAILED");
1972
+ if (fetchWarning) {
1973
+ const msg = fetchWarning.message.toLowerCase();
1974
+ if (msg.includes("timeout") || msg.includes("timed out") || msg.includes("abort")) {
1975
+ return { cause: "timeout", message: fetchWarning.message };
1976
+ }
1977
+ return { cause: "network", message: fetchWarning.message };
1978
+ }
1979
+ return { cause: "not_found" };
1980
+ }
1981
+ function getOpenApiInfo(document) {
1982
+ if (!isRecord5(document) || !isRecord5(document.info)) return void 0;
1983
+ if (typeof document.info.title !== "string") return void 0;
1984
+ return {
1985
+ title: document.info.title,
1986
+ ...typeof document.info.version === "string" ? { version: document.info.version } : {},
1987
+ ...typeof document.info.description === "string" ? { description: document.info.description } : {}
1988
+ };
1989
+ }
1990
+ function getGuidanceUrl(result) {
1991
+ const fromTrace = result.trace.find((entry) => entry.links?.llmsTxtUrl)?.links?.llmsTxtUrl;
1992
+ if (fromTrace) return fromTrace;
1993
+ const fromResource = result.resources.find((resource) => resource.links?.llmsTxtUrl)?.links?.llmsTxtUrl;
1994
+ if (fromResource) return fromResource;
1995
+ return void 0;
1996
+ }
1997
+ async function resolveGuidance(options) {
1998
+ let guidanceText = typeof options.result.rawSources?.llmsTxt === "string" ? options.result.rawSources.llmsTxt : void 0;
1999
+ if (!guidanceText) {
2000
+ const guidanceUrl = getGuidanceUrl(options.result) ?? `${options.result.origin}/llms.txt`;
2001
+ const fetcher = options.fetcher ?? fetch;
2002
+ try {
2003
+ const response = await fetcher(guidanceUrl, {
2004
+ method: "GET",
2005
+ headers: { Accept: "text/plain", ...options.headers },
2006
+ signal: options.signal
2007
+ });
2008
+ if (response.ok) guidanceText = await response.text();
2009
+ } catch {
2010
+ }
2011
+ }
2012
+ if (!guidanceText) {
2013
+ return { guidanceAvailable: false };
2014
+ }
2015
+ const guidanceTokens = estimateTokenCount(guidanceText);
2016
+ const threshold = options.guidanceAutoIncludeMaxTokens ?? DEFAULT_GUIDANCE_AUTO_INCLUDE_MAX_TOKENS;
2017
+ const shouldInclude = options.includeGuidance === true || options.includeGuidance == null && guidanceTokens <= threshold;
2018
+ return {
2019
+ guidanceAvailable: true,
2020
+ guidanceTokens,
2021
+ ...shouldInclude ? { guidance: guidanceText } : {}
2022
+ };
2023
+ }
2024
+ function extractPathsDocument(document) {
2025
+ if (!isRecord5(document)) return void 0;
2026
+ if (!isRecord5(document.paths)) return void 0;
2027
+ return document.paths;
2028
+ }
2029
+ function findMatchingOpenApiPath(paths, targetPath) {
2030
+ const exact = paths[targetPath];
2031
+ if (isRecord5(exact)) return { matchedPath: targetPath, pathItem: exact };
2032
+ for (const [specPath, entry] of Object.entries(paths)) {
2033
+ if (!isRecord5(entry)) continue;
2034
+ const pattern = specPath.replace(/\{[^}]+\}/g, "[^/]+");
2035
+ const regex = new RegExp(`^${pattern}$`);
2036
+ if (regex.test(targetPath)) {
2037
+ return { matchedPath: specPath, pathItem: entry };
2038
+ }
2039
+ }
2040
+ return null;
2041
+ }
2042
+ function resolveRef(document, ref, seen) {
2043
+ if (!ref.startsWith("#/")) return void 0;
2044
+ if (seen.has(ref)) return { $circular: ref };
2045
+ seen.add(ref);
2046
+ const parts = ref.slice(2).split("/");
2047
+ let current = document;
2048
+ for (const part of parts) {
2049
+ if (!isRecord5(current)) return void 0;
2050
+ current = current[part];
2051
+ if (current === void 0) return void 0;
2052
+ }
2053
+ if (isRecord5(current)) return resolveRefs(document, current, seen);
2054
+ return current;
2055
+ }
2056
+ function resolveRefs(document, obj, seen, depth = 0) {
2057
+ if (depth > 4) return obj;
2058
+ const resolved = {};
2059
+ for (const [key, value] of Object.entries(obj)) {
2060
+ if (key === "$ref" && typeof value === "string") {
2061
+ const deref = resolveRef(document, value, seen);
2062
+ if (isRecord5(deref)) {
2063
+ Object.assign(resolved, deref);
2064
+ } else {
2065
+ resolved[key] = value;
2066
+ }
2067
+ continue;
2068
+ }
2069
+ if (isRecord5(value)) {
2070
+ resolved[key] = resolveRefs(document, value, seen, depth + 1);
2071
+ continue;
2072
+ }
2073
+ if (Array.isArray(value)) {
2074
+ resolved[key] = value.map(
2075
+ (item) => isRecord5(item) ? resolveRefs(document, item, seen, depth + 1) : item
2076
+ );
2077
+ continue;
2078
+ }
2079
+ resolved[key] = value;
2080
+ }
2081
+ return resolved;
2082
+ }
2083
+ function extractRequestBodySchema(operationSchema) {
2084
+ const requestBody = operationSchema.requestBody;
2085
+ if (!isRecord5(requestBody)) return void 0;
2086
+ const content = requestBody.content;
2087
+ if (!isRecord5(content)) return void 0;
2088
+ const jsonMediaType = content["application/json"];
2089
+ if (isRecord5(jsonMediaType) && isRecord5(jsonMediaType.schema)) {
2090
+ return jsonMediaType.schema;
2091
+ }
2092
+ for (const mediaType of Object.values(content)) {
2093
+ if (isRecord5(mediaType) && isRecord5(mediaType.schema)) {
2094
+ return mediaType.schema;
2095
+ }
2096
+ }
2097
+ return void 0;
2098
+ }
2099
+ function extractParameters(operationSchema) {
2100
+ const params = operationSchema.parameters;
2101
+ if (!Array.isArray(params)) return [];
2102
+ return params.filter((value) => isRecord5(value));
2103
+ }
2104
+ function extractInputSchema(operationSchema) {
2105
+ const requestBody = extractRequestBodySchema(operationSchema);
2106
+ const parameters = extractParameters(operationSchema);
2107
+ if (!requestBody && parameters.length === 0) return void 0;
2108
+ if (requestBody && parameters.length === 0) return requestBody;
2109
+ return {
2110
+ ...requestBody ? { requestBody } : {},
2111
+ ...parameters.length > 0 ? { parameters } : {}
2112
+ };
2113
+ }
2114
+ function parseOperationPrice(operation) {
2115
+ const paymentInfo = operation["x-payment-info"];
2116
+ if (!isRecord5(paymentInfo)) return void 0;
2117
+ const fixed = toFiniteNumber(paymentInfo.price);
2118
+ if (fixed != null) return `$${String(fixed)}`;
2119
+ const min = toFiniteNumber(paymentInfo.minPrice);
2120
+ const max = toFiniteNumber(paymentInfo.maxPrice);
2121
+ if (min != null && max != null) return `$${String(min)}-$${String(max)}`;
2122
+ return void 0;
2123
+ }
2124
+ function parseOperationProtocols(operation) {
2125
+ const paymentInfo = operation["x-payment-info"];
2126
+ if (!isRecord5(paymentInfo) || !Array.isArray(paymentInfo.protocols)) return void 0;
2127
+ const protocols = paymentInfo.protocols.filter(
2128
+ (protocol) => typeof protocol === "string" && protocol.length > 0
2129
+ );
2130
+ return protocols.length > 0 ? protocols : void 0;
2131
+ }
2132
+ function parseOperationAuthMode(operation) {
2133
+ const authExtension = operation["x-agentcash-auth"];
2134
+ if (isRecord5(authExtension)) {
2135
+ const mode = authExtension.mode;
2136
+ if (mode === "paid" || mode === "siwx" || mode === "apiKey" || mode === "unprotected") {
2137
+ return mode;
2138
+ }
2139
+ }
2140
+ if (isRecord5(operation["x-payment-info"])) return "paid";
2141
+ return void 0;
2142
+ }
2143
+ function createResourceMap(result) {
2144
+ const map = /* @__PURE__ */ new Map();
2145
+ for (const resource of result.resources) {
2146
+ map.set(resource.resourceKey, resource);
2147
+ }
2148
+ return map;
2149
+ }
2150
+ function toMcpEndpointSummary(resource) {
2151
+ const method = parseMethod(resource.method);
2152
+ if (!method) return void 0;
2153
+ const price = formatPriceHintFromResource(resource);
2154
+ const authMode = deriveMcpAuthMode(resource);
2155
+ return {
2156
+ path: resource.path,
2157
+ method,
2158
+ summary: resource.summary ?? `${method} ${resource.path}`,
2159
+ ...price ? { price } : {},
2160
+ ...resource.protocolHints?.length ? { protocols: [...resource.protocolHints] } : {},
2161
+ ...authMode ? { authMode } : {}
2162
+ };
2163
+ }
2164
+ async function discoverForMcp(options) {
2165
+ const result = await runDiscovery({
2166
+ detailed: true,
2167
+ options: {
2168
+ target: options.target,
2169
+ compatMode: options.compatMode ?? "off",
2170
+ rawView: "full",
2171
+ fetcher: options.fetcher,
2172
+ headers: options.headers,
2173
+ signal: options.signal
2174
+ }
2175
+ });
2176
+ if (result.resources.length === 0) {
2177
+ const failure = inferFailureCause(result.warnings);
2178
+ return {
2179
+ found: false,
2180
+ origin: result.origin,
2181
+ cause: failure.cause,
2182
+ ...failure.message ? { message: failure.message } : {},
2183
+ warnings: result.warnings
2184
+ };
2185
+ }
2186
+ const source = result.selectedStage ?? "openapi";
2187
+ const endpoints = result.resources.map(toMcpEndpointSummary).filter((endpoint) => endpoint != null).sort((a, b) => {
2188
+ if (a.path !== b.path) return a.path.localeCompare(b.path);
2189
+ return a.method.localeCompare(b.method);
2190
+ });
2191
+ const guidance = await resolveGuidance({
2192
+ result,
2193
+ includeGuidance: options.includeGuidance,
2194
+ guidanceAutoIncludeMaxTokens: options.guidanceAutoIncludeMaxTokens,
2195
+ fetcher: options.fetcher,
2196
+ headers: options.headers,
2197
+ signal: options.signal
2198
+ });
2199
+ return {
2200
+ found: true,
2201
+ origin: result.origin,
2202
+ source,
2203
+ ...getOpenApiInfo(result.rawSources?.openapi) ? { info: getOpenApiInfo(result.rawSources?.openapi) } : {},
2204
+ ...guidance,
2205
+ endpoints,
2206
+ warnings: result.warnings
2207
+ };
2208
+ }
2209
+ async function inspectEndpointForMcp(options) {
2210
+ const endpoint = new URL(options.endpointUrl);
2211
+ const origin = normalizeOrigin(endpoint.origin);
2212
+ const path = normalizePath(endpoint.pathname || "/");
2213
+ const result = await runDiscovery({
2214
+ detailed: true,
2215
+ options: {
2216
+ target: options.target,
2217
+ compatMode: options.compatMode ?? "off",
2218
+ rawView: "full",
2219
+ fetcher: options.fetcher,
2220
+ headers: options.headers,
2221
+ signal: options.signal
2222
+ }
2223
+ });
2224
+ const document = result.rawSources?.openapi;
2225
+ const paths = extractPathsDocument(document);
2226
+ const resourceMap = createResourceMap(result);
2227
+ const advisories = {};
2228
+ const specMethods = [];
2229
+ if (paths) {
2230
+ const matched = findMatchingOpenApiPath(paths, path);
2231
+ if (matched) {
2232
+ for (const candidate of OPENAPI_METHODS) {
2233
+ const operation = matched.pathItem[candidate.toLowerCase()];
2234
+ if (!isRecord5(operation) || !isRecord5(document)) continue;
2235
+ specMethods.push(candidate);
2236
+ const resolvedOperation = resolveRefs(document, operation, /* @__PURE__ */ new Set());
2237
+ const resource = resourceMap.get(toResourceKey(origin, candidate, matched.matchedPath));
2238
+ const formattedPrice = resource ? formatPriceHintFromResource(resource) : void 0;
2239
+ const operationSummary = typeof resolvedOperation.summary === "string" ? resolvedOperation.summary : typeof resolvedOperation.description === "string" ? resolvedOperation.description : void 0;
2240
+ const operationPrice = parseOperationPrice(resolvedOperation);
2241
+ const operationProtocols = parseOperationProtocols(resolvedOperation);
2242
+ const operationAuthMode = parseOperationAuthMode(resolvedOperation);
2243
+ const inputSchema = extractInputSchema(resolvedOperation);
2244
+ advisories[candidate] = {
2245
+ method: candidate,
2246
+ ...resource?.summary || operationSummary ? {
2247
+ summary: resource?.summary ?? operationSummary
2248
+ } : {},
2249
+ ...formattedPrice || operationPrice ? {
2250
+ estimatedPrice: formattedPrice ?? operationPrice
2251
+ } : {},
2252
+ ...resource?.protocolHints?.length || operationProtocols ? {
2253
+ protocols: resource?.protocolHints ?? operationProtocols
2254
+ } : {},
2255
+ ...deriveMcpAuthMode(resource) || operationAuthMode ? {
2256
+ authMode: deriveMcpAuthMode(resource) ?? operationAuthMode
2257
+ } : {},
2258
+ ...inputSchema ? { inputSchema } : {},
2259
+ operationSchema: resolvedOperation
2260
+ };
2261
+ }
2262
+ }
2263
+ }
2264
+ if (specMethods.length === 0) {
2265
+ for (const method of OPENAPI_METHODS) {
2266
+ const resource = resourceMap.get(toResourceKey(origin, method, path));
2267
+ if (!resource) continue;
2268
+ specMethods.push(method);
2269
+ const formattedPrice = formatPriceHintFromResource(resource);
2270
+ advisories[method] = {
2271
+ method,
2272
+ ...resource.summary ? { summary: resource.summary } : {},
2273
+ ...formattedPrice ? { estimatedPrice: formattedPrice } : {},
2274
+ ...resource.protocolHints?.length ? { protocols: [...resource.protocolHints] } : {},
2275
+ ...deriveMcpAuthMode(resource) ? { authMode: deriveMcpAuthMode(resource) } : {}
2276
+ };
2277
+ }
2278
+ }
2279
+ if (specMethods.length === 0) {
2280
+ const failure = inferFailureCause(result.warnings);
2281
+ return {
2282
+ found: false,
2283
+ origin,
2284
+ path,
2285
+ cause: failure.cause,
2286
+ ...failure.message ? { message: failure.message } : {},
2287
+ warnings: result.warnings
2288
+ };
2289
+ }
2290
+ return {
2291
+ found: true,
2292
+ origin,
2293
+ path,
2294
+ specMethods,
2295
+ advisories,
2296
+ warnings: result.warnings
2297
+ };
2298
+ }
2299
+
1440
2300
  // src/harness.ts
1441
2301
  var INTENT_TRIGGERS = ["x402", "mpp", "pay for", "micropayment", "agentic commerce"];
1442
2302
  var CLIENT_PROFILES = {
@@ -1690,12 +2550,17 @@ async function discoverDetailed(options) {
1690
2550
  LEGACY_SUNSET_DATE,
1691
2551
  STRICT_ESCALATION_CODES,
1692
2552
  UPGRADE_WARNING_CODES,
2553
+ VALIDATION_CODES,
1693
2554
  auditContextHarness,
1694
2555
  buildContextHarnessReport,
1695
2556
  computeUpgradeSignal,
1696
2557
  defaultHarnessProbeCandidates,
1697
2558
  discover,
1698
2559
  discoverDetailed,
2560
+ discoverForMcp,
2561
+ evaluateMetadataCompleteness,
1699
2562
  getHarnessClientProfile,
1700
- listHarnessClientProfiles
2563
+ inspectEndpointForMcp,
2564
+ listHarnessClientProfiles,
2565
+ validatePaymentRequiredDetailed
1701
2566
  });