@frontmcp/adapters 1.9.1 → 1.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/esm/index.mjs CHANGED
@@ -10,7 +10,10 @@ var __decorateClass = (decorators, target, key, kind) => {
10
10
  };
11
11
 
12
12
  // libs/adapters/src/openapi/openapi.adapter.ts
13
- import { normalizeSsrfOptions as normalizeSsrfOptions2, OpenAPIToolGenerator } from "mcp-from-openapi";
13
+ import {
14
+ normalizeSsrfOptions as normalizeSsrfOptions2,
15
+ OpenAPIToolGenerator
16
+ } from "mcp-from-openapi";
14
17
  import { Adapter, DynamicAdapter } from "@frontmcp/sdk";
15
18
 
16
19
  // libs/adapters/src/openapi/openapi-spec-poller.ts
@@ -605,7 +608,7 @@ async function resolveInputSecurity(tool2, input, options, securityResolver) {
605
608
 
606
609
  // libs/adapters/src/openapi/openapi.tool.ts
607
610
  import { z as z2 } from "@frontmcp/lazy-zod";
608
- import { tool } from "@frontmcp/sdk";
611
+ import { InvalidInputError, tool } from "@frontmcp/sdk";
609
612
  import { isRedirectResponse } from "@frontmcp/utils";
610
613
 
611
614
  // libs/adapters/src/openapi/openapi.frontmcp-schema.ts
@@ -651,7 +654,9 @@ var KNOWN_EXTENSION_FIELDS = /* @__PURE__ */ new Set([
651
654
  "codecall",
652
655
  "tags",
653
656
  "hideFromDiscovery",
654
- "examples"
657
+ "examples",
658
+ "meta",
659
+ "icons"
655
660
  ]);
656
661
  var KNOWN_ANNOTATION_FIELDS = /* @__PURE__ */ new Set([
657
662
  "title",
@@ -875,6 +880,10 @@ function appendCookie(headers, name, value) {
875
880
  const combined = existing ? `${existing}; ${cookiePair}` : cookiePair;
876
881
  headers.set("Cookie", combined);
877
882
  }
883
+ function specDefault(tool2, inputKey) {
884
+ const property = tool2.inputSchema.properties?.[inputKey];
885
+ return typeof property === "object" ? property.default : void 0;
886
+ }
878
887
  function buildRequest(tool2, input, security, baseUrl) {
879
888
  const rawBaseUrl = tool2.metadata.servers?.[0]?.url || baseUrl;
880
889
  validateBaseUrl(rawBaseUrl);
@@ -888,7 +897,7 @@ function buildRequest(tool2, input, security, baseUrl) {
888
897
  let body;
889
898
  for (const mapper of tool2.mapper) {
890
899
  if (mapper.security) continue;
891
- const value = input[mapper.inputKey];
900
+ const value = input[mapper.inputKey] ?? (mapper.required ? specDefault(tool2, mapper.inputKey) : void 0);
892
901
  if (value === void 0 || value === null) {
893
902
  if (mapper.required) {
894
903
  throw new Error(
@@ -1002,9 +1011,11 @@ function createOpenApiTool(openapiTool, options, logger) {
1002
1011
  const inputTransforms = metadata.adapter?.inputTransforms ?? [];
1003
1012
  const toolTransform = metadata.adapter?.toolTransform ?? {};
1004
1013
  const postToolTransform = metadata.adapter?.postToolTransform;
1014
+ const serverFilledKeys = new Set(inputTransforms.map((transform) => transform.inputKey));
1005
1015
  const frontmcpValidation = validateFrontMcpExtension(metadata.frontmcp, openapiTool.name, logger);
1006
1016
  const frontmcpExt = frontmcpValidation.data;
1007
1017
  const schemaResult = getZodSchemaFromJsonSchema(openapiTool.inputSchema, openapiTool.name, logger);
1018
+ const argumentCheck = schemaResult.conversionFailed ? void 0 : getZodSchemaFromJsonSchema(toArgumentJsonSchema(openapiTool), openapiTool.name, logger);
1008
1019
  let wrappedOutputSchema;
1009
1020
  if (openapiTool.outputSchema !== void 0) {
1010
1021
  const baseOutputSchema = openapiTool.outputSchema ?? { type: "string" };
@@ -1022,11 +1033,15 @@ function createOpenApiTool(openapiTool, options, logger) {
1022
1033
  const toolMetadata = {
1023
1034
  id: openapiTool.name,
1024
1035
  name: openapiTool.name,
1036
+ ...openapiTool.title && { title: openapiTool.title },
1025
1037
  description: openapiTool.description,
1026
1038
  inputSchema: schemaResult.schema.shape || {},
1027
1039
  rawInputSchema: openapiTool.inputSchema,
1028
1040
  // Add output schema for tool/list to expose (only if not moved to description)
1029
- ...wrappedOutputSchema && { rawOutputSchema: wrappedOutputSchema }
1041
+ ...wrappedOutputSchema && { rawOutputSchema: wrappedOutputSchema },
1042
+ ...openapiTool.annotations && { annotations: { ...openapiTool.annotations } },
1043
+ ...openapiTool.icons?.length && { icons: openapiTool.icons.map((icon) => ({ ...icon })) },
1044
+ ...openapiTool._meta && { _meta: { ...openapiTool._meta } }
1030
1045
  };
1031
1046
  if (schemaResult.conversionFailed) {
1032
1047
  toolMetadata["_schemaConversionFailed"] = true;
@@ -1034,7 +1049,10 @@ function createOpenApiTool(openapiTool, options, logger) {
1034
1049
  }
1035
1050
  if (frontmcpExt) {
1036
1051
  if (frontmcpExt.annotations) {
1037
- toolMetadata["annotations"] = { ...frontmcpExt.annotations };
1052
+ toolMetadata["annotations"] = {
1053
+ ...toolMetadata["annotations"] || {},
1054
+ ...frontmcpExt.annotations
1055
+ };
1038
1056
  }
1039
1057
  if (frontmcpExt.tags) {
1040
1058
  toolMetadata["tags"] = [...frontmcpExt.tags];
@@ -1074,16 +1092,20 @@ function createOpenApiTool(openapiTool, options, logger) {
1074
1092
  }
1075
1093
  return tool(toolMetadata)(async (input, toolCtx) => {
1076
1094
  const ctx = toolCtx.context;
1095
+ const modelInput = omitKeys(input, serverFilledKeys);
1096
+ if (argumentCheck && !argumentCheck.conversionFailed) {
1097
+ const validation = argumentCheck.schema.safeParse(modelInput);
1098
+ if (!validation.success) {
1099
+ const problems = validation.error.issues.map((issue) => `${issue.path.join(".") || "input"}: ${issue.message}`);
1100
+ throw new InvalidInputError(`Invalid arguments for tool '${openapiTool.name}': ${problems.join("; ")}`);
1101
+ }
1102
+ }
1077
1103
  const transformContext = {
1078
1104
  ctx,
1079
1105
  env: process.env,
1080
1106
  tool: openapiTool
1081
1107
  };
1082
- const injectedInput = await injectTransformedValues(
1083
- input,
1084
- inputTransforms,
1085
- transformContext
1086
- );
1108
+ const injectedInput = await injectTransformedValues(modelInput, inputTransforms, transformContext);
1087
1109
  const security = await resolveToolSecurity(openapiTool, ctx, options, {
1088
1110
  input: injectedInput,
1089
1111
  deferCredentialCheck: true
@@ -1268,6 +1290,10 @@ async function safeInject(transform, ctx, timeoutMs = DEFAULT_TRANSFORM_TIMEOUT_
1268
1290
  function isPlainObject(value) {
1269
1291
  return typeof value === "object" && value !== null && !Array.isArray(value);
1270
1292
  }
1293
+ function omitKeys(input, keys) {
1294
+ if (keys.size === 0 || !isPlainObject(input)) return input;
1295
+ return Object.fromEntries(Object.entries(input).filter(([key]) => !keys.has(key)));
1296
+ }
1271
1297
  async function injectTransformedValues(input, transforms, ctx) {
1272
1298
  if (!isPlainObject(input)) {
1273
1299
  throw new Error(`Invalid input type: expected object, got ${input === null ? "null" : typeof input}`);
@@ -1287,6 +1313,64 @@ async function injectTransformedValues(input, transforms, ctx) {
1287
1313
  }
1288
1314
  return result;
1289
1315
  }
1316
+ var SUBSCHEMA_KEYWORDS = [
1317
+ "items",
1318
+ "additionalProperties",
1319
+ "not",
1320
+ "if",
1321
+ "then",
1322
+ "else",
1323
+ "contains",
1324
+ "propertyNames",
1325
+ "unevaluatedItems",
1326
+ "unevaluatedProperties"
1327
+ ];
1328
+ var SUBSCHEMA_LIST_KEYWORDS = ["allOf", "anyOf", "prefixItems"];
1329
+ var SUBSCHEMA_MAP_KEYWORDS = ["properties", "patternProperties", "dependentSchemas", "$defs", "definitions"];
1330
+ function toArgumentJsonSchema(openapiTool) {
1331
+ const argumentSchema = relaxSchema(openapiTool.inputSchema);
1332
+ const credentialKeys = new Set(
1333
+ openapiTool.mapper.filter((mapper) => mapper.security).map((mapper) => mapper.inputKey)
1334
+ );
1335
+ const required = argumentSchema["required"];
1336
+ if (Array.isArray(required)) {
1337
+ argumentSchema["required"] = required.filter((key) => !credentialKeys.has(key));
1338
+ }
1339
+ return argumentSchema;
1340
+ }
1341
+ function relaxSchema(schema) {
1342
+ if (Array.isArray(schema)) return schema.map(relaxSchema);
1343
+ if (typeof schema !== "object" || schema === null) return schema;
1344
+ const relaxed = { ...schema };
1345
+ delete relaxed["format"];
1346
+ for (const keyword of SUBSCHEMA_KEYWORDS) {
1347
+ if (keyword in relaxed) relaxed[keyword] = relaxSchema(relaxed[keyword]);
1348
+ }
1349
+ for (const keyword of SUBSCHEMA_LIST_KEYWORDS) {
1350
+ const subschemas = relaxed[keyword];
1351
+ if (Array.isArray(subschemas)) relaxed[keyword] = subschemas.map(relaxSchema);
1352
+ }
1353
+ for (const keyword of SUBSCHEMA_MAP_KEYWORDS) {
1354
+ const subschemasByName = relaxed[keyword];
1355
+ if (typeof subschemasByName === "object" && subschemasByName !== null) {
1356
+ relaxed[keyword] = Object.fromEntries(
1357
+ Object.entries(subschemasByName).map(([name, subschema]) => [name, relaxSchema(subschema)])
1358
+ );
1359
+ }
1360
+ }
1361
+ const exclusiveBranches = relaxed["oneOf"];
1362
+ if (Array.isArray(exclusiveBranches)) {
1363
+ delete relaxed["oneOf"];
1364
+ const branches = exclusiveBranches.map(relaxSchema);
1365
+ if (Array.isArray(relaxed["anyOf"])) {
1366
+ const allOf = Array.isArray(relaxed["allOf"]) ? relaxed["allOf"] : [];
1367
+ relaxed["allOf"] = [...allOf, { anyOf: branches }];
1368
+ } else {
1369
+ relaxed["anyOf"] = branches;
1370
+ }
1371
+ }
1372
+ return relaxed;
1373
+ }
1290
1374
  function getZodSchemaFromJsonSchema(jsonSchema, toolName, logger) {
1291
1375
  if (typeof jsonSchema !== "object" || jsonSchema === null) {
1292
1376
  logger.warn(`[${toolName}] No valid JSON schema provided, using permissive schema`);
@@ -1314,6 +1398,18 @@ function getZodSchemaFromJsonSchema(jsonSchema, toolName, logger) {
1314
1398
 
1315
1399
  // libs/adapters/src/openapi/openapi.adapter.ts
1316
1400
  var RESERVED_KEYS2 = ["__proto__", "constructor", "prototype"];
1401
+ var HTTP_METHODS = ["get", "post", "put", "patch", "delete", "head", "options", "trace"];
1402
+ function normalizeMethods(adapterName, optionName, methods) {
1403
+ return methods?.map((method) => {
1404
+ const lowerMethod = method.toLowerCase();
1405
+ if (!HTTP_METHODS.includes(lowerMethod)) {
1406
+ throw new Error(
1407
+ `[OpenAPI Adapter: ${adapterName}] generateOptions.${optionName} lists "${method}", which is not an HTTP method; use one of ${HTTP_METHODS.join(", ")}.`
1408
+ );
1409
+ }
1410
+ return lowerMethod;
1411
+ });
1412
+ }
1317
1413
  function createConsoleLogger(prefix) {
1318
1414
  const formatMessage = (level, msg) => `[${prefix}] ${level}: ${msg}`;
1319
1415
  return {
@@ -1332,9 +1428,14 @@ var OpenapiAdapter = class extends DynamicAdapter {
1332
1428
  poller = null;
1333
1429
  updateCallbacks = /* @__PURE__ */ new Set();
1334
1430
  rebuildChain = Promise.resolve();
1431
+ methodFilters;
1335
1432
  constructor(options) {
1336
1433
  super();
1337
1434
  this.options = options;
1435
+ this.methodFilters = {
1436
+ includeMethods: normalizeMethods(options.name, "includeMethods", options.generateOptions?.includeMethods),
1437
+ excludeMethods: normalizeMethods(options.name, "excludeMethods", options.generateOptions?.excludeMethods)
1438
+ };
1338
1439
  this.logger = options.logger ?? createConsoleLogger(`openapi:${options.name}`);
1339
1440
  if (options.polling?.enabled && !("url" in options)) {
1340
1441
  throw new Error(
@@ -1358,6 +1459,8 @@ var OpenapiAdapter = class extends DynamicAdapter {
1358
1459
  const generateOptions = this.options.generateOptions;
1359
1460
  let openapiTools = await this.generator.generateTools({
1360
1461
  ...generateOptions,
1462
+ ...this.methodFilters,
1463
+ filterFn: this.operationFilter(),
1361
1464
  preferredStatusCodes: generateOptions?.preferredStatusCodes ?? [200, 201, 202, 204],
1362
1465
  includeDeprecated: generateOptions?.includeDeprecated ?? false,
1363
1466
  includeAllResponses: generateOptions?.includeAllResponses ?? true,
@@ -1443,28 +1546,31 @@ var OpenapiAdapter = class extends DynamicAdapter {
1443
1546
  resolveRefResolution() {
1444
1547
  return this.options.loadOptions?.refResolution ?? { allowedProtocols: [] };
1445
1548
  }
1549
+ /**
1550
+ * `generateOptions.filterFn`, and with `includeOperations` set, only the operations it names: the
1551
+ * generator lets an operation with no `operationId` through that list.
1552
+ */
1553
+ operationFilter() {
1554
+ const { includeOperations, filterFn } = this.options.generateOptions ?? {};
1555
+ if (!includeOperations) return filterFn;
1556
+ return (operation) => operation.operationId !== void 0 && includeOperations.includes(operation.operationId) && (filterFn?.(operation) ?? true);
1557
+ }
1446
1558
  async initializeGenerator() {
1447
- const refResolution = this.resolveRefResolution();
1559
+ const loadOptions = {
1560
+ ...this.options.loadOptions,
1561
+ baseUrl: this.options.baseUrl,
1562
+ validate: this.options.loadOptions?.validate ?? true,
1563
+ dereference: this.options.loadOptions?.dereference ?? true,
1564
+ // SECURITY: do not follow spec-URL redirects by default — a 3xx to an
1565
+ // internal target would otherwise be fetched. With mcp-from-openapi
1566
+ // ≥ 2.5.0 each hop is re-validated; opt in with loadOptions.followRedirects.
1567
+ followRedirects: this.options.loadOptions?.followRedirects ?? false,
1568
+ refResolution: this.resolveRefResolution()
1569
+ };
1448
1570
  if ("url" in this.options) {
1449
- return await OpenAPIToolGenerator.fromURL(this.options.url, {
1450
- baseUrl: this.options.baseUrl,
1451
- validate: this.options.loadOptions?.validate ?? true,
1452
- dereference: this.options.loadOptions?.dereference ?? true,
1453
- headers: this.options.loadOptions?.headers,
1454
- timeout: this.options.loadOptions?.timeout,
1455
- // SECURITY: do not follow spec-URL redirects by default — a 3xx to an
1456
- // internal target would otherwise be fetched. With mcp-from-openapi
1457
- // ≥ 2.5.0 each hop is re-validated; opt in with loadOptions.followRedirects.
1458
- followRedirects: this.options.loadOptions?.followRedirects ?? false,
1459
- refResolution
1460
- });
1571
+ return await OpenAPIToolGenerator.fromURL(this.options.url, loadOptions);
1461
1572
  } else if ("spec" in this.options) {
1462
- return await OpenAPIToolGenerator.fromJSON(this.options.spec, {
1463
- baseUrl: this.options.baseUrl,
1464
- validate: this.options.loadOptions?.validate ?? true,
1465
- dereference: this.options.loadOptions?.dereference ?? true,
1466
- refResolution
1467
- });
1573
+ return await OpenAPIToolGenerator.fromJSON(this.options.spec, loadOptions);
1468
1574
  } else {
1469
1575
  throw new Error("Either url or spec must be provided in OpenApiAdapterOptions");
1470
1576
  }
@@ -10,7 +10,10 @@ var __decorateClass = (decorators, target, key, kind) => {
10
10
  };
11
11
 
12
12
  // libs/adapters/src/openapi/openapi.adapter.ts
13
- import { normalizeSsrfOptions as normalizeSsrfOptions2, OpenAPIToolGenerator } from "mcp-from-openapi";
13
+ import {
14
+ normalizeSsrfOptions as normalizeSsrfOptions2,
15
+ OpenAPIToolGenerator
16
+ } from "mcp-from-openapi";
14
17
  import { Adapter, DynamicAdapter } from "@frontmcp/sdk";
15
18
 
16
19
  // libs/adapters/src/openapi/openapi-spec-poller.ts
@@ -605,7 +608,7 @@ async function resolveInputSecurity(tool2, input, options, securityResolver) {
605
608
 
606
609
  // libs/adapters/src/openapi/openapi.tool.ts
607
610
  import { z as z2 } from "@frontmcp/lazy-zod";
608
- import { tool } from "@frontmcp/sdk";
611
+ import { InvalidInputError, tool } from "@frontmcp/sdk";
609
612
  import { isRedirectResponse } from "@frontmcp/utils";
610
613
 
611
614
  // libs/adapters/src/openapi/openapi.frontmcp-schema.ts
@@ -651,7 +654,9 @@ var KNOWN_EXTENSION_FIELDS = /* @__PURE__ */ new Set([
651
654
  "codecall",
652
655
  "tags",
653
656
  "hideFromDiscovery",
654
- "examples"
657
+ "examples",
658
+ "meta",
659
+ "icons"
655
660
  ]);
656
661
  var KNOWN_ANNOTATION_FIELDS = /* @__PURE__ */ new Set([
657
662
  "title",
@@ -875,6 +880,10 @@ function appendCookie(headers, name, value) {
875
880
  const combined = existing ? `${existing}; ${cookiePair}` : cookiePair;
876
881
  headers.set("Cookie", combined);
877
882
  }
883
+ function specDefault(tool2, inputKey) {
884
+ const property = tool2.inputSchema.properties?.[inputKey];
885
+ return typeof property === "object" ? property.default : void 0;
886
+ }
878
887
  function buildRequest(tool2, input, security, baseUrl) {
879
888
  const rawBaseUrl = tool2.metadata.servers?.[0]?.url || baseUrl;
880
889
  validateBaseUrl(rawBaseUrl);
@@ -888,7 +897,7 @@ function buildRequest(tool2, input, security, baseUrl) {
888
897
  let body;
889
898
  for (const mapper of tool2.mapper) {
890
899
  if (mapper.security) continue;
891
- const value = input[mapper.inputKey];
900
+ const value = input[mapper.inputKey] ?? (mapper.required ? specDefault(tool2, mapper.inputKey) : void 0);
892
901
  if (value === void 0 || value === null) {
893
902
  if (mapper.required) {
894
903
  throw new Error(
@@ -1002,9 +1011,11 @@ function createOpenApiTool(openapiTool, options, logger) {
1002
1011
  const inputTransforms = metadata.adapter?.inputTransforms ?? [];
1003
1012
  const toolTransform = metadata.adapter?.toolTransform ?? {};
1004
1013
  const postToolTransform = metadata.adapter?.postToolTransform;
1014
+ const serverFilledKeys = new Set(inputTransforms.map((transform) => transform.inputKey));
1005
1015
  const frontmcpValidation = validateFrontMcpExtension(metadata.frontmcp, openapiTool.name, logger);
1006
1016
  const frontmcpExt = frontmcpValidation.data;
1007
1017
  const schemaResult = getZodSchemaFromJsonSchema(openapiTool.inputSchema, openapiTool.name, logger);
1018
+ const argumentCheck = schemaResult.conversionFailed ? void 0 : getZodSchemaFromJsonSchema(toArgumentJsonSchema(openapiTool), openapiTool.name, logger);
1008
1019
  let wrappedOutputSchema;
1009
1020
  if (openapiTool.outputSchema !== void 0) {
1010
1021
  const baseOutputSchema = openapiTool.outputSchema ?? { type: "string" };
@@ -1022,11 +1033,15 @@ function createOpenApiTool(openapiTool, options, logger) {
1022
1033
  const toolMetadata = {
1023
1034
  id: openapiTool.name,
1024
1035
  name: openapiTool.name,
1036
+ ...openapiTool.title && { title: openapiTool.title },
1025
1037
  description: openapiTool.description,
1026
1038
  inputSchema: schemaResult.schema.shape || {},
1027
1039
  rawInputSchema: openapiTool.inputSchema,
1028
1040
  // Add output schema for tool/list to expose (only if not moved to description)
1029
- ...wrappedOutputSchema && { rawOutputSchema: wrappedOutputSchema }
1041
+ ...wrappedOutputSchema && { rawOutputSchema: wrappedOutputSchema },
1042
+ ...openapiTool.annotations && { annotations: { ...openapiTool.annotations } },
1043
+ ...openapiTool.icons?.length && { icons: openapiTool.icons.map((icon) => ({ ...icon })) },
1044
+ ...openapiTool._meta && { _meta: { ...openapiTool._meta } }
1030
1045
  };
1031
1046
  if (schemaResult.conversionFailed) {
1032
1047
  toolMetadata["_schemaConversionFailed"] = true;
@@ -1034,7 +1049,10 @@ function createOpenApiTool(openapiTool, options, logger) {
1034
1049
  }
1035
1050
  if (frontmcpExt) {
1036
1051
  if (frontmcpExt.annotations) {
1037
- toolMetadata["annotations"] = { ...frontmcpExt.annotations };
1052
+ toolMetadata["annotations"] = {
1053
+ ...toolMetadata["annotations"] || {},
1054
+ ...frontmcpExt.annotations
1055
+ };
1038
1056
  }
1039
1057
  if (frontmcpExt.tags) {
1040
1058
  toolMetadata["tags"] = [...frontmcpExt.tags];
@@ -1074,16 +1092,20 @@ function createOpenApiTool(openapiTool, options, logger) {
1074
1092
  }
1075
1093
  return tool(toolMetadata)(async (input, toolCtx) => {
1076
1094
  const ctx = toolCtx.context;
1095
+ const modelInput = omitKeys(input, serverFilledKeys);
1096
+ if (argumentCheck && !argumentCheck.conversionFailed) {
1097
+ const validation = argumentCheck.schema.safeParse(modelInput);
1098
+ if (!validation.success) {
1099
+ const problems = validation.error.issues.map((issue) => `${issue.path.join(".") || "input"}: ${issue.message}`);
1100
+ throw new InvalidInputError(`Invalid arguments for tool '${openapiTool.name}': ${problems.join("; ")}`);
1101
+ }
1102
+ }
1077
1103
  const transformContext = {
1078
1104
  ctx,
1079
1105
  env: process.env,
1080
1106
  tool: openapiTool
1081
1107
  };
1082
- const injectedInput = await injectTransformedValues(
1083
- input,
1084
- inputTransforms,
1085
- transformContext
1086
- );
1108
+ const injectedInput = await injectTransformedValues(modelInput, inputTransforms, transformContext);
1087
1109
  const security = await resolveToolSecurity(openapiTool, ctx, options, {
1088
1110
  input: injectedInput,
1089
1111
  deferCredentialCheck: true
@@ -1268,6 +1290,10 @@ async function safeInject(transform, ctx, timeoutMs = DEFAULT_TRANSFORM_TIMEOUT_
1268
1290
  function isPlainObject(value) {
1269
1291
  return typeof value === "object" && value !== null && !Array.isArray(value);
1270
1292
  }
1293
+ function omitKeys(input, keys) {
1294
+ if (keys.size === 0 || !isPlainObject(input)) return input;
1295
+ return Object.fromEntries(Object.entries(input).filter(([key]) => !keys.has(key)));
1296
+ }
1271
1297
  async function injectTransformedValues(input, transforms, ctx) {
1272
1298
  if (!isPlainObject(input)) {
1273
1299
  throw new Error(`Invalid input type: expected object, got ${input === null ? "null" : typeof input}`);
@@ -1287,6 +1313,64 @@ async function injectTransformedValues(input, transforms, ctx) {
1287
1313
  }
1288
1314
  return result;
1289
1315
  }
1316
+ var SUBSCHEMA_KEYWORDS = [
1317
+ "items",
1318
+ "additionalProperties",
1319
+ "not",
1320
+ "if",
1321
+ "then",
1322
+ "else",
1323
+ "contains",
1324
+ "propertyNames",
1325
+ "unevaluatedItems",
1326
+ "unevaluatedProperties"
1327
+ ];
1328
+ var SUBSCHEMA_LIST_KEYWORDS = ["allOf", "anyOf", "prefixItems"];
1329
+ var SUBSCHEMA_MAP_KEYWORDS = ["properties", "patternProperties", "dependentSchemas", "$defs", "definitions"];
1330
+ function toArgumentJsonSchema(openapiTool) {
1331
+ const argumentSchema = relaxSchema(openapiTool.inputSchema);
1332
+ const credentialKeys = new Set(
1333
+ openapiTool.mapper.filter((mapper) => mapper.security).map((mapper) => mapper.inputKey)
1334
+ );
1335
+ const required = argumentSchema["required"];
1336
+ if (Array.isArray(required)) {
1337
+ argumentSchema["required"] = required.filter((key) => !credentialKeys.has(key));
1338
+ }
1339
+ return argumentSchema;
1340
+ }
1341
+ function relaxSchema(schema) {
1342
+ if (Array.isArray(schema)) return schema.map(relaxSchema);
1343
+ if (typeof schema !== "object" || schema === null) return schema;
1344
+ const relaxed = { ...schema };
1345
+ delete relaxed["format"];
1346
+ for (const keyword of SUBSCHEMA_KEYWORDS) {
1347
+ if (keyword in relaxed) relaxed[keyword] = relaxSchema(relaxed[keyword]);
1348
+ }
1349
+ for (const keyword of SUBSCHEMA_LIST_KEYWORDS) {
1350
+ const subschemas = relaxed[keyword];
1351
+ if (Array.isArray(subschemas)) relaxed[keyword] = subschemas.map(relaxSchema);
1352
+ }
1353
+ for (const keyword of SUBSCHEMA_MAP_KEYWORDS) {
1354
+ const subschemasByName = relaxed[keyword];
1355
+ if (typeof subschemasByName === "object" && subschemasByName !== null) {
1356
+ relaxed[keyword] = Object.fromEntries(
1357
+ Object.entries(subschemasByName).map(([name, subschema]) => [name, relaxSchema(subschema)])
1358
+ );
1359
+ }
1360
+ }
1361
+ const exclusiveBranches = relaxed["oneOf"];
1362
+ if (Array.isArray(exclusiveBranches)) {
1363
+ delete relaxed["oneOf"];
1364
+ const branches = exclusiveBranches.map(relaxSchema);
1365
+ if (Array.isArray(relaxed["anyOf"])) {
1366
+ const allOf = Array.isArray(relaxed["allOf"]) ? relaxed["allOf"] : [];
1367
+ relaxed["allOf"] = [...allOf, { anyOf: branches }];
1368
+ } else {
1369
+ relaxed["anyOf"] = branches;
1370
+ }
1371
+ }
1372
+ return relaxed;
1373
+ }
1290
1374
  function getZodSchemaFromJsonSchema(jsonSchema, toolName, logger) {
1291
1375
  if (typeof jsonSchema !== "object" || jsonSchema === null) {
1292
1376
  logger.warn(`[${toolName}] No valid JSON schema provided, using permissive schema`);
@@ -1314,6 +1398,18 @@ function getZodSchemaFromJsonSchema(jsonSchema, toolName, logger) {
1314
1398
 
1315
1399
  // libs/adapters/src/openapi/openapi.adapter.ts
1316
1400
  var RESERVED_KEYS2 = ["__proto__", "constructor", "prototype"];
1401
+ var HTTP_METHODS = ["get", "post", "put", "patch", "delete", "head", "options", "trace"];
1402
+ function normalizeMethods(adapterName, optionName, methods) {
1403
+ return methods?.map((method) => {
1404
+ const lowerMethod = method.toLowerCase();
1405
+ if (!HTTP_METHODS.includes(lowerMethod)) {
1406
+ throw new Error(
1407
+ `[OpenAPI Adapter: ${adapterName}] generateOptions.${optionName} lists "${method}", which is not an HTTP method; use one of ${HTTP_METHODS.join(", ")}.`
1408
+ );
1409
+ }
1410
+ return lowerMethod;
1411
+ });
1412
+ }
1317
1413
  function createConsoleLogger(prefix) {
1318
1414
  const formatMessage = (level, msg) => `[${prefix}] ${level}: ${msg}`;
1319
1415
  return {
@@ -1332,9 +1428,14 @@ var OpenapiAdapter = class extends DynamicAdapter {
1332
1428
  poller = null;
1333
1429
  updateCallbacks = /* @__PURE__ */ new Set();
1334
1430
  rebuildChain = Promise.resolve();
1431
+ methodFilters;
1335
1432
  constructor(options) {
1336
1433
  super();
1337
1434
  this.options = options;
1435
+ this.methodFilters = {
1436
+ includeMethods: normalizeMethods(options.name, "includeMethods", options.generateOptions?.includeMethods),
1437
+ excludeMethods: normalizeMethods(options.name, "excludeMethods", options.generateOptions?.excludeMethods)
1438
+ };
1338
1439
  this.logger = options.logger ?? createConsoleLogger(`openapi:${options.name}`);
1339
1440
  if (options.polling?.enabled && !("url" in options)) {
1340
1441
  throw new Error(
@@ -1358,6 +1459,8 @@ var OpenapiAdapter = class extends DynamicAdapter {
1358
1459
  const generateOptions = this.options.generateOptions;
1359
1460
  let openapiTools = await this.generator.generateTools({
1360
1461
  ...generateOptions,
1462
+ ...this.methodFilters,
1463
+ filterFn: this.operationFilter(),
1361
1464
  preferredStatusCodes: generateOptions?.preferredStatusCodes ?? [200, 201, 202, 204],
1362
1465
  includeDeprecated: generateOptions?.includeDeprecated ?? false,
1363
1466
  includeAllResponses: generateOptions?.includeAllResponses ?? true,
@@ -1443,28 +1546,31 @@ var OpenapiAdapter = class extends DynamicAdapter {
1443
1546
  resolveRefResolution() {
1444
1547
  return this.options.loadOptions?.refResolution ?? { allowedProtocols: [] };
1445
1548
  }
1549
+ /**
1550
+ * `generateOptions.filterFn`, and with `includeOperations` set, only the operations it names: the
1551
+ * generator lets an operation with no `operationId` through that list.
1552
+ */
1553
+ operationFilter() {
1554
+ const { includeOperations, filterFn } = this.options.generateOptions ?? {};
1555
+ if (!includeOperations) return filterFn;
1556
+ return (operation) => operation.operationId !== void 0 && includeOperations.includes(operation.operationId) && (filterFn?.(operation) ?? true);
1557
+ }
1446
1558
  async initializeGenerator() {
1447
- const refResolution = this.resolveRefResolution();
1559
+ const loadOptions = {
1560
+ ...this.options.loadOptions,
1561
+ baseUrl: this.options.baseUrl,
1562
+ validate: this.options.loadOptions?.validate ?? true,
1563
+ dereference: this.options.loadOptions?.dereference ?? true,
1564
+ // SECURITY: do not follow spec-URL redirects by default — a 3xx to an
1565
+ // internal target would otherwise be fetched. With mcp-from-openapi
1566
+ // ≥ 2.5.0 each hop is re-validated; opt in with loadOptions.followRedirects.
1567
+ followRedirects: this.options.loadOptions?.followRedirects ?? false,
1568
+ refResolution: this.resolveRefResolution()
1569
+ };
1448
1570
  if ("url" in this.options) {
1449
- return await OpenAPIToolGenerator.fromURL(this.options.url, {
1450
- baseUrl: this.options.baseUrl,
1451
- validate: this.options.loadOptions?.validate ?? true,
1452
- dereference: this.options.loadOptions?.dereference ?? true,
1453
- headers: this.options.loadOptions?.headers,
1454
- timeout: this.options.loadOptions?.timeout,
1455
- // SECURITY: do not follow spec-URL redirects by default — a 3xx to an
1456
- // internal target would otherwise be fetched. With mcp-from-openapi
1457
- // ≥ 2.5.0 each hop is re-validated; opt in with loadOptions.followRedirects.
1458
- followRedirects: this.options.loadOptions?.followRedirects ?? false,
1459
- refResolution
1460
- });
1571
+ return await OpenAPIToolGenerator.fromURL(this.options.url, loadOptions);
1461
1572
  } else if ("spec" in this.options) {
1462
- return await OpenAPIToolGenerator.fromJSON(this.options.spec, {
1463
- baseUrl: this.options.baseUrl,
1464
- validate: this.options.loadOptions?.validate ?? true,
1465
- dereference: this.options.loadOptions?.dereference ?? true,
1466
- refResolution
1467
- });
1573
+ return await OpenAPIToolGenerator.fromJSON(this.options.spec, loadOptions);
1468
1574
  } else {
1469
1575
  throw new Error("Either url or spec must be provided in OpenApiAdapterOptions");
1470
1576
  }
package/esm/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/adapters",
3
- "version": "1.9.1",
3
+ "version": "1.9.3",
4
4
  "description": "Adapters for the FrontMCP framework",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "homepage": "https://docs.agentfront.dev",
@@ -64,15 +64,15 @@
64
64
  "node": ">=24.0.0"
65
65
  },
66
66
  "dependencies": {
67
- "@frontmcp/auth": "1.9.1",
68
- "@frontmcp/di": "1.9.1",
69
- "@frontmcp/sdk": "1.9.1",
70
- "@frontmcp/utils": "1.9.1",
67
+ "@frontmcp/auth": "1.9.3",
68
+ "@frontmcp/di": "1.9.3",
69
+ "@frontmcp/sdk": "1.9.3",
70
+ "@frontmcp/utils": "1.9.3",
71
71
  "js-yaml": "^4.1.0",
72
72
  "mcp-from-openapi": "2.8.0",
73
73
  "openapi-types": "^12.1.3"
74
74
  },
75
75
  "peerDependencies": {
76
- "@frontmcp/lazy-zod": "1.9.1"
76
+ "@frontmcp/lazy-zod": "1.9.3"
77
77
  }
78
78
  }
@@ -843,7 +843,7 @@ var SaasPullSource = class {
843
843
  bundle = await this.loadCache();
844
844
  if (!bundle) {
845
845
  throw new Error(
846
- `[saas-source] initial pull failed and no cached bundle is available at ${this.cachePath()}: ${e.message}`,
846
+ `[saas-source] initial pull failed and no cached bundle is available ${this.cacheLocation()}: ${e.message}`,
847
847
  { cause: e }
848
848
  );
849
849
  }
@@ -989,6 +989,10 @@ var SaasPullSource = class {
989
989
  trimmed.startsWith("{") || trimmed.startsWith("[") ? { kind: "json", content: body } : { kind: "yaml", content: body }
990
990
  );
991
991
  }
992
+ /** Where the last-good bundle is kept, for messages: an injected store (a Worker's KV) has no file path. */
993
+ cacheLocation() {
994
+ return this.deps.cache ? "in the injected bundle cache" : `at ${this.cachePath()}`;
995
+ }
992
996
  cachePath() {
993
997
  const dir = this.cacheDir ?? DEFAULT_CACHE_DIR;
994
998
  return pathResolve(dir, `${this.options.expectedAudience.replace(/[^a-zA-Z0-9_.-]/g, "_")}.json`);