mcp-from-openapi 2.6.0 → 2.6.1

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/index.js CHANGED
@@ -36,6 +36,7 @@ __export(index_exports, {
36
36
  LoadError: () => LoadError,
37
37
  OpenAPIToolError: () => OpenAPIToolError,
38
38
  OpenAPIToolGenerator: () => OpenAPIToolGenerator,
39
+ OverlayError: () => OverlayError,
39
40
  ParameterResolver: () => ParameterResolver,
40
41
  ParseError: () => ParseError,
41
42
  RequestBuildError: () => RequestBuildError,
@@ -46,7 +47,9 @@ __export(index_exports, {
46
47
  SsrfError: () => SsrfError,
47
48
  ValidationError: () => ValidationError,
48
49
  Validator: () => Validator,
50
+ analyzeToolSet: () => analyzeToolSet,
49
51
  applyClientTarget: () => applyClientTarget,
52
+ applyOverlay: () => applyOverlay,
50
53
  assertUrlSafe: () => assertUrlSafe,
51
54
  buildHttpRequest: () => buildHttpRequest,
52
55
  collapseNestedUnions: () => collapseNestedUnions,
@@ -57,12 +60,14 @@ __export(index_exports, {
57
60
  demoteFormats: () => demoteFormats,
58
61
  enforceClosedObjects: () => enforceClosedObjects,
59
62
  ensureArrayItems: () => ensureArrayItems,
63
+ estimateToolTokens: () => estimateToolTokens,
60
64
  extractExtensionOverrides: () => extractExtensionOverrides,
61
65
  inferAnnotationsFromMethod: () => inferAnnotationsFromMethod,
62
66
  inlineLocalRefs: () => inlineLocalRefs,
63
67
  isBlockedAddress: () => isBlockedAddress,
64
68
  isBlockedHostname: () => isBlockedHostname,
65
69
  isReferenceObject: () => isReferenceObject,
70
+ lintDocument: () => lintDocument,
66
71
  normalizeSsrfOptions: () => normalizeSsrfOptions,
67
72
  requireAllProperties: () => requireAllProperties,
68
73
  resolveExtensionEnabled: () => resolveExtensionEnabled,
@@ -1005,6 +1010,96 @@ var SchemaBuilder = class {
1005
1010
  }
1006
1011
  return copy;
1007
1012
  }
1013
+ // Copy-on-walk over every structural keyword (same key groups as
1014
+ // truncateDepth): `visit` transforms each node top-down and must return a
1015
+ // new node when it changes anything.
1016
+ static walkCopy(node, visit, seen = /* @__PURE__ */ new Map()) {
1017
+ if (!node || typeof node !== "object") return node;
1018
+ const existing = seen.get(node);
1019
+ if (existing) return existing;
1020
+ const copy = visit({ ...node });
1021
+ seen.set(node, copy);
1022
+ for (const key of this.TRUNCATE_MAP_KEYS) {
1023
+ const value = copy[key];
1024
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
1025
+ const mapped = {};
1026
+ for (const [name, sub] of Object.entries(value)) {
1027
+ mapped[name] = this.walkCopy(sub, visit, seen);
1028
+ }
1029
+ copy[key] = mapped;
1030
+ }
1031
+ }
1032
+ for (const key of this.TRUNCATE_SCHEMA_KEYS) {
1033
+ const value = copy[key];
1034
+ if (Array.isArray(value)) {
1035
+ copy[key] = value.map((item) => this.walkCopy(item, visit, seen));
1036
+ } else if (value !== null && typeof value === "object") {
1037
+ copy[key] = this.walkCopy(value, visit, seen);
1038
+ }
1039
+ }
1040
+ for (const key of this.TRUNCATE_LIST_KEYS) {
1041
+ const value = copy[key];
1042
+ if (Array.isArray(value)) {
1043
+ copy[key] = value.map((member) => this.walkCopy(member, visit, seen));
1044
+ }
1045
+ }
1046
+ return copy;
1047
+ }
1048
+ /**
1049
+ * Limit every object node to its first `max` properties (declaration
1050
+ * order). Dropped properties are pruned from `required` and counted in a
1051
+ * note appended to the node's description.
1052
+ */
1053
+ static limitProperties(schema, max) {
1054
+ const bound = Number.isFinite(max) ? Math.max(1, Math.floor(max)) : Number.MAX_SAFE_INTEGER;
1055
+ return this.walkCopy(schema, (node) => {
1056
+ const properties = node.properties;
1057
+ if (!properties || typeof properties !== "object") return node;
1058
+ const entries = Object.entries(properties);
1059
+ if (entries.length <= bound) return node;
1060
+ const kept = entries.slice(0, bound);
1061
+ const keptNames = new Set(kept.map(([name]) => name));
1062
+ const dropped = entries.length - bound;
1063
+ const note = `[${dropped} additional propert${dropped === 1 ? "y" : "ies"} omitted: exceeds maxProperties]`;
1064
+ const next = { ...node, properties: Object.fromEntries(kept) };
1065
+ if (Array.isArray(node.required)) {
1066
+ const required = node.required.filter((name) => keptNames.has(String(name)));
1067
+ if (required.length > 0) {
1068
+ next.required = required;
1069
+ } else {
1070
+ delete next.required;
1071
+ }
1072
+ }
1073
+ next.description = node.description ? `${node.description} ${note}` : note;
1074
+ return next;
1075
+ });
1076
+ }
1077
+ /**
1078
+ * Cap every description in the schema tree to `maxLength` characters,
1079
+ * truncating with an ellipsis.
1080
+ */
1081
+ static capDescriptions(schema, maxLength) {
1082
+ const bound = Number.isFinite(maxLength) ? Math.max(1, Math.floor(maxLength)) : Number.MAX_SAFE_INTEGER;
1083
+ return this.walkCopy(schema, (node) => {
1084
+ if (typeof node.description === "string" && node.description.length > bound) {
1085
+ return { ...node, description: `${node.description.slice(0, bound - 1)}\u2026` };
1086
+ }
1087
+ return node;
1088
+ });
1089
+ }
1090
+ /**
1091
+ * Remove every `examples` array from the schema tree (a token-budget
1092
+ * trimming step — validation keywords are untouched).
1093
+ */
1094
+ static stripExamples(schema) {
1095
+ return this.walkCopy(schema, (node) => {
1096
+ if ("examples" in node) {
1097
+ const { examples: _examples, ...rest } = node;
1098
+ return rest;
1099
+ }
1100
+ return node;
1101
+ });
1102
+ }
1008
1103
  /**
1009
1104
  * Simplify schema by removing unnecessary fields
1010
1105
  */
@@ -1445,6 +1540,557 @@ function applyClientTarget(schema, target) {
1445
1540
  return result;
1446
1541
  }
1447
1542
 
1543
+ // src/errors.ts
1544
+ var OpenAPIToolError = class extends Error {
1545
+ context;
1546
+ constructor(message, context) {
1547
+ super(message);
1548
+ this.name = this.constructor.name;
1549
+ this.context = context;
1550
+ if (Error.captureStackTrace) {
1551
+ Error.captureStackTrace(this, this.constructor);
1552
+ }
1553
+ }
1554
+ };
1555
+ var LoadError = class extends OpenAPIToolError {
1556
+ constructor(message, context) {
1557
+ super(message, context);
1558
+ }
1559
+ };
1560
+ var SsrfError = class extends LoadError {
1561
+ constructor(message, context) {
1562
+ super(message, context);
1563
+ }
1564
+ };
1565
+ var ParseError = class extends OpenAPIToolError {
1566
+ constructor(message, context) {
1567
+ super(message, context);
1568
+ }
1569
+ };
1570
+ var ValidationError = class extends OpenAPIToolError {
1571
+ errors;
1572
+ constructor(message, context) {
1573
+ super(message, context);
1574
+ this.errors = context?.["errors"];
1575
+ }
1576
+ };
1577
+ var GenerationError = class extends OpenAPIToolError {
1578
+ constructor(message, context) {
1579
+ super(message, context);
1580
+ }
1581
+ };
1582
+ var OverlayError = class extends OpenAPIToolError {
1583
+ constructor(message, context) {
1584
+ super(message, context);
1585
+ }
1586
+ };
1587
+ var RequestBuildError = class extends OpenAPIToolError {
1588
+ constructor(message, context) {
1589
+ super(message, context);
1590
+ }
1591
+ };
1592
+ var SchemaError = class extends OpenAPIToolError {
1593
+ constructor(message, context) {
1594
+ super(message, context);
1595
+ }
1596
+ };
1597
+
1598
+ // src/overlay.ts
1599
+ function parsePath(path) {
1600
+ if (typeof path !== "string" || !path.startsWith("$")) {
1601
+ throw new OverlayError(`Overlay target must be a JSONPath starting with '$'; received '${String(path)}'`, {
1602
+ target: path
1603
+ });
1604
+ }
1605
+ const segments = [];
1606
+ let rest = path.slice(1);
1607
+ while (rest.length > 0) {
1608
+ let recursive = false;
1609
+ if (rest.startsWith("..")) {
1610
+ recursive = true;
1611
+ rest = rest.slice(2);
1612
+ const bare = rest.match(/^([A-Za-z_][\w-]*)/);
1613
+ if (bare) {
1614
+ segments.push({ kind: "child", name: bare[1], recursive });
1615
+ rest = rest.slice(bare[0].length);
1616
+ continue;
1617
+ }
1618
+ } else if (rest.startsWith(".")) {
1619
+ rest = rest.slice(1);
1620
+ if (rest.startsWith("*")) {
1621
+ segments.push({ kind: "wildcard", recursive });
1622
+ rest = rest.slice(1);
1623
+ continue;
1624
+ }
1625
+ const bare = rest.match(/^([A-Za-z_][\w-]*)/);
1626
+ if (bare) {
1627
+ segments.push({ kind: "child", name: bare[1], recursive });
1628
+ rest = rest.slice(bare[0].length);
1629
+ continue;
1630
+ }
1631
+ throw new OverlayError(`Invalid JSONPath segment after '.' in '${path}'`, { target: path });
1632
+ }
1633
+ if (!rest.startsWith("[")) {
1634
+ throw new OverlayError(`Invalid JSONPath segment at '${rest}' in '${path}'`, { target: path });
1635
+ }
1636
+ const bracket = matchBracket(rest, path);
1637
+ const inner = bracket.inner.trim();
1638
+ rest = bracket.rest;
1639
+ if (inner === "*") {
1640
+ segments.push({ kind: "wildcard", recursive });
1641
+ } else if (/^-?\d+$/.test(inner)) {
1642
+ segments.push({ kind: "index", index: parseInt(inner, 10), recursive });
1643
+ } else if (/^'.*'$/.test(inner) || /^".*"$/.test(inner)) {
1644
+ segments.push({ kind: "child", name: inner.slice(1, -1), recursive });
1645
+ } else if (inner.startsWith("?(") && inner.endsWith(")")) {
1646
+ segments.push(parseFilter(inner.slice(2, -1).trim(), path, recursive));
1647
+ } else {
1648
+ throw new OverlayError(`Unsupported JSONPath selector '[${inner}]' in '${path}'`, { target: path });
1649
+ }
1650
+ }
1651
+ return segments;
1652
+ }
1653
+ function matchBracket(input, fullPath) {
1654
+ let quote = null;
1655
+ let depth = 0;
1656
+ for (let i = 1; i < input.length; i++) {
1657
+ const char = input[i];
1658
+ if (quote) {
1659
+ if (char === quote) quote = null;
1660
+ } else if (char === "'" || char === '"') {
1661
+ quote = char;
1662
+ } else if (char === "[") {
1663
+ depth++;
1664
+ } else if (char === "]") {
1665
+ if (depth === 0) {
1666
+ return { inner: input.slice(1, i), rest: input.slice(i + 1) };
1667
+ }
1668
+ depth--;
1669
+ }
1670
+ }
1671
+ throw new OverlayError(`Unterminated '[' selector in '${fullPath}'`, { target: fullPath });
1672
+ }
1673
+ function parseFilter(expr, path, recursive) {
1674
+ const match = expr.match(/^@(?:\.([A-Za-z_][\w-]*)|\['([^']*)'\]|\["([^"]*)"\])\s*(?:(==|!=)\s*(.+))?$/);
1675
+ if (!match) {
1676
+ throw new OverlayError(`Unsupported filter expression '?(${expr})' in '${path}'`, { target: path });
1677
+ }
1678
+ const field = match[1] ?? match[2] ?? match[3];
1679
+ const op = match[4];
1680
+ if (!op) {
1681
+ return { kind: "filter", field, op: "exists", recursive };
1682
+ }
1683
+ const raw = match[5].trim();
1684
+ let literal;
1685
+ if (/^'.*'$/.test(raw) || /^".*"$/.test(raw)) {
1686
+ literal = raw.slice(1, -1);
1687
+ } else if (/^-?\d+(\.\d+)?$/.test(raw)) {
1688
+ literal = parseFloat(raw);
1689
+ } else if (raw === "true" || raw === "false") {
1690
+ literal = raw === "true";
1691
+ } else {
1692
+ throw new OverlayError(`Unsupported filter literal '${raw}' in '${path}'`, { target: path });
1693
+ }
1694
+ return { kind: "filter", field, op, literal, recursive };
1695
+ }
1696
+ function isContainer(value) {
1697
+ return value !== null && typeof value === "object";
1698
+ }
1699
+ var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1700
+ function descendants(match) {
1701
+ const result = [];
1702
+ const walk = (node) => {
1703
+ if (!isContainer(node)) return;
1704
+ if (Array.isArray(node)) {
1705
+ node.forEach((item, index) => {
1706
+ result.push({ parent: node, key: index, value: item });
1707
+ walk(item);
1708
+ });
1709
+ } else {
1710
+ for (const [key, value] of Object.entries(node)) {
1711
+ result.push({ parent: node, key, value });
1712
+ walk(value);
1713
+ }
1714
+ }
1715
+ };
1716
+ walk(match.value);
1717
+ return result;
1718
+ }
1719
+ function dedupeMatches(matches) {
1720
+ const seen = /* @__PURE__ */ new Map();
1721
+ const result = [];
1722
+ for (const match of matches) {
1723
+ let keys = seen.get(match.parent);
1724
+ if (!keys) {
1725
+ keys = /* @__PURE__ */ new Set();
1726
+ seen.set(match.parent, keys);
1727
+ }
1728
+ if (keys.has(match.key)) continue;
1729
+ keys.add(match.key);
1730
+ result.push(match);
1731
+ }
1732
+ return result;
1733
+ }
1734
+ function applySegment(matches, segment) {
1735
+ const scope = segment.recursive ? matches.flatMap((m) => [m, ...descendants(m)]) : matches;
1736
+ const next = [];
1737
+ for (const match of scope) {
1738
+ const node = match.value;
1739
+ switch (segment.kind) {
1740
+ case "child": {
1741
+ if (isContainer(node) && !Array.isArray(node) && !UNSAFE_KEYS.has(segment.name) && Object.prototype.hasOwnProperty.call(node, segment.name)) {
1742
+ next.push({ parent: node, key: segment.name, value: node[segment.name] });
1743
+ }
1744
+ break;
1745
+ }
1746
+ case "wildcard": {
1747
+ if (Array.isArray(node)) {
1748
+ node.forEach((item, index) => next.push({ parent: node, key: index, value: item }));
1749
+ } else if (isContainer(node)) {
1750
+ for (const [key, value] of Object.entries(node)) {
1751
+ next.push({ parent: node, key, value });
1752
+ }
1753
+ }
1754
+ break;
1755
+ }
1756
+ case "index": {
1757
+ if (Array.isArray(node)) {
1758
+ const index = segment.index < 0 ? node.length + segment.index : segment.index;
1759
+ if (index >= 0 && index < node.length) {
1760
+ next.push({ parent: node, key: index, value: node[index] });
1761
+ }
1762
+ }
1763
+ break;
1764
+ }
1765
+ case "filter": {
1766
+ const members = Array.isArray(node) ? node.map((item, index) => ({ parent: node, key: index, value: item })) : isContainer(node) ? Object.entries(node).map(([key, value]) => ({ parent: node, key, value })) : [];
1767
+ for (const member of members) {
1768
+ if (!isContainer(member.value) || Array.isArray(member.value)) continue;
1769
+ const fieldValue = member.value[segment.field];
1770
+ const keep = segment.op === "exists" ? fieldValue !== void 0 : segment.op === "==" ? fieldValue === segment.literal : fieldValue !== segment.literal;
1771
+ if (keep) next.push(member);
1772
+ }
1773
+ break;
1774
+ }
1775
+ }
1776
+ }
1777
+ return next;
1778
+ }
1779
+ function deepMerge(target, update) {
1780
+ for (const [key, value] of Object.entries(update)) {
1781
+ if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
1782
+ const existing = target[key];
1783
+ if (isContainer(value) && !Array.isArray(value) && isContainer(existing) && !Array.isArray(existing)) {
1784
+ deepMerge(existing, value);
1785
+ } else {
1786
+ target[key] = value;
1787
+ }
1788
+ }
1789
+ }
1790
+ function applyOverlay(document, overlay) {
1791
+ if (!overlay || typeof overlay !== "object" || !Array.isArray(overlay.actions)) {
1792
+ throw new OverlayError("Overlay document must have an actions array", {});
1793
+ }
1794
+ const result = JSON.parse(JSON.stringify(document));
1795
+ for (const [index, action] of overlay.actions.entries()) {
1796
+ if (!action || typeof action !== "object" || typeof action.target !== "string") {
1797
+ throw new OverlayError(`Overlay action #${index} must have a string target`, { index });
1798
+ }
1799
+ if (action.update === void 0 && action.remove !== true) {
1800
+ throw new OverlayError(`Overlay action #${index} needs 'update' or 'remove: true'`, {
1801
+ index,
1802
+ target: action.target
1803
+ });
1804
+ }
1805
+ const segments = parsePath(action.target);
1806
+ let matches = [{ parent: null, key: null, value: result }];
1807
+ for (const segment of segments) {
1808
+ matches = dedupeMatches(applySegment(matches, segment));
1809
+ }
1810
+ if (action.remove === true) {
1811
+ const arrayRemovals = /* @__PURE__ */ new Map();
1812
+ for (const match of matches) {
1813
+ if (match.parent === null) {
1814
+ throw new OverlayError("Overlay cannot remove the document root", { target: action.target });
1815
+ }
1816
+ if (Array.isArray(match.parent)) {
1817
+ const indices = arrayRemovals.get(match.parent) ?? [];
1818
+ indices.push(match.key);
1819
+ arrayRemovals.set(match.parent, indices);
1820
+ } else {
1821
+ delete match.parent[match.key];
1822
+ }
1823
+ }
1824
+ for (const [parent, indices] of arrayRemovals) {
1825
+ for (const index2 of indices.sort((a, b) => b - a)) {
1826
+ parent.splice(index2, 1);
1827
+ }
1828
+ }
1829
+ continue;
1830
+ }
1831
+ for (const match of matches) {
1832
+ const node = match.value;
1833
+ if (Array.isArray(node)) {
1834
+ node.push(action.update);
1835
+ } else if (isContainer(node) && isContainer(action.update) && !Array.isArray(action.update)) {
1836
+ deepMerge(node, action.update);
1837
+ } else {
1838
+ if (match.parent === null) {
1839
+ throw new OverlayError("Overlay cannot replace the document root with a non-object", {
1840
+ target: action.target
1841
+ });
1842
+ }
1843
+ match.parent[match.key] = action.update;
1844
+ }
1845
+ }
1846
+ }
1847
+ return result;
1848
+ }
1849
+
1850
+ // src/lint.ts
1851
+ var METHODS = ["get", "post", "put", "patch", "delete", "head", "options", "trace"];
1852
+ var PAGINATION_PARAM = /^(page|limit|offset|cursor|per_page|pagesize|page_size|after|before)$/i;
1853
+ var DEEP_SCHEMA_THRESHOLD = 8;
1854
+ var WIDE_SCHEMA_THRESHOLD = 30;
1855
+ function measureSchema(node, seen = /* @__PURE__ */ new Map()) {
1856
+ if (node === null || typeof node !== "object") {
1857
+ return { depth: 0, widestObject: 0, hasArray: false };
1858
+ }
1859
+ if (seen.has(node)) {
1860
+ return seen.get(node) ?? { depth: 0, widestObject: 0, hasArray: false };
1861
+ }
1862
+ seen.set(node, null);
1863
+ const record = node;
1864
+ let childDepth = 0;
1865
+ let widestObject = 0;
1866
+ let hasArray = record["type"] === "array" || Array.isArray(record["type"]) && record["type"].includes("array");
1867
+ const visit = (child) => {
1868
+ const shape2 = measureSchema(child, seen);
1869
+ childDepth = Math.max(childDepth, shape2.depth);
1870
+ widestObject = Math.max(widestObject, shape2.widestObject);
1871
+ hasArray = hasArray || shape2.hasArray;
1872
+ };
1873
+ const properties = record["properties"];
1874
+ if (properties && typeof properties === "object") {
1875
+ widestObject = Math.max(widestObject, Object.keys(properties).length);
1876
+ for (const child of Object.values(properties)) visit(child);
1877
+ }
1878
+ for (const key of ["items", "additionalProperties", "not", "contentSchema"]) {
1879
+ const value = record[key];
1880
+ if (value && typeof value === "object" && !Array.isArray(value)) visit(value);
1881
+ if (Array.isArray(value)) value.forEach(visit);
1882
+ }
1883
+ for (const key of ["allOf", "anyOf", "oneOf", "prefixItems"]) {
1884
+ const value = record[key];
1885
+ if (Array.isArray(value)) value.forEach(visit);
1886
+ }
1887
+ const shape = { depth: childDepth + 1, widestObject, hasArray };
1888
+ seen.set(node, shape);
1889
+ return shape;
1890
+ }
1891
+ function schemaHasExample(node, seen = /* @__PURE__ */ new Set()) {
1892
+ if (node === null || typeof node !== "object" || seen.has(node)) return false;
1893
+ seen.add(node);
1894
+ const record = node;
1895
+ if (record["example"] !== void 0 || record["examples"] !== void 0) return true;
1896
+ const properties = record["properties"];
1897
+ if (properties && typeof properties === "object") {
1898
+ if (Object.values(properties).some((child) => schemaHasExample(child, seen))) return true;
1899
+ }
1900
+ for (const key of ["items", "additionalProperties", "not", "contentSchema"]) {
1901
+ const value = record[key];
1902
+ if (value && typeof value === "object" && !Array.isArray(value) && schemaHasExample(value, seen)) return true;
1903
+ if (Array.isArray(value) && value.some((item) => schemaHasExample(item, seen))) return true;
1904
+ }
1905
+ for (const key of ["allOf", "anyOf", "oneOf", "prefixItems"]) {
1906
+ const value = record[key];
1907
+ if (Array.isArray(value) && value.some((member) => schemaHasExample(member, seen))) return true;
1908
+ }
1909
+ return false;
1910
+ }
1911
+ function hasAnyExample(content) {
1912
+ if (!content) return false;
1913
+ return Object.values(content).some((media) => {
1914
+ if (!media || typeof media !== "object") return false;
1915
+ const record = media;
1916
+ if (record["example"] !== void 0 || record["examples"] !== void 0) return true;
1917
+ return schemaHasExample(record["schema"]);
1918
+ });
1919
+ }
1920
+ function lintDocument(document) {
1921
+ const findings = [];
1922
+ const operationIds = /* @__PURE__ */ new Map();
1923
+ const paths = document.paths ?? {};
1924
+ for (const [pathStr, pathItem] of Object.entries(paths).sort(([a], [b]) => a < b ? -1 : 1)) {
1925
+ if (!pathItem || "$ref" in pathItem) continue;
1926
+ const pathLevelParameters = (pathItem["parameters"] ?? []).filter(
1927
+ (param) => !isReferenceObject(param)
1928
+ );
1929
+ for (const method of METHODS) {
1930
+ const operation = pathItem[method];
1931
+ if (!operation) continue;
1932
+ const label = `${method.toUpperCase()} ${pathStr}`;
1933
+ if (!operation.operationId) {
1934
+ findings.push({
1935
+ severity: "warning",
1936
+ code: "missing-operation-id",
1937
+ message: "Operation has no operationId; the tool name will be generated from the method and path.",
1938
+ path: label,
1939
+ hint: "Add a short, action-oriented operationId (it becomes the tool name)."
1940
+ });
1941
+ } else {
1942
+ const existing = operationIds.get(operation.operationId) ?? [];
1943
+ existing.push(label);
1944
+ operationIds.set(operation.operationId, existing);
1945
+ if (operation.operationId.length > 64) {
1946
+ findings.push({
1947
+ severity: "info",
1948
+ code: "long-operation-id",
1949
+ message: `operationId '${operation.operationId.slice(0, 40)}\u2026' exceeds 64 characters and will be truncated with a hash suffix.`,
1950
+ path: label,
1951
+ hint: "Shorten the operationId below 64 characters to keep tool names readable."
1952
+ });
1953
+ }
1954
+ }
1955
+ const prose = `${operation.summary ?? ""} ${operation.description ?? ""}`.trim();
1956
+ if (prose.length === 0) {
1957
+ findings.push({
1958
+ severity: "warning",
1959
+ code: "missing-description",
1960
+ message: "Operation has neither summary nor description; the model only sees the method and path.",
1961
+ path: label,
1962
+ hint: "Describe WHEN to use this operation and what it returns (or patch it in with an overlay)."
1963
+ });
1964
+ } else if (prose.length < 20) {
1965
+ findings.push({
1966
+ severity: "info",
1967
+ code: "vague-description",
1968
+ message: `Operation description is only ${prose.length} characters \u2014 likely too vague for reliable tool selection.`,
1969
+ path: label,
1970
+ hint: "Expand the description with the use case and key parameters."
1971
+ });
1972
+ }
1973
+ const parameters = [
1974
+ ...pathLevelParameters,
1975
+ ...(operation.parameters ?? []).filter((param) => !isReferenceObject(param))
1976
+ ];
1977
+ const undescribed = parameters.filter((param) => !param.description).map((param) => param.name);
1978
+ if (undescribed.length > 0) {
1979
+ findings.push({
1980
+ severity: "info",
1981
+ code: "missing-parameter-description",
1982
+ message: `Parameter(s) without description: ${undescribed.join(", ")}.`,
1983
+ path: label,
1984
+ hint: "Describe each parameter \u2014 models mis-fill undocumented arguments."
1985
+ });
1986
+ }
1987
+ const responses = operation.responses ?? {};
1988
+ const successCodes = Object.keys(responses).filter((code) => /^2(\d\d|XX)$/i.test(code));
1989
+ if (successCodes.length === 0 && !responses["default"]) {
1990
+ findings.push({
1991
+ severity: "warning",
1992
+ code: "missing-success-response",
1993
+ message: "Operation declares no 2xx or default response; no output schema can be generated.",
1994
+ path: label,
1995
+ hint: "Add the success response with its schema."
1996
+ });
1997
+ }
1998
+ let responseShape = { depth: 0, widestObject: 0, hasArray: false };
1999
+ for (const code of [...successCodes, "default"]) {
2000
+ const response = responses[code];
2001
+ if (!response || typeof response !== "object" || isReferenceObject(response)) continue;
2002
+ const content = response["content"];
2003
+ if (!content) continue;
2004
+ for (const media of Object.values(content)) {
2005
+ const schema = media && typeof media === "object" ? media["schema"] : void 0;
2006
+ const shape = measureSchema(schema);
2007
+ responseShape = {
2008
+ depth: Math.max(responseShape.depth, shape.depth),
2009
+ widestObject: Math.max(responseShape.widestObject, shape.widestObject),
2010
+ hasArray: responseShape.hasArray || shape.hasArray
2011
+ };
2012
+ }
2013
+ }
2014
+ if (method === "get" && responseShape.hasArray) {
2015
+ const hasPagination = parameters.some((param) => param.in === "query" && PAGINATION_PARAM.test(param.name));
2016
+ if (!hasPagination) {
2017
+ findings.push({
2018
+ severity: "warning",
2019
+ code: "unpaginated-list",
2020
+ message: "GET returns an array but declares no pagination parameter \u2014 responses can blow past client result limits (Claude Code caps tool results at 25K tokens).",
2021
+ path: label,
2022
+ hint: "Add limit/cursor/page parameters, or shape responses at the server."
2023
+ });
2024
+ }
2025
+ }
2026
+ const body = operation.requestBody;
2027
+ const bodyContent = body && !isReferenceObject(body) ? body.content : void 0;
2028
+ let requestShape = { depth: 0, widestObject: 0, hasArray: false };
2029
+ for (const media of Object.values(bodyContent ?? {})) {
2030
+ const schema = media && typeof media === "object" ? media["schema"] : void 0;
2031
+ const shape = measureSchema(schema);
2032
+ requestShape = {
2033
+ depth: Math.max(requestShape.depth, shape.depth),
2034
+ widestObject: Math.max(requestShape.widestObject, shape.widestObject),
2035
+ hasArray: requestShape.hasArray || shape.hasArray
2036
+ };
2037
+ }
2038
+ const maxDepth = Math.max(requestShape.depth, responseShape.depth);
2039
+ if (maxDepth > DEEP_SCHEMA_THRESHOLD) {
2040
+ findings.push({
2041
+ severity: "warning",
2042
+ code: "deep-schema",
2043
+ message: `Schema nesting reaches depth ${maxDepth} (threshold ${DEEP_SCHEMA_THRESHOLD}) \u2014 deep schemas cost tokens and reduce accuracy.`,
2044
+ path: label,
2045
+ hint: "Flatten the schema, or bound generation with maxSchemaDepth."
2046
+ });
2047
+ }
2048
+ const maxWidth = Math.max(requestShape.widestObject, responseShape.widestObject);
2049
+ if (maxWidth > WIDE_SCHEMA_THRESHOLD) {
2050
+ findings.push({
2051
+ severity: "info",
2052
+ code: "wide-schema",
2053
+ message: `An object schema declares ${maxWidth} properties (threshold ${WIDE_SCHEMA_THRESHOLD}).`,
2054
+ path: label,
2055
+ hint: "Split the payload, or bound generation with maxProperties."
2056
+ });
2057
+ }
2058
+ if (bodyContent && !hasAnyExample(bodyContent)) {
2059
+ findings.push({
2060
+ severity: "info",
2061
+ code: "missing-request-example",
2062
+ message: "Request body has no example \u2014 examples measurably improve complex-parameter accuracy.",
2063
+ path: label,
2064
+ hint: "Add a media-type example (and enable includeExamples), or patch one in with an overlay."
2065
+ });
2066
+ }
2067
+ }
2068
+ }
2069
+ for (const [operationId, labels] of operationIds) {
2070
+ if (labels.length > 1) {
2071
+ findings.push({
2072
+ severity: "error",
2073
+ code: "duplicate-operation-id",
2074
+ message: `operationId '${operationId}' is used by ${labels.length} operations: ${labels.join(", ")}.`,
2075
+ path: labels[0],
2076
+ hint: "Make operationIds unique \u2014 duplicates force hash-suffixed tool names."
2077
+ });
2078
+ }
2079
+ }
2080
+ const rank = { error: 0, warning: 1, info: 2 };
2081
+ findings.sort(
2082
+ (a, b) => rank[a.severity] - rank[b.severity] || (a.path < b.path ? -1 : a.path > b.path ? 1 : 0) || (a.code < b.code ? -1 : 1)
2083
+ );
2084
+ return {
2085
+ findings,
2086
+ counts: {
2087
+ error: findings.filter((f) => f.severity === "error").length,
2088
+ warning: findings.filter((f) => f.severity === "warning").length,
2089
+ info: findings.filter((f) => f.severity === "info").length
2090
+ }
2091
+ };
2092
+ }
2093
+
1448
2094
  // src/validator.ts
1449
2095
  var Validator = class {
1450
2096
  /**
@@ -1576,7 +2222,7 @@ var Validator = class {
1576
2222
  if (operation.parameters) {
1577
2223
  this.validateParameters(operation.parameters, path, method, errors, warnings);
1578
2224
  }
1579
- const pathParams = path.match(/\{([^}]+)\}/g)?.map((p) => p.slice(1, -1)) ?? [];
2225
+ const pathParams = path.match(/\{([^{}]+)\}/g)?.map((p) => p.slice(1, -1)) ?? [];
1580
2226
  const definedPathParams = new Set(
1581
2227
  operation.parameters?.filter((p) => p.in === "path").map((p) => p.name) ?? []
1582
2228
  );
@@ -1636,56 +2282,6 @@ var Validator = class {
1636
2282
  }
1637
2283
  };
1638
2284
 
1639
- // src/errors.ts
1640
- var OpenAPIToolError = class extends Error {
1641
- context;
1642
- constructor(message, context) {
1643
- super(message);
1644
- this.name = this.constructor.name;
1645
- this.context = context;
1646
- if (Error.captureStackTrace) {
1647
- Error.captureStackTrace(this, this.constructor);
1648
- }
1649
- }
1650
- };
1651
- var LoadError = class extends OpenAPIToolError {
1652
- constructor(message, context) {
1653
- super(message, context);
1654
- }
1655
- };
1656
- var SsrfError = class extends LoadError {
1657
- constructor(message, context) {
1658
- super(message, context);
1659
- }
1660
- };
1661
- var ParseError = class extends OpenAPIToolError {
1662
- constructor(message, context) {
1663
- super(message, context);
1664
- }
1665
- };
1666
- var ValidationError = class extends OpenAPIToolError {
1667
- errors;
1668
- constructor(message, context) {
1669
- super(message, context);
1670
- this.errors = context?.["errors"];
1671
- }
1672
- };
1673
- var GenerationError = class extends OpenAPIToolError {
1674
- constructor(message, context) {
1675
- super(message, context);
1676
- }
1677
- };
1678
- var RequestBuildError = class extends OpenAPIToolError {
1679
- constructor(message, context) {
1680
- super(message, context);
1681
- }
1682
- };
1683
- var SchemaError = class extends OpenAPIToolError {
1684
- constructor(message, context) {
1685
- super(message, context);
1686
- }
1687
- };
1688
-
1689
2285
  // src/format-resolver.ts
1690
2286
  var BUILTIN_FORMAT_RESOLVERS = {
1691
2287
  // String formats
@@ -2084,6 +2680,103 @@ function applySecureDefaults(options) {
2084
2680
  }
2085
2681
  };
2086
2682
  }
2683
+ function hasUnboundedArray(node, seen = /* @__PURE__ */ new Set()) {
2684
+ if (node === null || typeof node !== "object" || seen.has(node)) return false;
2685
+ seen.add(node);
2686
+ const record = node;
2687
+ const type = record["type"];
2688
+ const isArray = type === "array" || Array.isArray(type) && type.includes("array");
2689
+ if (isArray && record["maxItems"] === void 0) return true;
2690
+ const children = [];
2691
+ const properties = record["properties"];
2692
+ if (properties && typeof properties === "object") children.push(...Object.values(properties));
2693
+ for (const key of ["items", "additionalProperties", "contentSchema"]) {
2694
+ const value = record[key];
2695
+ if (Array.isArray(value)) children.push(...value);
2696
+ else if (value && typeof value === "object") children.push(value);
2697
+ }
2698
+ for (const key of ["allOf", "anyOf", "oneOf", "prefixItems"]) {
2699
+ if (Array.isArray(record[key])) children.push(...record[key]);
2700
+ }
2701
+ return children.some((child) => hasUnboundedArray(child, seen));
2702
+ }
2703
+ function detectResponseHints(outputSchema, mapper) {
2704
+ const paginationParams = [
2705
+ ...new Set(mapper.filter((m) => m.type === "query" && !m.security && PAGINATION_PARAM.test(m.key)).map((m) => m.key))
2706
+ ];
2707
+ const unboundedArray = outputSchema !== void 0 && hasUnboundedArray(outputSchema);
2708
+ if (!unboundedArray && paginationParams.length === 0) return void 0;
2709
+ return {
2710
+ ...unboundedArray && { unboundedArray: true },
2711
+ ...paginationParams.length > 0 && { paginationParams },
2712
+ ...unboundedArray && paginationParams.length === 0 && { largeResponseRisk: true }
2713
+ };
2714
+ }
2715
+ function composeDescription(operation, method, pathStr, strategy) {
2716
+ const fallback = `${method.toUpperCase()} ${pathStr}`;
2717
+ const summary = operation.summary?.trim();
2718
+ const description = operation.description?.trim();
2719
+ switch (strategy) {
2720
+ case "descriptionOnly":
2721
+ return description || summary || fallback;
2722
+ case "combined":
2723
+ if (summary && description && summary !== description) {
2724
+ return `${summary}
2725
+
2726
+ ${description}`;
2727
+ }
2728
+ return summary || description || fallback;
2729
+ case "full": {
2730
+ const parts = [];
2731
+ if (summary) parts.push(summary);
2732
+ if (description && description !== summary) parts.push(description);
2733
+ if (operation.operationId) parts.push(`Operation: ${operation.operationId}`);
2734
+ parts.push(fallback);
2735
+ return parts.join("\n\n");
2736
+ }
2737
+ default:
2738
+ return summary || description || fallback;
2739
+ }
2740
+ }
2741
+ function propertyNames(schema, cap = 8) {
2742
+ const properties = schema["properties"];
2743
+ if (!properties || typeof properties !== "object") return "";
2744
+ const names = Object.keys(properties);
2745
+ const listed = names.slice(0, cap).join(", ");
2746
+ return names.length > cap ? `${listed}, \u2026` : listed;
2747
+ }
2748
+ function summarizeOutputSchema(schema) {
2749
+ const record = schema;
2750
+ const variants = record["oneOf"];
2751
+ if (Array.isArray(variants) && variants.length > 0) {
2752
+ const first = variants[0];
2753
+ const firstSummary = first && typeof first === "object" ? summarizeOutputSchema(first) : void 0;
2754
+ return firstSummary ? `${firstSummary} (${variants.length} response variants)` : void 0;
2755
+ }
2756
+ const type = record["type"];
2757
+ if (type === "object" || type === void 0 && record["properties"]) {
2758
+ const names = propertyNames(record);
2759
+ return names ? `object with fields: ${names}` : "object";
2760
+ }
2761
+ if (type === "array") {
2762
+ const items = record["items"];
2763
+ if (items && typeof items === "object" && !Array.isArray(items)) {
2764
+ const itemRecord = items;
2765
+ if (itemRecord["type"] === "object" || itemRecord["properties"]) {
2766
+ const names = propertyNames(itemRecord);
2767
+ return names ? `array of objects with fields: ${names}` : "array of objects";
2768
+ }
2769
+ if (typeof itemRecord["type"] === "string") {
2770
+ return `array of ${itemRecord["type"]}`;
2771
+ }
2772
+ }
2773
+ return "array";
2774
+ }
2775
+ if (typeof type === "string" && type !== "null") {
2776
+ return type;
2777
+ }
2778
+ return void 0;
2779
+ }
2087
2780
  function globToRegExp(glob) {
2088
2781
  let pattern = "^";
2089
2782
  for (let i = 0; i < glob.length; i++) {
@@ -2106,6 +2799,13 @@ function globToRegExp(glob) {
2106
2799
  function matchesAnyGlob(path, globs) {
2107
2800
  return globs.some((glob) => globToRegExp(glob).test(path));
2108
2801
  }
2802
+ function trimUnderscores(value) {
2803
+ let start = 0;
2804
+ let end = value.length;
2805
+ while (start < end && value[start] === "_") start++;
2806
+ while (end > start && value[end - 1] === "_") end--;
2807
+ return value.slice(start, end);
2808
+ }
2109
2809
  function fnv1aHex(input) {
2110
2810
  let hash = 2166136261;
2111
2811
  for (let i = 0; i < input.length; i++) {
@@ -2116,7 +2816,7 @@ function fnv1aHex(input) {
2116
2816
  }
2117
2817
  function normalizeToolName(raw, maxLength, fallbackSeed) {
2118
2818
  let hashSeed = raw;
2119
- let name = raw.replace(/[^A-Za-z0-9_.-]/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
2819
+ let name = trimUnderscores(raw.replace(/[^A-Za-z0-9_.-]/g, "_").replace(/_+/g, "_"));
2120
2820
  if (name.length === 0) {
2121
2821
  hashSeed = fallbackSeed;
2122
2822
  name = `tool_${fnv1aHex(fallbackSeed)}`;
@@ -2149,8 +2849,15 @@ var OpenAPIToolGenerator = class _OpenAPIToolGenerator {
2149
2849
  validate: options.validate ?? true,
2150
2850
  followRedirects: options.followRedirects ?? true,
2151
2851
  refResolution: options.refResolution ?? {},
2152
- secureDefaults: options.secureDefaults ?? false
2852
+ secureDefaults: options.secureDefaults ?? false,
2853
+ overlays: options.overlays
2153
2854
  };
2855
+ if (this.options.overlays) {
2856
+ const overlays = Array.isArray(this.options.overlays) ? this.options.overlays : [this.options.overlays];
2857
+ for (const overlay of overlays) {
2858
+ this.document = applyOverlay(this.document, overlay);
2859
+ }
2860
+ }
2154
2861
  }
2155
2862
  /**
2156
2863
  * Create generator from a URL
@@ -2180,7 +2887,7 @@ var OpenAPIToolGenerator = class _OpenAPIToolGenerator {
2180
2887
  }
2181
2888
  return new _OpenAPIToolGenerator(document, options);
2182
2889
  } catch (error) {
2183
- if (error instanceof LoadError) {
2890
+ if (error instanceof LoadError || error instanceof OverlayError) {
2184
2891
  throw error;
2185
2892
  }
2186
2893
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -2213,6 +2920,9 @@ var OpenAPIToolGenerator = class _OpenAPIToolGenerator {
2213
2920
  }
2214
2921
  return new _OpenAPIToolGenerator(document, options);
2215
2922
  } catch (error) {
2923
+ if (error instanceof OverlayError) {
2924
+ throw error;
2925
+ }
2216
2926
  const errorMessage = error instanceof Error ? error.message : String(error);
2217
2927
  throw new LoadError(`Failed to load OpenAPI spec from file: ${errorMessage}`, {
2218
2928
  filePath,
@@ -2228,6 +2938,9 @@ var OpenAPIToolGenerator = class _OpenAPIToolGenerator {
2228
2938
  const document = yaml.parse(yamlString);
2229
2939
  return new _OpenAPIToolGenerator(document, options);
2230
2940
  } catch (error) {
2941
+ if (error instanceof OverlayError) {
2942
+ throw error;
2943
+ }
2231
2944
  const errorMessage = error instanceof Error ? error.message : String(error);
2232
2945
  throw new ParseError(`Failed to parse YAML: ${errorMessage}`, {
2233
2946
  originalError: error
@@ -2254,6 +2967,16 @@ var OpenAPIToolGenerator = class _OpenAPIToolGenerator {
2254
2967
  const validator = new Validator();
2255
2968
  return validator.validate(this.document);
2256
2969
  }
2970
+ /**
2971
+ * Lint the loaded document for agent-readiness (missing operationIds,
2972
+ * vague descriptions, unpaginated lists, oversized schemas, ...). Runs
2973
+ * after overlays and dereferencing so findings reflect what tools would
2974
+ * actually be generated from.
2975
+ */
2976
+ async lint() {
2977
+ await this.initialize(false);
2978
+ return lintDocument(this.getDocument());
2979
+ }
2257
2980
  // NOTE: internal/private-address blocking + IPv4-mapped-IPv6 decoding now live
2258
2981
  // in `ssrf.ts` (`isBlockedHostname` / `isBlockedAddress` / `decodeIpv4MappedIpv6`),
2259
2982
  // shared by the spec-URL fetch (`fromURL`) and the `$ref` resolver below, and
@@ -2391,7 +3114,7 @@ var OpenAPIToolGenerator = class _OpenAPIToolGenerator {
2391
3114
  /**
2392
3115
  * Initialize the generator (dereference if needed, then validate)
2393
3116
  */
2394
- async initialize() {
3117
+ async initialize(runValidation = this.options.validate) {
2395
3118
  if (this.options.dereference && !this.dereferencedDocument) {
2396
3119
  const cloned = JSON.parse(JSON.stringify(this.document));
2397
3120
  if (!_OpenAPIToolGenerator.hasExternalRefs(cloned)) {
@@ -2409,7 +3132,7 @@ var OpenAPIToolGenerator = class _OpenAPIToolGenerator {
2409
3132
  }
2410
3133
  }
2411
3134
  }
2412
- if (this.options.validate) {
3135
+ if (runValidation) {
2413
3136
  const validator = new Validator();
2414
3137
  const documentToValidate = this.dereferencedDocument ?? this.document;
2415
3138
  const result = await validator.validate(documentToValidate);
@@ -2507,7 +3230,7 @@ var OpenAPIToolGenerator = class _OpenAPIToolGenerator {
2507
3230
  const outputSchema = responseBuilder.build(operation.responses);
2508
3231
  const overrides = extractExtensionOverrides(operation);
2509
3232
  const name = this.generateToolName(pathStr, method, overrides.name ?? operation.operationId, options);
2510
- const description = overrides.description ?? (operation.summary || operation.description || `${method.toUpperCase()} ${pathStr}`);
3233
+ const description = overrides.description ?? composeDescription(operation, method, pathStr, options.descriptionStrategy ?? "summaryOnly");
2511
3234
  const title = overrides.title ?? operation.summary;
2512
3235
  const inferred = options.inferAnnotations !== false ? inferAnnotationsFromMethod(method.toLowerCase()) : void 0;
2513
3236
  const annotations = inferred || overrides.annotations ? { ...inferred, ...overrides.annotations } : void 0;
@@ -2524,16 +3247,57 @@ var OpenAPIToolGenerator = class _OpenAPIToolGenerator {
2524
3247
  if (resolvedOutputSchema) {
2525
3248
  resolvedOutputSchema = SchemaBuilder.truncateDepth(resolvedOutputSchema, maxSchemaDepth);
2526
3249
  }
3250
+ const applyTrim = (schema, isInputRoot) => {
3251
+ let trimmed = schema;
3252
+ if (options.stripExamples) trimmed = SchemaBuilder.stripExamples(trimmed);
3253
+ if (options.maxDescriptionLength !== void 0) {
3254
+ trimmed = SchemaBuilder.capDescriptions(trimmed, options.maxDescriptionLength);
3255
+ }
3256
+ if (options.maxProperties !== void 0) {
3257
+ if (isInputRoot) {
3258
+ const properties = trimmed.properties;
3259
+ if (properties && typeof properties === "object") {
3260
+ const limited = {};
3261
+ for (const [key, value] of Object.entries(properties)) {
3262
+ limited[key] = SchemaBuilder.limitProperties(value, options.maxProperties);
3263
+ }
3264
+ trimmed = { ...trimmed, properties: limited };
3265
+ }
3266
+ } else {
3267
+ trimmed = SchemaBuilder.limitProperties(trimmed, options.maxProperties);
3268
+ }
3269
+ }
3270
+ return trimmed;
3271
+ };
3272
+ if (options.stripExamples || options.maxProperties !== void 0 || options.maxDescriptionLength !== void 0) {
3273
+ resolvedInputSchema = applyTrim(resolvedInputSchema, true);
3274
+ if (resolvedOutputSchema) {
3275
+ resolvedOutputSchema = applyTrim(resolvedOutputSchema, false);
3276
+ }
3277
+ }
2527
3278
  if (options.target) {
2528
3279
  resolvedInputSchema = applyClientTarget(resolvedInputSchema, options.target);
2529
3280
  if (resolvedOutputSchema) {
2530
3281
  resolvedOutputSchema = applyClientTarget(resolvedOutputSchema, options.target);
2531
3282
  }
2532
3283
  }
3284
+ const responseHints = detectResponseHints(resolvedOutputSchema, mapper);
3285
+ if (responseHints) {
3286
+ metadata.responseHints = responseHints;
3287
+ }
3288
+ let finalDescription = description;
3289
+ if (options.appendResponseSummary && resolvedOutputSchema) {
3290
+ const summary = summarizeOutputSchema(resolvedOutputSchema);
3291
+ if (summary) {
3292
+ finalDescription = `${finalDescription}
3293
+
3294
+ Returns: ${summary}`;
3295
+ }
3296
+ }
2533
3297
  return {
2534
3298
  name,
2535
3299
  ...title !== void 0 && { title },
2536
- description,
3300
+ description: finalDescription,
2537
3301
  ...annotations && { annotations },
2538
3302
  inputSchema: resolvedInputSchema,
2539
3303
  outputSchema: resolvedOutputSchema,
@@ -2609,7 +3373,9 @@ var OpenAPIToolGenerator = class _OpenAPIToolGenerator {
2609
3373
  } else if (operationId) {
2610
3374
  rawName = operationId;
2611
3375
  } else {
2612
- const sanitized = path.replace(/\{([^}]+)\}/g, "By_$1").replace(/[^a-zA-Z0-9_]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
3376
+ const sanitized = trimUnderscores(
3377
+ path.replace(/\{([^{}]+)\}/g, "By_$1").replace(/[^a-zA-Z0-9_]/g, "_").replace(/_+/g, "_")
3378
+ );
2613
3379
  rawName = `${method}_${sanitized}`;
2614
3380
  }
2615
3381
  return normalizeToolName(
@@ -2824,7 +3590,7 @@ var SecurityResolver = class {
2824
3590
  resolveDigestAuth(context) {
2825
3591
  const digest = context.digest;
2826
3592
  if (!digest) return void 0;
2827
- const quoted = (v) => String(v).replace(/[\r\n]/g, "").replace(/"/g, '\\"');
3593
+ const quoted = (v) => String(v).replace(/[\r\n]/g, "").replace(/\\/g, "\\\\").replace(/"/g, '\\"');
2828
3594
  const token = (v) => String(v).replace(/[\r\n",]/g, "");
2829
3595
  const parts = [
2830
3596
  `username="${quoted(digest.username)}"`,
@@ -3285,6 +4051,44 @@ function toSdkTool(tool, wrapper) {
3285
4051
  }
3286
4052
  ];
3287
4053
  }
4054
+
4055
+ // src/token-report.ts
4056
+ function estimateToolTokens(tool) {
4057
+ const advertised = {
4058
+ name: tool.name,
4059
+ ...tool.title !== void 0 && { title: tool.title },
4060
+ description: tool.description,
4061
+ ...tool.annotations !== void 0 && { annotations: tool.annotations },
4062
+ inputSchema: tool.inputSchema,
4063
+ ...tool.outputSchema !== void 0 && { outputSchema: tool.outputSchema }
4064
+ };
4065
+ return Math.ceil(JSON.stringify(advertised).length / 4);
4066
+ }
4067
+ function analyzeToolSet(tools, options = {}) {
4068
+ const tokenBudget = options.tokenBudget ?? 1e4;
4069
+ const maxRecommendedTools = options.maxRecommendedTools ?? 40;
4070
+ const perToolWarning = options.perToolWarning ?? 2e3;
4071
+ const perTool = tools.map((tool) => ({ name: tool.name, tokens: estimateToolTokens(tool) })).sort((a, b) => b.tokens - a.tokens || (a.name < b.name ? -1 : 1));
4072
+ const estimatedTokens = perTool.reduce((sum, entry) => sum + entry.tokens, 0);
4073
+ const warnings = [];
4074
+ if (tools.length > maxRecommendedTools) {
4075
+ warnings.push(
4076
+ `${tools.length} tools exceeds the ~${maxRecommendedTools}-tool range where model selection accuracy degrades \u2014 curate with filters (tags, paths, readOnlyOnly) or split into focused servers.`
4077
+ );
4078
+ }
4079
+ if (estimatedTokens > tokenBudget) {
4080
+ warnings.push(
4081
+ `Estimated ${estimatedTokens} tokens of tool definitions exceeds the ${tokenBudget}-token budget \u2014 trim schemas (maxSchemaDepth, maxProperties) or reduce the tool count.`
4082
+ );
4083
+ }
4084
+ const heavy = perTool.filter((entry) => entry.tokens > perToolWarning);
4085
+ if (heavy.length > 0) {
4086
+ warnings.push(
4087
+ `${heavy.length} tool(s) exceed ${perToolWarning} tokens each (${heavy.slice(0, 3).map((entry) => `${entry.name}: ~${entry.tokens}`).join(", ")}${heavy.length > 3 ? ", \u2026" : ""}) \u2014 consider schema trimming for these.`
4088
+ );
4089
+ }
4090
+ return { toolCount: tools.length, estimatedTokens, perTool, warnings };
4091
+ }
3288
4092
  // Annotate the CommonJS export names for ESM import in node:
3289
4093
  0 && (module.exports = {
3290
4094
  BLOCKED_HOSTNAMES,
@@ -3293,6 +4097,7 @@ function toSdkTool(tool, wrapper) {
3293
4097
  LoadError,
3294
4098
  OpenAPIToolError,
3295
4099
  OpenAPIToolGenerator,
4100
+ OverlayError,
3296
4101
  ParameterResolver,
3297
4102
  ParseError,
3298
4103
  RequestBuildError,
@@ -3303,7 +4108,9 @@ function toSdkTool(tool, wrapper) {
3303
4108
  SsrfError,
3304
4109
  ValidationError,
3305
4110
  Validator,
4111
+ analyzeToolSet,
3306
4112
  applyClientTarget,
4113
+ applyOverlay,
3307
4114
  assertUrlSafe,
3308
4115
  buildHttpRequest,
3309
4116
  collapseNestedUnions,
@@ -3314,12 +4121,14 @@ function toSdkTool(tool, wrapper) {
3314
4121
  demoteFormats,
3315
4122
  enforceClosedObjects,
3316
4123
  ensureArrayItems,
4124
+ estimateToolTokens,
3317
4125
  extractExtensionOverrides,
3318
4126
  inferAnnotationsFromMethod,
3319
4127
  inlineLocalRefs,
3320
4128
  isBlockedAddress,
3321
4129
  isBlockedHostname,
3322
4130
  isReferenceObject,
4131
+ lintDocument,
3323
4132
  normalizeSsrfOptions,
3324
4133
  requireAllProperties,
3325
4134
  resolveExtensionEnabled,