@agentcash/discovery 0.1.0 → 0.1.2

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.js CHANGED
@@ -11,6 +11,9 @@ var STRICT_ESCALATION_CODES = [
11
11
  "INTEROP_MPP_USED"
12
12
  ];
13
13
 
14
+ // src/mmm-enabled.ts
15
+ var isMmmEnabled = () => "0.1.2".includes("-mmm");
16
+
14
17
  // src/core/constants.ts
15
18
  var OPENAPI_PATH_CANDIDATES = ["/openapi.json", "/.well-known/openapi.json"];
16
19
  var WELL_KNOWN_MPP_PATH = "/.well-known/mpp";
@@ -963,7 +966,7 @@ function detectProtocols(response) {
963
966
  }
964
967
  const authHeader = response.headers.get("www-authenticate")?.toLowerCase() ?? "";
965
968
  if (authHeader.includes("x402")) protocols.add("x402");
966
- if (authHeader.includes("mpp")) protocols.add("mpp");
969
+ if (isMmmEnabled() && authHeader.includes("mpp")) protocols.add("mpp");
967
970
  return [...protocols];
968
971
  }
969
972
  async function runProbeStage(options) {
@@ -1295,7 +1298,7 @@ async function runDiscovery({
1295
1298
  includeRaw
1296
1299
  })
1297
1300
  );
