@frontmcp/adapters 1.8.2 → 1.8.4

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
@@ -46,7 +46,7 @@ module.exports = __toCommonJS(index_exports);
46
46
 
47
47
  // libs/adapters/src/openapi/openapi.adapter.ts
48
48
  var import_mcp_from_openapi3 = require("mcp-from-openapi");
49
- var import_sdk4 = require("@frontmcp/sdk");
49
+ var import_sdk5 = require("@frontmcp/sdk");
50
50
 
51
51
  // libs/adapters/src/openapi/openapi-spec-poller.ts
52
52
  var import_mcp_from_openapi = require("mcp-from-openapi");
@@ -219,12 +219,13 @@ var OpenApiSpecPoller = class {
219
219
 
220
220
  // libs/adapters/src/openapi/openapi.security.ts
221
221
  var import_mcp_from_openapi2 = require("mcp-from-openapi");
222
+ var import_sdk2 = require("@frontmcp/sdk");
222
223
  async function createSecurityContextFromAuth(tool2, ctx, options) {
223
224
  if (options.securityResolver) {
224
225
  return await options.securityResolver(tool2, ctx);
225
226
  }
226
227
  if (options.authProviderMapper) {
227
- const context = (0, import_mcp_from_openapi2.createSecurityContext)({});
228
+ const context = {};
228
229
  const securitySchemes = /* @__PURE__ */ new Set();
229
230
  for (const mapper of tool2.mapper) {
230
231
  if (mapper.security?.scheme) {
@@ -273,26 +274,42 @@ async function createSecurityContextFromAuth(tool2, ctx, options) {
273
274
  throw err;
274
275
  }
275
276
  const errorMessage = err instanceof Error ? err.message : String(err);
276
- throw new Error(`authProviderMapper['${scheme}'] threw an error: ${errorMessage}`);
277
+ throw new Error(`authProviderMapper['${scheme}'] threw an error: ${errorMessage}`, { cause: err });
277
278
  }
278
279
  }
279
280
  }
281
+ if (hasStaticAuth(options)) {
282
+ return (0, import_mcp_from_openapi2.createSecurityContext)({ ...options.staticAuth, ...context });
283
+ }
280
284
  const hasAnyAuth = context.jwt || context.apiKey || context.basic || context.oauth2Token;
281
- const authToken = ctx.authInfo?.token;
282
- if (!hasAnyAuth && authToken) {
283
- if (typeof authToken !== "string") {
284
- throw new Error(`authInfo.token must be a string, but got: ${typeof authToken}`);
285
+ if (!hasAnyAuth && options.passthroughCallerToken === true) {
286
+ const callerToken = getCallerToken(ctx);
287
+ if (callerToken) {
288
+ context.jwt = callerToken;
285
289
  }
286
- context.jwt = authToken;
287
290
  }
288
- return context;
291
+ return (0, import_mcp_from_openapi2.createSecurityContext)(context);
289
292
  }
290
- if (options.staticAuth) {
293
+ if (hasStaticAuth(options)) {
291
294
  return (0, import_mcp_from_openapi2.createSecurityContext)(options.staticAuth);
292
295
  }
293
- return (0, import_mcp_from_openapi2.createSecurityContext)({
294
- jwt: ctx.authInfo?.token
295
- });
296
+ if (options.passthroughCallerToken === true) {
297
+ return (0, import_mcp_from_openapi2.createSecurityContext)({ jwt: getCallerToken(ctx) });
298
+ }
299
+ return (0, import_mcp_from_openapi2.createSecurityContext)({});
300
+ }
301
+ function hasStaticAuth(options) {
302
+ return !!options.staticAuth && Object.keys(options.staticAuth).length > 0;
303
+ }
304
+ function getCallerToken(ctx) {
305
+ const authToken = ctx.authInfo?.token;
306
+ if (authToken === void 0 || authToken === null || authToken === "") {
307
+ return void 0;
308
+ }
309
+ if (typeof authToken !== "string") {
310
+ throw new Error(`authInfo.token must be a string, but got: ${typeof authToken}`);
311
+ }
312
+ return authToken;
296
313
  }
297
314
  function extractSecuritySchemes(tools) {
298
315
  const schemes = /* @__PURE__ */ new Set();
@@ -305,19 +322,158 @@ function extractSecuritySchemes(tools) {
305
322
  }
306
323
  return schemes;
307
324
  }
325
+ function describeSecuritySchemes(tools) {
326
+ const schemes = /* @__PURE__ */ new Map();
327
+ for (const tool2 of tools) {
328
+ for (const mapper of tool2.mapper) {
329
+ if (mapper.security?.scheme && !schemes.has(mapper.security.scheme)) {
330
+ schemes.set(mapper.security.scheme, mapper.security);
331
+ }
332
+ }
333
+ }
334
+ return schemes;
335
+ }
336
+ function schemeParametersOf(tools) {
337
+ const parameters = /* @__PURE__ */ new Map();
338
+ for (const tool2 of tools) {
339
+ for (const mapper of tool2.mapper) {
340
+ const scheme = mapper.security?.scheme;
341
+ if (scheme && !parameters.has(scheme)) parameters.set(scheme, mapper);
342
+ }
343
+ }
344
+ return parameters;
345
+ }
346
+ function headerCredentialSource(parameter, options) {
347
+ if (!parameter) return void 0;
348
+ const staticHeaders = new Headers(options.additionalHeaders ?? {});
349
+ if (carriesSchemeParameter(parameter, new URL("http://startup.invalid/"), staticHeaders)) return "static";
350
+ if (options.headersMapper && (parameter.type === "header" || parameter.type === "cookie")) return "per-request";
351
+ return void 0;
352
+ }
353
+ function isBearerScheme(security) {
354
+ if (!security || security.type !== "http") return false;
355
+ return (security.httpScheme ?? "bearer").toLowerCase() === "bearer";
356
+ }
357
+ function credentialFieldOf(security) {
358
+ if (security?.type === "apiKey") return "apiKey";
359
+ if (security?.type === "oauth2" || security?.type === "openIdConnect") return "oauth2Token";
360
+ if (security?.type === "http" && security.httpScheme?.toLowerCase() === "basic") return "basic";
361
+ return "jwt";
362
+ }
363
+ function formatMissingSecurityMappingsError(adapterName, tools, missingMappings) {
364
+ const schemes = describeSecuritySchemes(tools);
365
+ const fieldOf = (scheme) => credentialFieldOf(schemes.get(scheme));
366
+ const firstField = fieldOf(missingMappings[0] ?? "");
367
+ const bearerSchemes = missingMappings.filter((scheme) => isBearerScheme(schemes.get(scheme)));
368
+ const lines = [
369
+ `[OpenAPI Adapter: ${adapterName}] Invalid security configuration.`,
370
+ `Missing auth provider mappings for security schemes: ${missingMappings.join(", ")}`,
371
+ "",
372
+ "Your OpenAPI spec requires these security schemes, but no credential option covers them.",
373
+ "",
374
+ "Add one of the following to your adapter configuration:",
375
+ "",
376
+ "1. authProviderMapper (recommended): a function per scheme that returns the credential issued for the API:",
377
+ " authProviderMapper: {",
378
+ ...missingMappings.map((scheme) => ` '${scheme}': (ctx) => getApiCredential(ctx), // the ${fieldOf(scheme)}`),
379
+ " }",
380
+ "",
381
+ "2. securityResolver:",
382
+ ` securityResolver: async (tool, ctx) => ({ ${firstField}: await getApiCredential(ctx) })`,
383
+ "",
384
+ "3. staticAuth (one credential for every caller):",
385
+ ` staticAuth: { ${firstField}: process.env.API_CREDENTIAL }`
386
+ ];
387
+ if (bearerSchemes.length > 0) {
388
+ lines.push(
389
+ "",
390
+ `4. passthroughCallerToken: true, for the HTTP bearer schemes (${bearerSchemes.join(", ")}) only, and only if the API`,
391
+ " accepts the MCP client's own token (same issuer and audience). It sends nothing for other schemes."
392
+ );
393
+ }
394
+ return lines.join("\n");
395
+ }
396
+ function authenticationRequiredError(tool2) {
397
+ const required = requiredSecurityOf(tool2);
398
+ const schemes = [...new Map(required.map((security) => [security.scheme, security])).values()];
399
+ const schemesStr = schemes.map((security) => `${security.scheme} (${security.type})`).join(", ") || "unknown";
400
+ const first = schemes[0];
401
+ const firstScheme = first?.scheme ?? "BearerAuth";
402
+ const field = credentialFieldOf(first);
403
+ const bearerSchemes = schemes.filter(isBearerScheme).map((security) => security.scheme);
404
+ const solutions = [
405
+ ` 1. Add authProviderMapper: { '${firstScheme}': (ctx) => getApiCredential(ctx) }, returning the ${field} issued for the API`,
406
+ ` 2. Add securityResolver: async (tool, ctx) => ({ ${field}: await getApiCredential(ctx) })`,
407
+ ` 3. Add staticAuth: { ${field}: process.env.API_CREDENTIAL }`
408
+ ];
409
+ if (bearerSchemes.length > 0) {
410
+ solutions.push(
411
+ ` 4. Set passthroughCallerToken: true, only if the API accepts the MCP client's own token (it fills ${bearerSchemes.join(", ")} only)`
412
+ );
413
+ }
414
+ return new Error(
415
+ `Authentication required for tool '${tool2.name}': no credential for its security schemes.
416
+ Required security schemes: ${schemesStr}
417
+ Solutions:
418
+ ` + solutions.join("\n")
419
+ );
420
+ }
421
+ function requiredSecurityOf(tool2) {
422
+ return tool2.mapper.flatMap((mapper) => mapper.security && mapper.required === true ? [mapper.security] : []);
423
+ }
424
+ function authorizationSchemeOf(security) {
425
+ if (security?.type === "http") return (security.httpScheme ?? "bearer").toLowerCase();
426
+ if (security?.type === "oauth2" || security?.type === "openIdConnect") return "bearer";
427
+ return void 0;
428
+ }
429
+ function carriesSchemeParameter(mapper, url, headers) {
430
+ if (mapper.type === "query") return !!url.searchParams.get(mapper.key);
431
+ if (mapper.type === "cookie") {
432
+ return (headers.get("cookie") ?? "").split(";").some((pair) => {
433
+ const [name, ...value2] = pair.trim().split("=");
434
+ return name === mapper.key && value2.join("=") !== "";
435
+ });
436
+ }
437
+ if (mapper.type !== "header") return false;
438
+ const value = headers.get(mapper.key)?.trim();
439
+ if (!value) return false;
440
+ const scheme = authorizationSchemeOf(mapper.security);
441
+ if (!scheme) return true;
442
+ const space = value.indexOf(" ");
443
+ return space > 0 && value.slice(0, space).toLowerCase() === scheme && value.slice(space + 1).trim() !== "";
444
+ }
445
+ function assertRequestHasCredential(tool2, url, headers) {
446
+ const securityMappers = tool2.mapper.filter((mapper) => mapper.security && mapper.required === true);
447
+ if (securityMappers.length === 0) return;
448
+ const parsedUrl = new URL(url);
449
+ if (!securityMappers.some((mapper) => carriesSchemeParameter(mapper, parsedUrl, headers))) {
450
+ throw authenticationRequiredError(tool2);
451
+ }
452
+ }
308
453
  function validateSecurityConfiguration(tools, options) {
454
+ const result = validateCredentialSources(tools, options);
455
+ const schemesInInput = options.securitySchemesInInput ?? [];
456
+ if (options.generateOptions?.includeSecurityInInput !== true && schemesInInput.length > 0) {
457
+ result.securityRiskScore = "high";
458
+ result.warnings.push(
459
+ `SECURITY WARNING: securitySchemesInInput is enabled. The model provides the credential for security schemes ${schemesInInput.join(", ")} in tool inputs, used when the server supplies none. Credentials may be logged or exposed, and the model chooses whose account a call uses.`
460
+ );
461
+ }
462
+ return result;
463
+ }
464
+ function validateCredentialSources(tools, options) {
309
465
  const result = {
310
466
  valid: true,
311
467
  missingMappings: [],
312
468
  warnings: [],
313
469
  securityRiskScore: "low"
314
470
  };
315
- const securitySchemes = extractSecuritySchemes(tools);
471
+ const securitySchemes = describeSecuritySchemes(tools);
316
472
  const includeSecurityInInput = options.generateOptions?.includeSecurityInInput ?? false;
317
473
  if (includeSecurityInInput) {
318
474
  result.securityRiskScore = "high";
319
475
  result.warnings.push(
320
- "SECURITY WARNING: includeSecurityInInput is enabled. Users will provide authentication directly in tool inputs. This increases security risk as credentials may be logged or exposed."
476
+ "SECURITY WARNING: includeSecurityInInput is enabled. Users will provide authentication directly in tool inputs (used for a scheme when the server supplies none). This increases security risk as credentials may be logged or exposed."
321
477
  );
322
478
  return result;
323
479
  }
@@ -328,28 +484,35 @@ function validateSecurityConfiguration(tools, options) {
328
484
  );
329
485
  return result;
330
486
  }
331
- if (options.staticAuth && Object.keys(options.staticAuth).length > 0) {
487
+ if (hasStaticAuth(options)) {
332
488
  result.securityRiskScore = "medium";
333
489
  result.warnings.push(
334
490
  "SECURITY INFO: Using staticAuth with hardcoded credentials. Ensure credentials are stored securely (environment variables, secrets manager)."
335
491
  );
336
- return result;
492
+ return withPassthroughRisk(result, securitySchemes, options);
337
493
  }
338
494
  const schemesInInput = new Set(options.securitySchemesInInput || []);
339
495
  if (options.authProviderMapper || schemesInInput.size > 0) {
340
- result.securityRiskScore = schemesInInput.size > 0 ? "medium" : "low";
341
- if (schemesInInput.size > 0) {
342
- result.warnings.push(
343
- `INFO: Per-scheme security control enabled. Schemes in input: ${Array.from(schemesInInput).join(", ")}`
344
- );
345
- }
346
- for (const scheme of securitySchemes) {
496
+ result.securityRiskScore = "low";
497
+ const passthroughFallback = [];
498
+ const perRequestHeaders = [];
499
+ const schemeParameters = schemeParametersOf(tools);
500
+ for (const [scheme, security] of securitySchemes) {
347
501
  if (schemesInInput.has(scheme)) {
348
502
  continue;
349
503
  }
350
504
  if (!options.authProviderMapper?.[scheme]) {
351
- result.valid = false;
352
- result.missingMappings.push(scheme);
505
+ const headerSource = headerCredentialSource(schemeParameters.get(scheme), options);
506
+ if (headerSource === "static") {
507
+ continue;
508
+ } else if (headerSource === "per-request") {
509
+ perRequestHeaders.push(scheme);
510
+ } else if (options.passthroughCallerToken === true && isBearerScheme(security)) {
511
+ passthroughFallback.push(scheme);
512
+ } else {
513
+ result.valid = false;
514
+ result.missingMappings.push(scheme);
515
+ }
353
516
  }
354
517
  }
355
518
  if (!result.valid) {
@@ -357,47 +520,113 @@ function validateSecurityConfiguration(tools, options) {
357
520
  `ERROR: Missing auth provider mappings for security schemes: ${result.missingMappings.join(", ")}`
358
521
  );
359
522
  }
523
+ if (perRequestHeaders.length > 0) {
524
+ result.warnings.push(
525
+ `INFO: Security schemes with no authProviderMapper entry (${perRequestHeaders.join(", ")}) take their credential from headersMapper. An operation that requires only them is refused when headersMapper sets none.`
526
+ );
527
+ }
528
+ withPassthroughRisk(result, securitySchemes, options);
529
+ if (passthroughFallback.length > 0) {
530
+ result.warnings.push(
531
+ `SECURITY WARNING: Security schemes with no authProviderMapper entry (${passthroughFallback.join(", ")}) get the MCP client's own token, because passthroughCallerToken is enabled.`
532
+ );
533
+ }
360
534
  return result;
361
535
  }
362
- if (securitySchemes.size > 0) {
536
+ if (securitySchemes.size > 0 && options.passthroughCallerToken !== true) {
537
+ const schemesStr = Array.from(securitySchemes.keys()).join(", ");
363
538
  result.securityRiskScore = "medium";
364
539
  result.warnings.push(
365
- `INFO: No auth configuration provided. Using default ctx.authInfo.token for all security schemes: ${Array.from(
366
- securitySchemes
367
- ).join(", ")}`
540
+ `SECURITY WARNING: No auth configuration provided, so the adapter has no credentials for the API (security schemes: ${schemesStr}). Operations that require authentication fail unless additionalHeaders or headersMapper sets their credential. The MCP client's own token is not forwarded: configure authProviderMapper, securityResolver or staticAuth, or set passthroughCallerToken: true if the API is meant to receive the caller's MCP token.`
368
541
  );
542
+ }
543
+ const unfilled = [...securitySchemes].filter(([, security]) => !isBearerScheme(security)).map(([scheme]) => scheme);
544
+ if (options.passthroughCallerToken === true && unfilled.length > 0) {
545
+ result.securityRiskScore = "medium";
369
546
  result.warnings.push(
370
- "RECOMMENDATION: For multiple auth providers, use authProviderMapper or securityResolver to map each security scheme to the correct auth provider."
547
+ `SECURITY WARNING: The adapter has no credentials for the API for security schemes: ${unfilled.join(", ")}. passthroughCallerToken sends only a bearer token, which these schemes don't use, so operations that require only them fail unless additionalHeaders or headersMapper sets their credential. Configure authProviderMapper, securityResolver or staticAuth for them.`
371
548
  );
372
549
  }
550
+ return withPassthroughRisk(result, securitySchemes, options);
551
+ }
552
+ function canPassThroughCallerToken(options) {
553
+ return options.passthroughCallerToken === true && !options.securityResolver && !hasStaticAuth(options);
554
+ }
555
+ function withPassthroughRisk(result, securitySchemes, options) {
556
+ const bearerSchemes = [...securitySchemes].filter(([, security]) => isBearerScheme(security));
557
+ if (bearerSchemes.length === 0 || !canPassThroughCallerToken(options)) {
558
+ return result;
559
+ }
560
+ const schemesStr = bearerSchemes.map(([scheme]) => scheme).join(", ");
561
+ const when = options.authProviderMapper ? " whenever no authProviderMapper function returns a credential" : "";
562
+ result.securityRiskScore = "high";
563
+ result.warnings.push(
564
+ `SECURITY WARNING: passthroughCallerToken is enabled. The MCP client's own token (ctx.authInfo.token) is sent to the API${when} for security schemes: ${schemesStr}. Only use this when the API accepts tokens issued for this MCP server.`
565
+ );
373
566
  return result;
374
567
  }
375
- async function resolveToolSecurity(tool2, ctx, options) {
568
+ async function resolveToolSecurity(tool2, ctx, options, request = {}) {
376
569
  const securityResolver = new import_mcp_from_openapi2.SecurityResolver();
377
570
  const securityContext = await createSecurityContextFromAuth(tool2, ctx, options);
378
571
  const hasAuth = securityContext.jwt || securityContext.apiKey || securityContext.basic || securityContext.oauth2Token || securityContext.apiKeys && Object.keys(securityContext.apiKeys).length > 0 || securityContext.customHeaders && Object.keys(securityContext.customHeaders).length > 0;
379
572
  const requiresSecurity = tool2.mapper.some((m) => m.security && m.required === true);
380
- if (requiresSecurity && !hasAuth) {
381
- const requiredSchemes = tool2.mapper.filter((m) => m.security && m.required === true).map((m) => m.security?.scheme ?? "unknown");
382
- const uniqueSchemes = [...new Set(requiredSchemes)];
383
- const schemesStr = uniqueSchemes.join(", ") || "unknown";
384
- const firstScheme = uniqueSchemes[0] || "BearerAuth";
385
- throw new Error(
386
- `Authentication required for tool '${tool2.name}' but no auth configuration found.
387
- Required security schemes: ${schemesStr}
388
- Solutions:
389
- 1. Add authProviderMapper: { '${firstScheme}': (ctx) => ctx.authInfo.user?.token }
390
- 2. Add securityResolver: (tool, ctx) => ({ jwt: ctx.authInfo.token })
391
- 3. Add staticAuth: { jwt: process.env.API_TOKEN }
392
- 4. Set generateOptions.includeSecurityInInput: true (not recommended for production)`
393
- );
573
+ const fromInput = request.input ? await resolveInputSecurity(tool2, request.input, options, securityResolver) : void 0;
574
+ const hasInputCredential = !!fromInput && Object.keys(fromInput.headers).length + Object.keys(fromInput.query).length + Object.keys(fromInput.cookies).length > 0;
575
+ if (requiresSecurity && !hasAuth && !hasInputCredential && !request.deferCredentialCheck) {
576
+ throw authenticationRequiredError(tool2);
394
577
  }
395
- return await securityResolver.resolve(tool2.mapper, securityContext);
578
+ const resolved = await securityResolver.resolve(tool2.mapper, securityContext);
579
+ if (!fromInput) return resolved;
580
+ return {
581
+ ...resolved,
582
+ headers: { ...fromInput.headers, ...resolved.headers },
583
+ query: { ...fromInput.query, ...resolved.query },
584
+ cookies: { ...fromInput.cookies, ...resolved.cookies }
585
+ };
586
+ }
587
+ function isInputScheme(scheme, options) {
588
+ return options.generateOptions?.includeSecurityInInput === true || (options.securitySchemesInInput ?? []).includes(scheme);
589
+ }
590
+ function inputCredentialContext(security, value) {
591
+ const bare = value.replace(/^(?:bearer|basic)\s+/i, "");
592
+ if (security.type === "apiKey") {
593
+ return security.apiKeyName ? { apiKeys: { [security.apiKeyName]: value } } : { apiKey: value };
594
+ }
595
+ if (security.type === "oauth2" || security.type === "openIdConnect") return { oauth2Token: bare };
596
+ if (security.type === "http") {
597
+ const httpScheme = (security.httpScheme ?? "bearer").toLowerCase();
598
+ if (httpScheme === "bearer") return { jwt: bare };
599
+ if (httpScheme === "basic") return { basic: bare };
600
+ }
601
+ return void 0;
602
+ }
603
+ async function resolveInputSecurity(tool2, input, options, securityResolver) {
604
+ const resolved = { headers: {}, query: {}, cookies: {} };
605
+ for (const mapper of tool2.mapper) {
606
+ const security = mapper.security;
607
+ if (!security || !isInputScheme(security.scheme, options)) continue;
608
+ const value = input[mapper.inputKey];
609
+ if (typeof value !== "string" || value === "") continue;
610
+ if (/[\r\n\x00\f\v]/.test(value)) {
611
+ throw new import_sdk2.PublicMcpError(
612
+ `Invalid value for '${mapper.inputKey}': contains control characters (possible header injection attack)`,
613
+ "INVALID_HEADER_VALUE",
614
+ 400
615
+ );
616
+ }
617
+ const context = inputCredentialContext(security, value);
618
+ if (!context) continue;
619
+ const one = await securityResolver.resolve([mapper], (0, import_mcp_from_openapi2.createSecurityContext)(context));
620
+ Object.assign(resolved.headers, one.headers);
621
+ Object.assign(resolved.query, one.query);
622
+ Object.assign(resolved.cookies, one.cookies);
623
+ }
624
+ return resolved;
396
625
  }
397
626
 
398
627
  // libs/adapters/src/openapi/openapi.tool.ts
399
628
  var import_lazy_zod2 = require("@frontmcp/lazy-zod");
400
- var import_sdk3 = require("@frontmcp/sdk");
629
+ var import_sdk4 = require("@frontmcp/sdk");
401
630
  var import_utils3 = require("@frontmcp/utils");
402
631
 
403
632
  // libs/adapters/src/openapi/openapi.frontmcp-schema.ts
@@ -641,7 +870,7 @@ function formatZodIssues(issues, prefix) {
641
870
  }
642
871
 
643
872
  // libs/adapters/src/openapi/openapi.utils.ts
644
- var import_sdk2 = require("@frontmcp/sdk");
873
+ var import_sdk3 = require("@frontmcp/sdk");
645
874
  var import_utils2 = require("@frontmcp/utils");
646
875
  function coerceToString(value, paramName, location) {
647
876
  if (value === null || value === void 0) {
@@ -699,7 +928,7 @@ function buildRequest(tool2, input, security, baseUrl) {
699
928
  case "header": {
700
929
  const headerValue = coerceToString(value, mapper.key, "header");
701
930
  if (/[\r\n\x00\f\v]/.test(headerValue)) {
702
- throw new import_sdk2.PublicMcpError(
931
+ throw new import_sdk3.PublicMcpError(
703
932
  `Invalid header value for '${mapper.key}': contains control characters (possible header injection attack)`,
704
933
  "INVALID_HEADER_VALUE",
705
934
  400
@@ -864,7 +1093,7 @@ function createOpenApiTool(openapiTool, options, logger) {
864
1093
  if (toolTransform.ui) {
865
1094
  toolMetadata["ui"] = toolTransform.ui;
866
1095
  }
867
- return (0, import_sdk3.tool)(toolMetadata)(async (input, toolCtx) => {
1096
+ return (0, import_sdk4.tool)(toolMetadata)(async (input, toolCtx) => {
868
1097
  const ctx = toolCtx.context;
869
1098
  const transformContext = {
870
1099
  ctx,
@@ -876,7 +1105,10 @@ function createOpenApiTool(openapiTool, options, logger) {
876
1105
  inputTransforms,
877
1106
  transformContext
878
1107
  );
879
- const security = await resolveToolSecurity(openapiTool, ctx, options);
1108
+ const security = await resolveToolSecurity(openapiTool, ctx, options, {
1109
+ input: injectedInput,
1110
+ deferCredentialCheck: true
1111
+ });
880
1112
  const { url, headers, body: requestBody } = buildRequest(openapiTool, injectedInput, security, options.baseUrl);
881
1113
  applyAdditionalHeaders(headers, options.additionalHeaders);
882
1114
  if (options.headersMapper) {
@@ -892,6 +1124,7 @@ function createOpenApiTool(openapiTool, options, logger) {
892
1124
  throw new Error(`headersMapper failed for tool '${openapiTool.name}': ${errorMessage}`, { cause: err });
893
1125
  }
894
1126
  }
1127
+ assertRequestHasCredential(openapiTool, url, headers);
895
1128
  let finalBody = requestBody;
896
1129
  if (options.bodyMapper && requestBody) {
897
1130
  try {
@@ -1113,7 +1346,7 @@ function createConsoleLogger(prefix) {
1113
1346
  child: (childPrefix) => createConsoleLogger(`${prefix}:${childPrefix}`)
1114
1347
  };
1115
1348
  }
1116
- var OpenapiAdapter = class extends import_sdk4.DynamicAdapter {
1349
+ var OpenapiAdapter = class extends import_sdk5.DynamicAdapter {
1117
1350
  generator;
1118
1351
  logger;
1119
1352
  options;
@@ -1176,28 +1409,7 @@ var OpenapiAdapter = class extends import_sdk4.DynamicAdapter {
1176
1409
  });
1177
1410
  }
1178
1411
  if (!validation.valid) {
1179
- throw new Error(
1180
- `[OpenAPI Adapter: ${this.options.name}] Invalid security configuration.
1181
- Missing auth provider mappings for security schemes: ${validation.missingMappings.join(", ")}
1182
-
1183
- Your OpenAPI spec requires these security schemes, but no auth configuration was provided.
1184
-
1185
- Add one of the following to your adapter configuration:
1186
-
1187
- 1. authProviderMapper (recommended):
1188
- authProviderMapper: {
1189
- ` + validation.missingMappings.map((s) => ` '${s}': (authInfo) => authInfo.user?.${s.toLowerCase()}Token,`).join("\n") + `
1190
- }
1191
-
1192
- 2. securityResolver:
1193
- securityResolver: (tool, authInfo) => ({ jwt: authInfo.token })
1194
-
1195
- 3. staticAuth:
1196
- staticAuth: { jwt: process.env.API_TOKEN }
1197
-
1198
- 4. Include security in input (NOT recommended for production):
1199
- generateOptions: { includeSecurityInInput: true }`
1200
- );
1412
+ throw new Error(formatMissingSecurityMappingsError(this.options.name, openapiTools, validation.missingMappings));
1201
1413
  }
1202
1414
  let transformedTools = openapiTools;
1203
1415
  if (this.options.descriptionMode && this.options.descriptionMode !== "summaryOnly") {
@@ -1851,7 +2063,7 @@ ${this.formatSchemaAsSummary(schema)}`;
1851
2063
  }
1852
2064
  };
1853
2065
  OpenapiAdapter = __decorateClass([
1854
- (0, import_sdk4.Adapter)({
2066
+ (0, import_sdk5.Adapter)({
1855
2067
  name: "openapi",
1856
2068
  description: "OpenAPI adapter for FrontMCP - Automatically generates MCP tools from OpenAPI specifications"
1857
2069
  })