@frontmcp/adapters 0.12.2 → 1.0.0-beta.10

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/openapi/index.js CHANGED
@@ -29,6 +29,8 @@ var __decorateClass = (decorators, target, key, kind) => {
29
29
  var openapi_exports = {};
30
30
  __export(openapi_exports, {
31
31
  FRONTMCP_EXTENSION_KEY: () => FRONTMCP_EXTENSION_KEY,
32
+ OpenAPIFetchError: () => OpenAPIFetchError,
33
+ OpenApiSpecPoller: () => OpenApiSpecPoller,
32
34
  default: () => OpenapiAdapter,
33
35
  forceJwtSecurity: () => forceJwtSecurity,
34
36
  removeSecurityFromOperations: () => removeSecurityFromOperations
@@ -36,16 +38,17 @@ __export(openapi_exports, {
36
38
  module.exports = __toCommonJS(openapi_exports);
37
39
 
38
40
  // libs/adapters/src/openapi/openapi.adapter.ts
39
- var import_sdk2 = require("@frontmcp/sdk");
41
+ var import_sdk4 = require("@frontmcp/sdk");
40
42
  var import_mcp_from_openapi2 = require("mcp-from-openapi");
41
43
 
42
44
  // libs/adapters/src/openapi/openapi.tool.ts
43
45
  var import_zod2 = require("zod");
44
- var import_sdk = require("@frontmcp/sdk");
46
+ var import_sdk2 = require("@frontmcp/sdk");
45
47
  var import_zod_from_json_schema = require("zod-from-json-schema");
46
48
 
47
49
  // libs/adapters/src/openapi/openapi.utils.ts
48
50
  var import_utils = require("@frontmcp/utils");
51
+ var import_sdk = require("@frontmcp/sdk");
49
52
  function coerceToString(value, paramName, location) {
50
53
  if (value === null || value === void 0) {
51
54
  return "";
@@ -102,8 +105,10 @@ function buildRequest(tool2, input, security, baseUrl) {
102
105
  case "header": {
103
106
  const headerValue = coerceToString(value, mapper.key, "header");
104
107
  if (/[\r\n\x00\f\v]/.test(headerValue)) {
105
- throw new Error(
106
- `Invalid header value for '${mapper.key}': contains control characters (possible header injection attack)`
108
+ throw new import_sdk.PublicMcpError(
109
+ `Invalid header value for '${mapper.key}': contains control characters (possible header injection attack)`,
110
+ "INVALID_HEADER_VALUE",
111
+ 400
107
112
  );
108
113
  }
109
114
  headers.set(mapper.key, headerValue);
@@ -683,7 +688,7 @@ function createOpenApiTool(openapiTool, options, logger) {
683
688
  if (toolTransform.ui) {
684
689
  toolMetadata["ui"] = toolTransform.ui;
685
690
  }
686
- return (0, import_sdk.tool)(toolMetadata)(async (input, toolCtx) => {
691
+ return (0, import_sdk2.tool)(toolMetadata)(async (input, toolCtx) => {
687
692
  const ctx = toolCtx.context;
688
693
  const transformContext = {
689
694
  ctx,
@@ -886,6 +891,174 @@ function getZodSchemaFromJsonSchema(jsonSchema, toolName, logger) {
886
891
  }
887
892
  }
888
893
 
894
+ // libs/adapters/src/openapi/openapi-spec-poller.ts
895
+ var import_utils2 = require("@frontmcp/utils");
896
+
897
+ // libs/adapters/src/openapi/openapi.errors.ts
898
+ var import_sdk3 = require("@frontmcp/sdk");
899
+ var OpenAPIFetchError = class extends import_sdk3.InternalMcpError {
900
+ constructor(url, status, statusText) {
901
+ super(`OpenAPI spec fetch failed from "${url}": ${status} ${statusText}`, "OPENAPI_FETCH_FAILED");
902
+ }
903
+ };
904
+
905
+ // libs/adapters/src/openapi/openapi-spec-poller.ts
906
+ var DEFAULT_RETRY = {
907
+ maxRetries: 3,
908
+ initialDelayMs: 1e3,
909
+ maxDelayMs: 1e4,
910
+ backoffMultiplier: 2
911
+ };
912
+ var OpenApiSpecPoller = class {
913
+ url;
914
+ intervalMs;
915
+ fetchTimeoutMs;
916
+ changeDetection;
917
+ retry;
918
+ unhealthyThreshold;
919
+ headers;
920
+ callbacks;
921
+ intervalTimer = null;
922
+ lastHash = null;
923
+ lastEtag = null;
924
+ lastModified = null;
925
+ consecutiveFailures = 0;
926
+ isPolling = false;
927
+ wasUnhealthy = false;
928
+ health = "unknown";
929
+ constructor(url, options = {}, callbacks = {}) {
930
+ this.url = url;
931
+ this.intervalMs = options.intervalMs ?? 6e4;
932
+ this.fetchTimeoutMs = options.fetchTimeoutMs ?? 1e4;
933
+ this.changeDetection = options.changeDetection ?? "auto";
934
+ this.retry = { ...DEFAULT_RETRY, ...options.retry };
935
+ this.unhealthyThreshold = options.unhealthyThreshold ?? 3;
936
+ this.headers = options.headers ?? {};
937
+ this.callbacks = callbacks;
938
+ }
939
+ /**
940
+ * Start polling for changes.
941
+ */
942
+ start() {
943
+ if (this.intervalTimer) return;
944
+ this.poll().catch(() => {
945
+ });
946
+ this.intervalTimer = setInterval(() => {
947
+ this.poll().catch(() => {
948
+ });
949
+ }, this.intervalMs);
950
+ }
951
+ /**
952
+ * Stop polling.
953
+ */
954
+ stop() {
955
+ if (this.intervalTimer) {
956
+ clearInterval(this.intervalTimer);
957
+ this.intervalTimer = null;
958
+ }
959
+ }
960
+ /**
961
+ * Get current stats.
962
+ */
963
+ getStats() {
964
+ return {
965
+ hash: this.lastHash,
966
+ consecutiveFailures: this.consecutiveFailures,
967
+ health: this.health,
968
+ isRunning: this.intervalTimer !== null
969
+ };
970
+ }
971
+ /**
972
+ * Perform a single poll cycle.
973
+ */
974
+ async poll() {
975
+ if (this.isPolling) return;
976
+ this.isPolling = true;
977
+ try {
978
+ await this.fetchWithRetry();
979
+ } finally {
980
+ this.isPolling = false;
981
+ }
982
+ }
983
+ async fetchWithRetry() {
984
+ const { maxRetries, initialDelayMs, maxDelayMs, backoffMultiplier } = this.retry;
985
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
986
+ try {
987
+ await this.doFetch();
988
+ if (this.consecutiveFailures > 0) {
989
+ this.consecutiveFailures = 0;
990
+ if (this.wasUnhealthy) {
991
+ this.wasUnhealthy = false;
992
+ this.health = "healthy";
993
+ this.callbacks.onRecovered?.();
994
+ }
995
+ }
996
+ this.health = "healthy";
997
+ return;
998
+ } catch (error) {
999
+ if (attempt === maxRetries) {
1000
+ this.consecutiveFailures++;
1001
+ this.callbacks.onError?.(error instanceof Error ? error : new Error(String(error)));
1002
+ if (this.consecutiveFailures >= this.unhealthyThreshold && !this.wasUnhealthy) {
1003
+ this.wasUnhealthy = true;
1004
+ this.health = "unhealthy";
1005
+ this.callbacks.onUnhealthy?.(this.consecutiveFailures);
1006
+ }
1007
+ return;
1008
+ }
1009
+ const delay = Math.min(initialDelayMs * Math.pow(backoffMultiplier, attempt), maxDelayMs);
1010
+ await new Promise((r) => setTimeout(r, delay));
1011
+ }
1012
+ }
1013
+ }
1014
+ async doFetch() {
1015
+ const headers = { ...this.headers };
1016
+ if (this.changeDetection !== "content-hash") {
1017
+ if (this.lastEtag) {
1018
+ headers["If-None-Match"] = this.lastEtag;
1019
+ }
1020
+ if (this.lastModified) {
1021
+ headers["If-Modified-Since"] = this.lastModified;
1022
+ }
1023
+ }
1024
+ const controller = new AbortController();
1025
+ const timeout = setTimeout(() => controller.abort(), this.fetchTimeoutMs);
1026
+ try {
1027
+ const response = await fetch(this.url, {
1028
+ headers,
1029
+ signal: controller.signal
1030
+ });
1031
+ if (response.status === 304) {
1032
+ this.callbacks.onUnchanged?.();
1033
+ return;
1034
+ }
1035
+ if (!response.ok) {
1036
+ throw new OpenAPIFetchError(this.url, response.status, response.statusText);
1037
+ }
1038
+ const etag = response.headers.get("etag");
1039
+ const lastModified = response.headers.get("last-modified");
1040
+ if (etag) this.lastEtag = etag;
1041
+ if (lastModified) this.lastModified = lastModified;
1042
+ const body = await response.text();
1043
+ const hash = (0, import_utils2.sha256Hex)(body);
1044
+ if (this.lastHash && this.lastHash === hash) {
1045
+ this.callbacks.onUnchanged?.();
1046
+ return;
1047
+ }
1048
+ this.lastHash = hash;
1049
+ this.callbacks.onChanged?.(body, hash);
1050
+ } finally {
1051
+ clearTimeout(timeout);
1052
+ }
1053
+ }
1054
+ /**
1055
+ * Dispose the poller and release resources.
1056
+ */
1057
+ dispose() {
1058
+ this.stop();
1059
+ }
1060
+ };
1061
+
889
1062
  // libs/adapters/src/openapi/openapi.adapter.ts
890
1063
  var RESERVED_KEYS2 = ["__proto__", "constructor", "prototype"];
891
1064
  function createConsoleLogger(prefix) {
@@ -899,14 +1072,22 @@ function createConsoleLogger(prefix) {
899
1072
  child: (childPrefix) => createConsoleLogger(`${prefix}:${childPrefix}`)
900
1073
  };
901
1074
  }
902
- var OpenapiAdapter = class extends import_sdk2.DynamicAdapter {
1075
+ var OpenapiAdapter = class extends import_sdk4.DynamicAdapter {
903
1076
  generator;
904
1077
  logger;
905
1078
  options;
1079
+ poller = null;
1080
+ updateCallbacks = /* @__PURE__ */ new Set();
1081
+ rebuildChain = Promise.resolve();
906
1082
  constructor(options) {
907
1083
  super();
908
1084
  this.options = options;
909
1085
  this.logger = options.logger ?? createConsoleLogger(`openapi:${options.name}`);
1086
+ if (options.polling?.enabled && !("url" in options)) {
1087
+ throw new Error(
1088
+ `[OpenAPI Adapter: ${options.name}] Polling requires URL-based options (use 'url' instead of 'spec').`
1089
+ );
1090
+ }
910
1091
  }
911
1092
  /**
912
1093
  * Receive the SDK logger. Called by the SDK before fetch().
@@ -982,13 +1163,31 @@ Add one of the following to your adapter configuration:
982
1163
  if (this.options.toolTransforms) {
983
1164
  transformedTools = transformedTools.map((tool2) => this.applyToolTransforms(tool2));
984
1165
  }
1166
+ const nameMap = /* @__PURE__ */ new Map();
1167
+ for (const tool2 of transformedTools) {
1168
+ const meta = tool2.metadata;
1169
+ const source = `${meta["method"]?.toUpperCase() ?? "?"} ${meta["path"] ?? "?"}`;
1170
+ const existing = nameMap.get(tool2.name);
1171
+ if (existing) {
1172
+ existing.push(source);
1173
+ } else {
1174
+ nameMap.set(tool2.name, [source]);
1175
+ }
1176
+ }
1177
+ for (const [name, sources] of nameMap) {
1178
+ if (sources.length > 1) {
1179
+ throw new Error(
1180
+ `Tool name collision: "${name}" produced by ${sources.length} operations: ${sources.join(", ")}. Rename conflicting operations in your OpenAPI spec or use toolTransforms.perTool to assign unique names.`
1181
+ );
1182
+ }
1183
+ }
985
1184
  if (this.options.inputTransforms) {
986
1185
  transformedTools = transformedTools.map((tool2) => this.applyInputTransforms(tool2));
987
1186
  }
988
1187
  if (this.options.schemaTransforms) {
989
1188
  transformedTools = transformedTools.map((tool2) => this.applySchemaTransforms(tool2));
990
1189
  }
991
- const dataTransforms = this.options.dataTransforms || this.options.outputTransforms;
1190
+ const dataTransforms = this.options.dataTransforms;
992
1191
  if (this.options.outputSchema || dataTransforms) {
993
1192
  transformedTools = await Promise.all(
994
1193
  transformedTools.map((tool2) => this.applyOutputSchemaOptions(tool2, dataTransforms))
@@ -1064,6 +1263,15 @@ ${opDescription}`;
1064
1263
  description
1065
1264
  };
1066
1265
  }
1266
+ /**
1267
+ * Look up a value in a perTool map by tool name, using own-property check.
1268
+ * @private
1269
+ */
1270
+ resolvePerTool(perTool, tool2) {
1271
+ if (!perTool) return void 0;
1272
+ if (Object.prototype.hasOwnProperty.call(perTool, tool2.name)) return perTool[tool2.name];
1273
+ return void 0;
1274
+ }
1067
1275
  /**
1068
1276
  * Collect tool transforms for a specific tool
1069
1277
  * @private
@@ -1084,8 +1292,9 @@ ${opDescription}`;
1084
1292
  result.examples = [...opts.global.examples];
1085
1293
  }
1086
1294
  }
1087
- if (opts.perTool?.[tool2.name]) {
1088
- const perTool = opts.perTool[tool2.name];
1295
+ const perToolMatch = this.resolvePerTool(opts.perTool, tool2);
1296
+ if (perToolMatch) {
1297
+ const perTool = perToolMatch;
1089
1298
  if (perTool.name) result.name = perTool.name;
1090
1299
  if (perTool.description) result.description = perTool.description;
1091
1300
  if (perTool.hideFromDiscovery !== void 0) result.hideFromDiscovery = perTool.hideFromDiscovery;
@@ -1162,8 +1371,9 @@ ${opDescription}`;
1162
1371
  if (opts.global) {
1163
1372
  transforms.push(...opts.global);
1164
1373
  }
1165
- if (opts.perTool?.[tool2.name]) {
1166
- transforms.push(...opts.perTool[tool2.name]);
1374
+ const perToolInputTransforms = this.resolvePerTool(opts.perTool, tool2);
1375
+ if (perToolInputTransforms) {
1376
+ transforms.push(...perToolInputTransforms);
1167
1377
  }
1168
1378
  if (opts.generator) {
1169
1379
  transforms.push(...opts.generator(tool2));
@@ -1310,8 +1520,9 @@ ${opDescription}`;
1310
1520
  const generated = opts.generator(tool2);
1311
1521
  if (generated) return generated;
1312
1522
  }
1313
- if (opts.perTool?.[tool2.name]) {
1314
- return opts.perTool[tool2.name];
1523
+ const perToolInputSchema = this.resolvePerTool(opts.perTool, tool2);
1524
+ if (perToolInputSchema) {
1525
+ return perToolInputSchema;
1315
1526
  }
1316
1527
  return opts.global;
1317
1528
  }
@@ -1326,8 +1537,9 @@ ${opDescription}`;
1326
1537
  const generated = opts.generator(tool2);
1327
1538
  if (generated) return generated;
1328
1539
  }
1329
- if (opts.perTool?.[tool2.name]) {
1330
- return opts.perTool[tool2.name];
1540
+ const perToolOutputSchema = this.resolvePerTool(opts.perTool, tool2);
1541
+ if (perToolOutputSchema) {
1542
+ return perToolOutputSchema;
1331
1543
  }
1332
1544
  return opts.global;
1333
1545
  }
@@ -1429,8 +1641,9 @@ ${this.formatSchemaAsSummary(schema)}`;
1429
1641
  if (opts.global) {
1430
1642
  result = { ...opts.global };
1431
1643
  }
1432
- if (opts.perTool?.[tool2.name]) {
1433
- result = { ...result, ...opts.perTool[tool2.name] };
1644
+ const perToolPre = this.resolvePerTool(opts.perTool, tool2);
1645
+ if (perToolPre) {
1646
+ result = { ...result, ...perToolPre };
1434
1647
  }
1435
1648
  if (opts.generator) {
1436
1649
  const generated = opts.generator(tool2);
@@ -1451,12 +1664,12 @@ ${this.formatSchemaAsSummary(schema)}`;
1451
1664
  if (opts.global) {
1452
1665
  result = { ...opts.global };
1453
1666
  }
1454
- if (opts.perTool?.[tool2.name]) {
1455
- const perTool = opts.perTool[tool2.name];
1667
+ const perToolPost = this.resolvePerTool(opts.perTool, tool2);
1668
+ if (perToolPost) {
1456
1669
  result = result ? {
1457
- transform: perTool.transform,
1458
- filter: perTool.filter ?? result.filter
1459
- } : perTool;
1670
+ transform: perToolPost.transform,
1671
+ filter: perToolPost.filter ?? result.filter
1672
+ } : perToolPost;
1460
1673
  }
1461
1674
  if (opts.generator) {
1462
1675
  const generated = opts.generator(tool2);
@@ -1508,9 +1721,69 @@ ${this.formatSchemaAsSummary(schema)}`;
1508
1721
  }
1509
1722
  return schema.type || "any";
1510
1723
  }
1724
+ // ============================================================================
1725
+ // Polling Lifecycle
1726
+ // ============================================================================
1727
+ /**
1728
+ * Register a callback for when the adapter's tools are updated via polling.
1729
+ * Returns an unsubscribe function.
1730
+ */
1731
+ onUpdate(callback) {
1732
+ this.updateCallbacks.add(callback);
1733
+ return () => {
1734
+ this.updateCallbacks.delete(callback);
1735
+ };
1736
+ }
1737
+ /**
1738
+ * Start polling for spec changes.
1739
+ * Creates the poller and on spec change, re-runs fetch() and calls the update callback.
1740
+ * Rebuilds are serialized via a Promise chain to prevent races.
1741
+ */
1742
+ startPolling() {
1743
+ if (this.poller) return;
1744
+ const polling = this.options.polling;
1745
+ if (!polling?.enabled || !("url" in this.options)) return;
1746
+ this.poller = new OpenApiSpecPoller(this.options.url, polling, {
1747
+ onChanged: (_spec, _hash) => {
1748
+ this.rebuildChain = this.rebuildChain.then(async () => {
1749
+ const previousGenerator = this.generator;
1750
+ try {
1751
+ this.generator = void 0;
1752
+ const response = await this.fetch();
1753
+ this.updateCallbacks.forEach((cb) => cb(response));
1754
+ this.logger.info("OpenAPI spec updated, tools rebuilt");
1755
+ } catch (error) {
1756
+ this.generator = previousGenerator;
1757
+ this.logger.error(`Failed to rebuild tools after spec change: ${error.message}`);
1758
+ }
1759
+ });
1760
+ },
1761
+ onError: (error) => {
1762
+ this.logger.warn(`OpenAPI spec poll error: ${error.message}`);
1763
+ },
1764
+ onUnhealthy: (failures) => {
1765
+ this.logger.error(`OpenAPI spec poller unhealthy after ${failures} consecutive failures`);
1766
+ },
1767
+ onRecovered: () => {
1768
+ this.logger.info("OpenAPI spec poller recovered");
1769
+ }
1770
+ });
1771
+ this.poller.start();
1772
+ this.logger.info(`Started polling OpenAPI spec at ${this.options.polling?.intervalMs ?? 6e4}ms intervals`);
1773
+ }
1774
+ /**
1775
+ * Stop polling for spec changes.
1776
+ */
1777
+ stopPolling() {
1778
+ if (this.poller) {
1779
+ this.poller.dispose();
1780
+ this.poller = null;
1781
+ this.logger.info("Stopped polling OpenAPI spec");
1782
+ }
1783
+ }
1511
1784
  };
1512
1785
  OpenapiAdapter = __decorateClass([
1513
- (0, import_sdk2.Adapter)({
1786
+ (0, import_sdk4.Adapter)({
1514
1787
  name: "openapi",
1515
1788
  description: "OpenAPI adapter for FrontMCP - Automatically generates MCP tools from OpenAPI specifications"
1516
1789
  })
@@ -1622,6 +1895,8 @@ function removeSecurityFromOperations(spec, operations) {
1622
1895
  // Annotate the CommonJS export names for ESM import in node:
1623
1896
  0 && (module.exports = {
1624
1897
  FRONTMCP_EXTENSION_KEY,
1898
+ OpenAPIFetchError,
1899
+ OpenApiSpecPoller,
1625
1900
  forceJwtSecurity,
1626
1901
  removeSecurityFromOperations
1627
1902
  });
@@ -0,0 +1,56 @@
1
+ /**
2
+ * OpenAPI Spec Poller
3
+ *
4
+ * Polls an OpenAPI specification URL for changes using content-hash detection.
5
+ * Uses @frontmcp/utils for crypto operations (sha256Hex) and follows the
6
+ * HealthChecker pattern from libs/sdk/src/remote-mcp/resilience/health-check.ts.
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+ import type { SpecPollerOptions, SpecPollerCallbacks, SpecPollerStats } from './openapi-spec-poller.types';
11
+ /**
12
+ * Polls an OpenAPI spec URL for changes using content-hash and ETag detection.
13
+ * Follows the callback-based emitter pattern (like ToolEmitter).
14
+ */
15
+ export declare class OpenApiSpecPoller {
16
+ private readonly url;
17
+ private readonly intervalMs;
18
+ private readonly fetchTimeoutMs;
19
+ private readonly changeDetection;
20
+ private readonly retry;
21
+ private readonly unhealthyThreshold;
22
+ private readonly headers;
23
+ private readonly callbacks;
24
+ private intervalTimer;
25
+ private lastHash;
26
+ private lastEtag;
27
+ private lastModified;
28
+ private consecutiveFailures;
29
+ private isPolling;
30
+ private wasUnhealthy;
31
+ private health;
32
+ constructor(url: string, options?: SpecPollerOptions, callbacks?: SpecPollerCallbacks);
33
+ /**
34
+ * Start polling for changes.
35
+ */
36
+ start(): void;
37
+ /**
38
+ * Stop polling.
39
+ */
40
+ stop(): void;
41
+ /**
42
+ * Get current stats.
43
+ */
44
+ getStats(): SpecPollerStats;
45
+ /**
46
+ * Perform a single poll cycle.
47
+ */
48
+ poll(): Promise<void>;
49
+ private fetchWithRetry;
50
+ private doFetch;
51
+ /**
52
+ * Dispose the poller and release resources.
53
+ */
54
+ dispose(): void;
55
+ }
56
+ //# sourceMappingURL=openapi-spec-poller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi-spec-poller.d.ts","sourceRoot":"","sources":["../../src/openapi/openapi-spec-poller.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EACV,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EAGhB,MAAM,6BAA6B,CAAC;AAUrC;;;GAGG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAmC;IACnE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkC;IACxD,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;IACjD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsB;IAEhD,OAAO,CAAC,aAAa,CAA+C;IACpE,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,mBAAmB,CAAK;IAChC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,MAAM,CAAiC;gBAEnC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB,EAAE,SAAS,GAAE,mBAAwB;IAW7F;;OAEG;IACH,KAAK,IAAI,IAAI;IAWb;;OAEG;IACH,IAAI,IAAI,IAAI;IAOZ;;OAEG;IACH,QAAQ,IAAI,eAAe;IAS3B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAWb,cAAc;YAoCd,OAAO;IAqDrB;;OAEG;IACH,OAAO,IAAI,IAAI;CAGhB"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * OpenAPI Spec Poller Types
3
+ *
4
+ * Type definitions for the OpenAPI specification polling system.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ /**
9
+ * Change detection strategy.
10
+ */
11
+ export type SpecChangeDetection = 'content-hash' | 'etag' | 'auto';
12
+ /**
13
+ * Retry configuration for failed polls.
14
+ */
15
+ export interface SpecPollerRetryConfig {
16
+ /** Maximum number of retries per poll cycle @default 3 */
17
+ maxRetries?: number;
18
+ /** Initial delay before first retry in ms @default 1000 */
19
+ initialDelayMs?: number;
20
+ /** Maximum delay between retries in ms @default 10000 */
21
+ maxDelayMs?: number;
22
+ /** Backoff multiplier @default 2 */
23
+ backoffMultiplier?: number;
24
+ }
25
+ /**
26
+ * Health status of the poller.
27
+ */
28
+ export type PollerHealthStatus = 'healthy' | 'unhealthy' | 'unknown';
29
+ /**
30
+ * Configuration for the OpenAPI spec poller.
31
+ */
32
+ export interface SpecPollerOptions {
33
+ /** Poll interval in milliseconds @default 60000 */
34
+ intervalMs?: number;
35
+ /** Fetch timeout in milliseconds @default 10000 */
36
+ fetchTimeoutMs?: number;
37
+ /** Change detection strategy @default 'auto' */
38
+ changeDetection?: SpecChangeDetection;
39
+ /** Retry configuration */
40
+ retry?: SpecPollerRetryConfig;
41
+ /** Number of consecutive failures before marking unhealthy @default 3 */
42
+ unhealthyThreshold?: number;
43
+ /** Additional headers for fetch requests */
44
+ headers?: Record<string, string>;
45
+ }
46
+ /**
47
+ * Callback types for spec poller events.
48
+ */
49
+ export interface SpecPollerCallbacks {
50
+ /** Called when the spec content has changed */
51
+ onChanged?: (spec: string, hash: string) => void;
52
+ /** Called when the spec hasn't changed */
53
+ onUnchanged?: () => void;
54
+ /** Called on poll error */
55
+ onError?: (error: Error) => void;
56
+ /** Called when poller becomes unhealthy */
57
+ onUnhealthy?: (consecutiveFailures: number) => void;
58
+ /** Called when poller recovers from unhealthy state */
59
+ onRecovered?: () => void;
60
+ }
61
+ /**
62
+ * Spec poller stats for monitoring.
63
+ */
64
+ export interface SpecPollerStats {
65
+ /** Current content hash */
66
+ hash: string | null;
67
+ /** Number of consecutive failures */
68
+ consecutiveFailures: number;
69
+ /** Health status */
70
+ health: PollerHealthStatus;
71
+ /** Whether the poller is currently running */
72
+ isRunning: boolean;
73
+ }
74
+ //# sourceMappingURL=openapi-spec-poller.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi-spec-poller.types.d.ts","sourceRoot":"","sources":["../../src/openapi/openapi-spec-poller.types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG,cAAc,GAAG,MAAM,GAAG,MAAM,CAAC;AAEnE;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2DAA2D;IAC3D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oCAAoC;IACpC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,mDAAmD;IACnD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gDAAgD;IAChD,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,0BAA0B;IAC1B,KAAK,CAAC,EAAE,qBAAqB,CAAC;IAC9B,yEAAyE;IACzE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,4CAA4C;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,+CAA+C;IAC/C,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,2BAA2B;IAC3B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,2CAA2C;IAC3C,WAAW,CAAC,EAAE,CAAC,mBAAmB,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,uDAAuD;IACvD,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,2BAA2B;IAC3B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,qCAAqC;IACrC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,oBAAoB;IACpB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,8CAA8C;IAC9C,SAAS,EAAE,OAAO,CAAC;CACpB"}
@@ -4,6 +4,9 @@ export default class OpenapiAdapter extends DynamicAdapter<OpenApiAdapterOptions
4
4
  private generator?;
5
5
  private logger;
6
6
  options: OpenApiAdapterOptions;
7
+ private poller;
8
+ private updateCallbacks;
9
+ private rebuildChain;
7
10
  constructor(options: OpenApiAdapterOptions);
8
11
  /**
9
12
  * Receive the SDK logger. Called by the SDK before fetch().
@@ -20,6 +23,11 @@ export default class OpenapiAdapter extends DynamicAdapter<OpenApiAdapterOptions
20
23
  * @private
21
24
  */
22
25
  private applyDescriptionMode;
26
+ /**
27
+ * Look up a value in a perTool map by tool name, using own-property check.
28
+ * @private
29
+ */
30
+ private resolvePerTool;
23
31
  /**
24
32
  * Collect tool transforms for a specific tool
25
33
  * @private
@@ -95,5 +103,20 @@ export default class OpenapiAdapter extends DynamicAdapter<OpenApiAdapterOptions
95
103
  * @private
96
104
  */
97
105
  private getSchemaTypeString;
106
+ /**
107
+ * Register a callback for when the adapter's tools are updated via polling.
108
+ * Returns an unsubscribe function.
109
+ */
110
+ onUpdate(callback: (response: FrontMcpAdapterResponse) => void): () => void;
111
+ /**
112
+ * Start polling for spec changes.
113
+ * Creates the poller and on spec change, re-runs fetch() and calls the update callback.
114
+ * Rebuilds are serialized via a Promise chain to prevent races.
115
+ */
116
+ startPolling(): void;
117
+ /**
118
+ * Stop polling for spec changes.
119
+ */
120
+ stopPolling(): void;
98
121
  }
99
122
  //# sourceMappingURL=openapi.adapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"openapi.adapter.d.ts","sourceRoot":"","sources":["../../src/openapi/openapi.adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,cAAc,EAAE,uBAAuB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACjG,OAAO,EACL,qBAAqB,EAWtB,MAAM,iBAAiB,CAAC;AA2BzB,MAAM,CAAC,OAAO,OAAO,cAAe,SAAQ,cAAc,CAAC,qBAAqB,CAAC;IAC/E,OAAO,CAAC,SAAS,CAAC,CAAuB;IACzC,OAAO,CAAC,MAAM,CAAiB;IACxB,OAAO,EAAE,qBAAqB,CAAC;gBAE1B,OAAO,EAAE,qBAAqB;IAO1C;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI;IAIjC,KAAK,IAAI,OAAO,CAAC,uBAAuB,CAAC;IAgH/C;;;OAGG;YACW,mBAAmB;IAqBjC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IA0C5B;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IA4D7B;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAsC3B;;;OAGG;IACH,OAAO,CAAC,wBAAwB;IAuBhC;;;;;OAKG;IACH,OAAO,CAAC,oBAAoB;IAoD5B;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IA2D7B;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IA0C7B;;;OAGG;IACH,OAAO,CAAC,2BAA2B;IAiBnC;;;OAGG;IACH,OAAO,CAAC,4BAA4B;IAmBpC;;;;OAIG;YACW,wBAAwB;IAiFtC;;;OAGG;IACH,OAAO,CAAC,0BAA0B;IAgBlC;;;OAGG;IACH,OAAO,CAAC,0CAA0C;IA8BlD;;;OAGG;IACH,OAAO,CAAC,2CAA2C;IAyCnD;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAuB7B;;;OAGG;IACH,OAAO,CAAC,mBAAmB;CAY5B"}
1
+ {"version":3,"file":"openapi.adapter.d.ts","sourceRoot":"","sources":["../../src/openapi/openapi.adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,cAAc,EAAE,uBAAuB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACjG,OAAO,EACL,qBAAqB,EAWtB,MAAM,iBAAiB,CAAC;AA4BzB,MAAM,CAAC,OAAO,OAAO,cAAe,SAAQ,cAAc,CAAC,qBAAqB,CAAC;IAC/E,OAAO,CAAC,SAAS,CAAC,CAAuB;IACzC,OAAO,CAAC,MAAM,CAAiB;IACxB,OAAO,EAAE,qBAAqB,CAAC;IACtC,OAAO,CAAC,MAAM,CAAkC;IAChD,OAAO,CAAC,eAAe,CAA0D;IACjF,OAAO,CAAC,YAAY,CAAoC;gBAE5C,OAAO,EAAE,qBAAqB;IAc1C;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI;IAIjC,KAAK,IAAI,OAAO,CAAC,uBAAuB,CAAC;IAqI/C;;;OAGG;YACW,mBAAmB;IAqBjC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IA0C5B;;;OAGG;IACH,OAAO,CAAC,cAAc;IAMtB;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IA6D7B;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAsC3B;;;OAGG;IACH,OAAO,CAAC,wBAAwB;IAwBhC;;;;;OAKG;IACH,OAAO,CAAC,oBAAoB;IAoD5B;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IA2D7B;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IA0C7B;;;OAGG;IACH,OAAO,CAAC,2BAA2B;IAkBnC;;;OAGG;IACH,OAAO,CAAC,4BAA4B;IAoBpC;;;;OAIG;YACW,wBAAwB;IAiFtC;;;OAGG;IACH,OAAO,CAAC,0BAA0B;IAgBlC;;;OAGG;IACH,OAAO,CAAC,0CAA0C;IA+BlD;;;OAGG;IACH,OAAO,CAAC,2CAA2C;IAyCnD;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAuB7B;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAiB3B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,uBAAuB,KAAK,IAAI,GAAG,MAAM,IAAI;IAO3E;;;;OAIG;IACH,YAAY,IAAI,IAAI;IAsCpB;;OAEG;IACH,WAAW,IAAI,IAAI;CAOpB"}
@@ -0,0 +1,8 @@
1
+ import { InternalMcpError } from '@frontmcp/sdk';
2
+ /**
3
+ * Thrown when fetching the OpenAPI spec from a remote URL fails with a non-ok HTTP status.
4
+ */
5
+ export declare class OpenAPIFetchError extends InternalMcpError {
6
+ constructor(url: string, status: number, statusText: string);
7
+ }
8
+ //# sourceMappingURL=openapi.errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi.errors.d.ts","sourceRoot":"","sources":["../../src/openapi/openapi.errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjD;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,gBAAgB;gBACzC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM;CAG5D"}