1298
- if (includeInteropMpp) {
1301
+ if (includeInteropMpp && isMmmEnabled()) {
1299
1302
  stages.push(
1300
1303
  () => runInteropMppStage({
1301
1304
  origin,
@@ -1397,6 +1400,636 @@ async function runDiscovery({
1397
1400
  };
1398
1401
  }
1399
1402
 
1403
+ // src/adapters/mcp.ts
1404
+ var DEFAULT_GUIDANCE_AUTO_INCLUDE_MAX_TOKENS = 1e3;
1405
+ var OPENAPI_METHODS = [
1406
+ "GET",
1407
+ "POST",
1408
+ "PUT",
1409
+ "DELETE",
1410
+ "PATCH",
1411
+ "HEAD",
1412
+ "OPTIONS",
1413
+ "TRACE"
1414
+ ];
1415
+ function isRecord4(value) {
1416
+ return value != null && typeof value === "object" && !Array.isArray(value);
1417
+ }
1418
+ function toFiniteNumber(value) {
1419
+ if (typeof value === "number" && Number.isFinite(value)) return value;
1420
+ if (typeof value === "string" && value.trim().length > 0) {
1421
+ const parsed = Number(value);
1422
+ if (Number.isFinite(parsed)) return parsed;
1423
+ }
1424
+ return void 0;
1425
+ }
1426
+ function formatPriceHintFromResource(resource) {
1427
+ const pricing = resource.pricing;
1428
+ if (pricing) {
1429
+ if (pricing.pricingMode === "fixed" && pricing.price) {
1430
+ return pricing.price.startsWith("$") ? pricing.price : `$${pricing.price}`;
1431
+ }
1432
+ if (pricing.pricingMode === "range" && pricing.minPrice && pricing.maxPrice) {
1433
+ const min = pricing.minPrice.startsWith("$") ? pricing.minPrice : `$${pricing.minPrice}`;
1434
+ const max = pricing.maxPrice.startsWith("$") ? pricing.maxPrice : `$${pricing.maxPrice}`;
1435
+ return `${min}-${max}`;
1436
+ }
1437
+ }
1438
+ if (!resource.priceHint) return void 0;
1439
+ const hint = resource.priceHint.trim();
1440
+ if (hint.length === 0) return void 0;
1441
+ if (hint.startsWith("$")) return hint;
1442
+ if (hint.includes("-")) {
1443
+ const [min, max] = hint.split("-", 2).map((part) => part.trim());
1444
+ if (min && max) return `$${min}-$${max}`;
1445
+ }
1446
+ return `$${hint}`;
1447
+ }
1448
+ function deriveMcpAuthMode(resource) {
1449
+ if (!resource) return void 0;
1450
+ const hint = resource.authHint;
1451
+ if (hint === "paid" || hint === "siwx" || hint === "apiKey" || hint === "unprotected") {
1452
+ return hint;
1453
+ }
1454
+ return void 0;
1455
+ }
1456
+ function inferFailureCause(warnings) {
1457
+ const openapiWarnings = warnings.filter((w) => w.stage === "openapi");
1458
+ const parseWarning = openapiWarnings.find(
1459
+ (w) => w.code === "PARSE_FAILED" || w.code === "OPENAPI_TOP_LEVEL_INVALID" || w.code === "OPENAPI_OPERATION_INVALID" || w.code === "OPENAPI_PRICING_INVALID"
1460
+ );
1461
+ if (parseWarning) {
1462
+ return { cause: "parse", message: parseWarning.message };
1463
+ }
1464
+ const fetchWarning = openapiWarnings.find((w) => w.code === "FETCH_FAILED");
1465
+ if (fetchWarning) {
1466
+ const msg = fetchWarning.message.toLowerCase();
1467
+ if (msg.includes("timeout") || msg.includes("timed out") || msg.includes("abort")) {
1468
+ return { cause: "timeout", message: fetchWarning.message };
1469
+ }
1470
+ return { cause: "network", message: fetchWarning.message };
1471
+ }
1472
+ return { cause: "not_found" };
1473
+ }
1474
+ function getOpenApiInfo(document) {
1475
+ if (!isRecord4(document) || !isRecord4(document.info)) return void 0;
1476
+ if (typeof document.info.title !== "string") return void 0;
1477
+ return {
1478
+ title: document.info.title,
1479
+ ...typeof document.info.version === "string" ? { version: document.info.version } : {},
1480
+ ...typeof document.info.description === "string" ? { description: document.info.description } : {}
1481
+ };
1482
+ }
1483
+ function getGuidanceUrl(result) {
1484
+ const fromTrace = result.trace.find((entry) => entry.links?.llmsTxtUrl)?.links?.llmsTxtUrl;
1485
+ if (fromTrace) return fromTrace;
1486
+ const fromResource = result.resources.find((resource) => resource.links?.llmsTxtUrl)?.links?.llmsTxtUrl;
1487
+ if (fromResource) return fromResource;
1488
+ return void 0;
1489
+ }
1490
+ async function resolveGuidance(options) {
1491
+ let guidanceText = typeof options.result.rawSources?.llmsTxt === "string" ? options.result.rawSources.llmsTxt : void 0;
1492
+ if (!guidanceText) {
1493
+ const guidanceUrl = getGuidanceUrl(options.result) ?? `${options.result.origin}/llms.txt`;
1494
+ const fetcher = options.fetcher ?? fetch;
1495
+ try {
1496
+ const response = await fetcher(guidanceUrl, {
1497
+ method: "GET",
1498
+ headers: { Accept: "text/plain", ...options.headers },
1499
+ signal: options.signal
1500
+ });
1501
+ if (response.ok) guidanceText = await response.text();
1502
+ } catch {
1503
+ }
1504
+ }
1505
+ if (!guidanceText) {
1506
+ return { guidanceAvailable: false };
1507
+ }
1508
+ const guidanceTokens = estimateTokenCount(guidanceText);
1509
+ const threshold = options.guidanceAutoIncludeMaxTokens ?? DEFAULT_GUIDANCE_AUTO_INCLUDE_MAX_TOKENS;
1510
+ const shouldInclude = options.includeGuidance === true || options.includeGuidance == null && guidanceTokens <= threshold;
1511
+ return {
1512
+ guidanceAvailable: true,
1513
+ guidanceTokens,
1514
+ ...shouldInclude ? { guidance: guidanceText } : {}
1515
+ };
1516
+ }
1517
+ function extractPathsDocument(document) {
1518
+ if (!isRecord4(document)) return void 0;
1519
+ if (!isRecord4(document.paths)) return void 0;
1520
+ return document.paths;
1521
+ }
1522
+ function findMatchingOpenApiPath(paths, targetPath) {
1523
+ const exact = paths[targetPath];
1524
+ if (isRecord4(exact)) return { matchedPath: targetPath, pathItem: exact };
1525
+ for (const [specPath, entry] of Object.entries(paths)) {
1526
+ if (!isRecord4(entry)) continue;
1527
+ const pattern = specPath.replace(/\{[^}]+\}/g, "[^/]+");
1528
+ const regex = new RegExp(`^${pattern}$`);
1529
+ if (regex.test(targetPath)) {
1530
+ return { matchedPath: specPath, pathItem: entry };
1531
+ }
1532
+ }
1533
+ return null;
1534
+ }
1535
+ function resolveRef(document, ref, seen) {
1536
+ if (!ref.startsWith("#/")) return void 0;
1537
+ if (seen.has(ref)) return { $circular: ref };
1538
+ seen.add(ref);
1539
+ const parts = ref.slice(2).split("/");
1540
+ let current = document;
1541
+ for (const part of parts) {
1542
+ if (!isRecord4(current)) return void 0;
1543
+ current = current[part];
1544
+ if (current === void 0) return void 0;
1545
+ }
1546
+ if (isRecord4(current)) return resolveRefs(document, current, seen);
1547
+ return current;
1548
+ }
1549
+ function resolveRefs(document, obj, seen, depth = 0) {
1550
+ if (depth > 4) return obj;
1551
+ const resolved = {};
1552
+ for (const [key, value] of Object.entries(obj)) {
1553
+ if (key === "$ref" && typeof value === "string") {
1554
+ const deref = resolveRef(document, value, seen);
1555
+ if (isRecord4(deref)) {
1556
+ Object.assign(resolved, deref);
1557
+ } else {
1558
+ resolved[key] = value;
1559
+ }
1560
+ continue;
1561
+ }
1562
+ if (isRecord4(value)) {
1563
+ resolved[key] = resolveRefs(document, value, seen, depth + 1);
1564
+ continue;
1565
+ }
1566
+ if (Array.isArray(value)) {
1567
+ resolved[key] = value.map(
1568
+ (item) => isRecord4(item) ? resolveRefs(document, item, seen, depth + 1) : item
1569
+ );
1570
+ continue;
1571
+ }
1572
+ resolved[key] = value;
1573
+ }
1574
+ return resolved;
1575
+ }
1576
+ function extractRequestBodySchema(operationSchema) {
1577
+ const requestBody = operationSchema.requestBody;
1578
+ if (!isRecord4(requestBody)) return void 0;
1579
+ const content = requestBody.content;
1580
+ if (!isRecord4(content)) return void 0;
1581
+ const jsonMediaType = content["application/json"];
1582
+ if (isRecord4(jsonMediaType) && isRecord4(jsonMediaType.schema)) {
1583
+ return jsonMediaType.schema;
1584
+ }
1585
+ for (const mediaType of Object.values(content)) {
1586
+ if (isRecord4(mediaType) && isRecord4(mediaType.schema)) {
1587
+ return mediaType.schema;
1588
+ }
1589
+ }
1590
+ return void 0;
1591
+ }
1592
+ function extractParameters(operationSchema) {
1593
+ const params = operationSchema.parameters;
1594
+ if (!Array.isArray(params)) return [];
1595
+ return params.filter((value) => isRecord4(value));
1596
+ }
1597
+ function extractInputSchema(operationSchema) {
1598
+ const requestBody = extractRequestBodySchema(operationSchema);
1599
+ const parameters = extractParameters(operationSchema);
1600
+ if (!requestBody && parameters.length === 0) return void 0;
1601
+ if (requestBody && parameters.length === 0) return requestBody;
1602
+ return {
1603
+ ...requestBody ? { requestBody } : {},
1604
+ ...parameters.length > 0 ? { parameters } : {}
1605
+ };
1606
+ }
1607
+ function parseOperationPrice(operation) {
1608
+ const paymentInfo = operation["x-payment-info"];
1609
+ if (!isRecord4(paymentInfo)) return void 0;
1610
+ const fixed = toFiniteNumber(paymentInfo.price);
1611
+ if (fixed != null) return `$${String(fixed)}`;
1612
+ const min = toFiniteNumber(paymentInfo.minPrice);
1613
+ const max = toFiniteNumber(paymentInfo.maxPrice);
1614
+ if (min != null && max != null) return `$${String(min)}-$${String(max)}`;
1615
+ return void 0;
1616
+ }
1617
+ function parseOperationProtocols(operation) {
1618
+ const paymentInfo = operation["x-payment-info"];
1619
+ if (!isRecord4(paymentInfo) || !Array.isArray(paymentInfo.protocols)) return void 0;
1620
+ const protocols = paymentInfo.protocols.filter(
1621
+ (protocol) => typeof protocol === "string" && protocol.length > 0
1622
+ );
1623
+ return protocols.length > 0 ? protocols : void 0;
1624
+ }
1625
+ function parseOperationAuthMode(operation) {
1626
+ const authExtension = operation["x-agentcash-auth"];
1627
+ if (isRecord4(authExtension)) {
1628
+ const mode = authExtension.mode;
1629
+ if (mode === "paid" || mode === "siwx" || mode === "apiKey" || mode === "unprotected") {
1630
+ return mode;
1631
+ }
1632
+ }
1633
+ if (isRecord4(operation["x-payment-info"])) return "paid";
1634
+ return void 0;
1635
+ }
1636
+ function createResourceMap(result) {
1637
+ const map = /* @__PURE__ */ new Map();
1638
+ for (const resource of result.resources) {
1639
+ map.set(resource.resourceKey, resource);
1640
+ }
1641
+ return map;
1642
+ }
1643
+ function toMcpEndpointSummary(resource) {
1644
+ const method = parseMethod(resource.method);
1645
+ if (!method) return void 0;
1646
+ const price = formatPriceHintFromResource(resource);
1647
+ const authMode = deriveMcpAuthMode(resource);
1648
+ return {
1649
+ path: resource.path,
1650
+ method,
1651
+ summary: resource.summary ?? `${method} ${resource.path}`,
1652
+ ...price ? { price } : {},
1653
+ ...resource.protocolHints?.length ? { protocols: [...resource.protocolHints] } : {},
1654
+ ...authMode ? { authMode } : {}
1655
+ };
1656
+ }
1657
+ async function discoverForMcp(options) {
1658
+ const result = await runDiscovery({
1659
+ detailed: true,
1660
+ options: {
1661
+ target: options.target,
1662
+ compatMode: options.compatMode ?? "off",
1663
+ rawView: "full",
1664
+ fetcher: options.fetcher,
1665
+ headers: options.headers,
1666
+ signal: options.signal
1667
+ }
1668
+ });
1669
+ if (result.resources.length === 0) {
1670
+ const failure = inferFailureCause(result.warnings);
1671
+ return {
1672
+ found: false,
1673
+ origin: result.origin,
1674
+ cause: failure.cause,
1675
+ ...failure.message ? { message: failure.message } : {},
1676
+ warnings: result.warnings
1677
+ };
1678
+ }
1679
+ const source = result.selectedStage ?? "openapi";
1680
+ const endpoints = result.resources.map(toMcpEndpointSummary).filter((endpoint) => endpoint != null).sort((a, b) => {
1681
+ if (a.path !== b.path) return a.path.localeCompare(b.path);
1682
+ return a.method.localeCompare(b.method);
1683
+ });
1684
+ const guidance = await resolveGuidance({
1685
+ result,
1686
+ includeGuidance: options.includeGuidance,
1687
+ guidanceAutoIncludeMaxTokens: options.guidanceAutoIncludeMaxTokens,
1688
+ fetcher: options.fetcher,
1689
+ headers: options.headers,
1690
+ signal: options.signal
1691
+ });
1692
+ return {
1693
+ found: true,
1694
+ origin: result.origin,
1695
+ source,
1696
+ ...getOpenApiInfo(result.rawSources?.openapi) ? { info: getOpenApiInfo(result.rawSources?.openapi) } : {},
1697
+ ...guidance,
1698
+ endpoints,
1699
+ warnings: result.warnings
1700
+ };
1701
+ }
1702
+ async function inspectEndpointForMcp(options) {
1703
+ const endpoint = new URL(options.endpointUrl);
1704
+ const origin = normalizeOrigin(endpoint.origin);
1705
+ const path = normalizePath(endpoint.pathname || "/");
1706
+ const result = await runDiscovery({
1707
+ detailed: true,
1708
+ options: {
1709
+ target: options.target,
1710
+ compatMode: options.compatMode ?? "off",
1711
+ rawView: "full",
1712
+ fetcher: options.fetcher,
1713
+ headers: options.headers,
1714
+ signal: options.signal
1715
+ }
1716
+ });
1717
+ const document = result.rawSources?.openapi;
1718
+ const paths = extractPathsDocument(document);
1719
+ const resourceMap = createResourceMap(result);
1720
+ const advisories = {};
1721
+ const specMethods = [];
1722
+ if (paths) {
1723
+ const matched = findMatchingOpenApiPath(paths, path);
1724
+ if (matched) {
1725
+ for (const candidate of OPENAPI_METHODS) {
1726
+ const operation = matched.pathItem[candidate.toLowerCase()];
1727
+ if (!isRecord4(operation) || !isRecord4(document)) continue;
1728
+ specMethods.push(candidate);
1729
+ const resolvedOperation = resolveRefs(document, operation, /* @__PURE__ */ new Set());
1730
+ const resource = resourceMap.get(toResourceKey(origin, candidate, matched.matchedPath));
1731
+ const formattedPrice = resource ? formatPriceHintFromResource(resource) : void 0;
1732
+ const operationSummary = typeof resolvedOperation.summary === "string" ? resolvedOperation.summary : typeof resolvedOperation.description === "string" ? resolvedOperation.description : void 0;
1733
+ const operationPrice = parseOperationPrice(resolvedOperation);
1734
+ const operationProtocols = parseOperationProtocols(resolvedOperation);
1735
+ const operationAuthMode = parseOperationAuthMode(resolvedOperation);
1736
+ const inputSchema = extractInputSchema(resolvedOperation);
1737
+ advisories[candidate] = {
1738
+ method: candidate,
1739
+ ...resource?.summary || operationSummary ? {
1740
+ summary: resource?.summary ?? operationSummary
1741
+ } : {},
1742
+ ...formattedPrice || operationPrice ? {
1743
+ estimatedPrice: formattedPrice ?? operationPrice
1744
+ } : {},
1745
+ ...resource?.protocolHints?.length || operationProtocols ? {
1746
+ protocols: resource?.protocolHints ?? operationProtocols
1747
+ } : {},
1748
+ ...deriveMcpAuthMode(resource) || operationAuthMode ? {
1749
+ authMode: deriveMcpAuthMode(resource) ?? operationAuthMode
1750
+ } : {},
1751
+ ...inputSchema ? { inputSchema } : {},
1752
+ operationSchema: resolvedOperation
1753
+ };
1754
+ }
1755
+ }
1756
+ }
1757
+ if (specMethods.length === 0) {
1758
+ for (const method of OPENAPI_METHODS) {
1759
+ const resource = resourceMap.get(toResourceKey(origin, method, path));
1760
+ if (!resource) continue;
1761
+ specMethods.push(method);
1762
+ const formattedPrice = formatPriceHintFromResource(resource);
1763
+ advisories[method] = {
1764
+ method,
1765
+ ...resource.summary ? { summary: resource.summary } : {},
1766
+ ...formattedPrice ? { estimatedPrice: formattedPrice } : {},
1767
+ ...resource.protocolHints?.length ? { protocols: [...resource.protocolHints] } : {},
1768
+ ...deriveMcpAuthMode(resource) ? { authMode: deriveMcpAuthMode(resource) } : {}
1769
+ };
1770
+ }
1771
+ }
1772
+ if (specMethods.length === 0) {
1773
+ const failure = inferFailureCause(result.warnings);
1774
+ return {
1775
+ found: false,
1776
+ origin,
1777
+ path,
1778
+ cause: failure.cause,
1779
+ ...failure.message ? { message: failure.message } : {},
1780
+ warnings: result.warnings
1781
+ };
1782
+ }
1783
+ return {
1784
+ found: true,
1785
+ origin,
1786
+ path,
1787
+ specMethods,
1788
+ advisories,
1789
+ warnings: result.warnings
1790
+ };
1791
+ }
1792
+
1793
+ // src/harness.ts
1794
+ var INTENT_TRIGGERS = ["x402", "mpp", "pay for", "micropayment", "agentic commerce"];
1795
+ var CLIENT_PROFILES = {
1796
+ "claude-code": {
1797
+ id: "claude-code",
1798
+ label: "Claude Code MCP Harness",
1799
+ surface: "mcp",
1800
+ defaultContextWindowTokens: 2e5,
1801
+ zeroHopBudgetPercent: 0.1,
1802
+ notes: "Targets environments where MCP context can use roughly 10% of total context."
1803
+ },
1804
+ "skill-cli": {
1805
+ id: "skill-cli",
1806
+ label: "Skill + CLI Harness",
1807
+ surface: "skill-cli",
1808
+ defaultContextWindowTokens: 2e5,
1809
+ zeroHopBudgetPercent: 0.02,
1810
+ notes: "Targets constrained skill contexts with title/description-heavy routing."
1811
+ },
1812
+ "generic-mcp": {
1813
+ id: "generic-mcp",
1814
+ label: "Generic MCP Harness",
1815
+ surface: "mcp",
1816
+ defaultContextWindowTokens: 128e3,
1817
+ zeroHopBudgetPercent: 0.05,
1818
+ notes: "Conservative MCP profile when specific client budgets are unknown."
1819
+ },
1820
+ generic: {
1821
+ id: "generic",
1822
+ label: "Generic Agent Harness",
1823
+ surface: "generic",
1824
+ defaultContextWindowTokens: 128e3,
1825
+ zeroHopBudgetPercent: 0.03,
1826
+ notes: "Fallback profile for unknown clients."
1827
+ }
1828
+ };
1829
+ function isFirstPartyDomain(hostname) {
1830
+ const normalized = hostname.toLowerCase();
1831
+ return normalized.endsWith(".dev") && normalized.startsWith("stable");
1832
+ }
1833
+ function safeHostname(origin) {
1834
+ try {
1835
+ return new URL(origin).hostname;
1836
+ } catch {
1837
+ return origin.replace(/^https?:\/\//, "").split("/")[0] ?? origin;
1838
+ }
1839
+ }
1840
+ function previewText(text) {
1841
+ if (!text) return null;
1842
+ const compact = text.replace(/\s+/g, " ").trim();
1843
+ if (!compact) return null;
1844
+ return compact.length > 220 ? `${compact.slice(0, 217)}...` : compact;
1845
+ }
1846
+ function toAuthModeCount(resources) {
1847
+ const counts = {
1848
+ paid: 0,
1849
+ siwx: 0,
1850
+ apiKey: 0,
1851
+ unprotected: 0,
1852
+ unknown: 0
1853
+ };
1854
+ for (const resource of resources) {
1855
+ const mode = resource.authHint;
1856
+ if (mode === "paid" || mode === "siwx" || mode === "apiKey" || mode === "unprotected") {
1857
+ counts[mode] += 1;
1858
+ } else {
1859
+ counts.unknown += 1;
1860
+ }
1861
+ }
1862
+ return counts;
1863
+ }
1864
+ function toSourceCount(resources) {
1865
+ return resources.reduce(
1866
+ (acc, resource) => {
1867
+ acc[resource.source] = (acc[resource.source] ?? 0) + 1;
1868
+ return acc;
1869
+ },
1870
+ {}
1871
+ );
1872
+ }
1873
+ function toL2Entries(resources) {
1874
+ return [...resources].sort((a, b) => {
1875
+ if (a.path !== b.path) return a.path.localeCompare(b.path);
1876
+ if (a.method !== b.method) return a.method.localeCompare(b.method);
1877
+ return a.resourceKey.localeCompare(b.resourceKey);
1878
+ }).map((resource) => ({
1879
+ resourceKey: resource.resourceKey,
1880
+ method: resource.method,
1881
+ path: resource.path,
1882
+ source: resource.source,
1883
+ authMode: resource.authHint ?? null
1884
+ }));
1885
+ }
1886
+ function getLlmsTxtInfo(result) {
1887
+ const llmsTxtUrl = result.trace.find((entry) => entry.links?.llmsTxtUrl)?.links?.llmsTxtUrl ?? result.resources.find((resource) => resource.links?.llmsTxtUrl)?.links?.llmsTxtUrl ?? null;
1888
+ const llmsTxt = result.rawSources?.llmsTxt;
1889
+ if (llmsTxt) {
1890
+ return {
1891
+ llmsTxtUrl,
1892
+ llmsTxtTokenEstimate: estimateTokenCount(llmsTxt),
1893
+ guidancePreview: previewText(llmsTxt),
1894
+ guidanceStatus: "present"
1895
+ };
1896
+ }
1897
+ if (llmsTxtUrl) {
1898
+ const failed = result.warnings.some((warning2) => warning2.code === "LLMSTXT_FETCH_FAILED");
1899
+ return {
1900
+ llmsTxtUrl,
1901
+ llmsTxtTokenEstimate: 0,
1902
+ guidancePreview: null,
1903
+ guidanceStatus: failed ? "advertised_but_unfetched" : "missing"
1904
+ };
1905
+ }
1906
+ return {
1907
+ llmsTxtUrl: null,
1908
+ llmsTxtTokenEstimate: 0,
1909
+ guidancePreview: null,
1910
+ guidanceStatus: "not_advertised"
1911
+ };
1912
+ }
1913
+ function getHarnessClientProfile(client = "claude-code") {
1914
+ return CLIENT_PROFILES[client] ?? CLIENT_PROFILES.generic;
1915
+ }
1916
+ function listHarnessClientProfiles() {
1917
+ return Object.values(CLIENT_PROFILES);
1918
+ }
1919
+ function buildContextHarnessReport(options) {
1920
+ const client = getHarnessClientProfile(options.client);
1921
+ const contextWindowTokens = options.contextWindowTokens ?? client.defaultContextWindowTokens;
1922
+ const zeroHopBudgetTokens = Math.floor(contextWindowTokens * client.zeroHopBudgetPercent);
1923
+ const hostname = safeHostname(options.result.origin);
1924
+ const domainClass = isFirstPartyDomain(hostname) ? "first-party" : "ugc";
1925
+ const l0Summary = "Route payment-capable intents to agentcash and use install/discover commands for progressive disclosure.";
1926
+ const l1Summary = "Expose installed domain routing hints and trigger fan-out into L2/L3 commands when user intent matches domain capabilities.";
1927
+ const l0EstimatedTokens = estimateTokenCount(
1928
+ `${INTENT_TRIGGERS.join(", ")} npx agentcash install npx agentcash install-ext`
1929
+ );
1930
+ const l1EstimatedTokens = estimateTokenCount(
1931
+ `${hostname} ${l1Summary} npx agentcash discover ${hostname} npx agentcash discover ${hostname} --verbose`
1932
+ );
1933
+ const llmsInfo = getLlmsTxtInfo(options.result);
1934
+ return {
1935
+ target: options.target,
1936
+ origin: options.result.origin,
1937
+ client,
1938
+ budget: {
1939
+ contextWindowTokens,
1940
+ zeroHopBudgetTokens,
1941
+ estimatedZeroHopTokens: l0EstimatedTokens + l1EstimatedTokens,
1942
+ withinBudget: l0EstimatedTokens + l1EstimatedTokens <= zeroHopBudgetTokens
1943
+ },
1944
+ levels: {
1945
+ l0: {
1946
+ layer: "L0",
1947
+ intentTriggers: INTENT_TRIGGERS,
1948
+ installCommand: "npx agentcash install",
1949
+ deliverySurfaces: ["MCP", "Skill+CLI"],
1950
+ summary: l0Summary,
1951
+ estimatedTokens: l0EstimatedTokens
1952
+ },
1953
+ l1: {
1954
+ layer: "L1",
1955
+ domain: hostname,
1956
+ domainClass,
1957
+ selectedDiscoveryStage: options.result.selectedStage ?? null,
1958
+ summary: l1Summary,
1959
+ fanoutCommands: [
1960
+ `npx agentcash discover ${hostname}`,
1961
+ `npx agentcash discover ${hostname} --verbose`
1962
+ ],
1963
+ estimatedTokens: l1EstimatedTokens
1964
+ },
1965
+ l2: {
1966
+ layer: "L2",
1967
+ command: `npx agentcash discover ${hostname}`,
1968
+ tokenLight: true,
1969
+ resourceCount: options.result.resources.length,
1970
+ resources: toL2Entries(options.result.resources)
1971
+ },
1972
+ l3: {
1973
+ layer: "L3",
1974
+ command: `npx agentcash discover ${hostname} --verbose`,
1975
+ detailLevel: "endpoint-schema-and-metadata",
1976
+ countsByAuthMode: toAuthModeCount(options.result.resources),
1977
+ countsBySource: toSourceCount(options.result.resources),
1978
+ pricedResourceCount: options.result.resources.filter(
1979
+ (resource) => resource.authHint === "paid"
1980
+ ).length
1981
+ },
1982
+ l4: {
1983
+ layer: "L4",
1984
+ guidanceSource: llmsInfo.llmsTxtUrl ? "llms.txt" : "none",
1985
+ llmsTxtUrl: llmsInfo.llmsTxtUrl,
1986
+ llmsTxtTokenEstimate: llmsInfo.llmsTxtTokenEstimate,
1987
+ guidancePreview: llmsInfo.guidancePreview,
1988
+ guidanceStatus: llmsInfo.guidanceStatus
1989
+ },
1990
+ l5: {
1991
+ layer: "L5",
1992
+ status: "out_of_scope",
1993
+ note: "Cross-domain composition is intentionally out of scope for discovery v1."
1994
+ }
1995
+ },
1996
+ diagnostics: {
1997
+ warningCount: options.result.warnings.length,
1998
+ errorWarningCount: options.result.warnings.filter((warning2) => warning2.severity === "error").length,
1999
+ selectedStage: options.result.selectedStage ?? null,
2000
+ upgradeSuggested: options.result.upgradeSuggested,
2001
+ upgradeReasons: options.result.upgradeReasons
2002
+ }
2003
+ };
2004
+ }
2005
+ async function auditContextHarness(options) {
2006
+ const result = await runDiscovery({
2007
+ detailed: true,
2008
+ options: {
2009
+ ...options,
2010
+ rawView: "full"
2011
+ }
2012
+ });
2013
+ return buildContextHarnessReport({
2014
+ target: options.target,
2015
+ result,
2016
+ client: options.client,
2017
+ contextWindowTokens: options.contextWindowTokens
2018
+ });
2019
+ }
2020
+ function defaultHarnessProbeCandidates(report) {
2021
+ const methodsByPath = /* @__PURE__ */ new Map();
2022
+ for (const resource of report.levels.l2.resources) {
2023
+ const methods = methodsByPath.get(resource.path) ?? /* @__PURE__ */ new Set();
2024
+ methods.add(resource.method);
2025
+ methodsByPath.set(resource.path, methods);
2026
+ }
2027
+ return [...methodsByPath.entries()].map(([path, methods]) => ({
2028
+ path,
2029
+ methods: [...methods]
2030
+ }));
2031
+ }
2032
+
1400
2033
  // src/index.ts
1401
2034
  async function discover(options) {
1402
2035
  return await runDiscovery({ detailed: false, options });
@@ -1409,7 +2042,14 @@ export {
1409
2042
  LEGACY_SUNSET_DATE,
1410
2043
  STRICT_ESCALATION_CODES,
1411
2044
  UPGRADE_WARNING_CODES,
2045
+ auditContextHarness,
2046
+ buildContextHarnessReport,
1412
2047
  computeUpgradeSignal,
2048
+ defaultHarnessProbeCandidates,
1413
2049
  discover,
1414
- discoverDetailed
2050
+ discoverDetailed,
2051
+ discoverForMcp,
2052
+ getHarnessClientProfile,
2053
+ inspectEndpointForMcp,
2054
+ listHarnessClientProfiles
1415
2055
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentcash/discovery",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Canonical OpenAPI-first discovery runtime for the agentcash ecosystem",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -44,7 +44,6 @@
44
44
  "bin",
45
45
  "README.md",
46
46
  "LICENSE",
47
- ".claude/CLAUDE.md",
48
47
  "AGENTS.md"
49
48
  ],
50
49
  "engines": {
@@ -54,7 +53,6 @@
54
53
  "agentcash",
55
54
  "discovery",
56
55
  "x402",
57
- "mpp",
58
56
  "openapi"
59
57
  ],
60
58
  "license": "MIT",
@@ -66,6 +64,7 @@
66
64
  "access": "public"
67
65
  },
68
66
  "dependencies": {
67
+ "table": "^6.9.0",
69
68
  "zod": "^4.1.13"
70
69
  },
71
70
  "devDependencies": {