@frontmcp/adapters 1.8.3 → 1.8.5
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 +256 -68
- package/esm/openapi/index.mjs +256 -68
- package/esm/package.json +6 -6
- package/index.js +257 -72
- package/openapi/index.js +257 -72
- package/openapi/openapi.adapter.d.ts +3 -2
- package/openapi/openapi.adapter.d.ts.map +1 -1
- package/openapi/openapi.security.d.ts +49 -4
- package/openapi/openapi.security.d.ts.map +1 -1
- package/openapi/openapi.tool.d.ts.map +1 -1
- package/openapi/openapi.types.d.ts +4 -2
- package/openapi/openapi.types.d.ts.map +1 -1
- package/package.json +6 -6
- package/skills/bundle/bundle.schema.d.ts +2 -2
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
|
|
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,6 +219,7 @@ 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);
|
|
@@ -273,7 +274,7 @@ 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
|
}
|
|
@@ -321,19 +322,163 @@ function extractSecuritySchemes(tools) {
|
|
|
321
322
|
}
|
|
322
323
|
return schemes;
|
|
323
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
|
+
}
|
|
324
453
|
function validateSecurityConfiguration(tools, options) {
|
|
454
|
+
const result = validateCredentialSources(tools, options);
|
|
455
|
+
const schemesInInput = inputSecuritySchemes(options);
|
|
456
|
+
if (schemesInInput !== "all" && schemesInInput.size > 0) {
|
|
457
|
+
const include = options.generateOptions?.includeSecurityInInput;
|
|
458
|
+
const sources = [
|
|
459
|
+
(options.securitySchemesInInput?.length ?? 0) > 0 ? "securitySchemesInInput" : void 0,
|
|
460
|
+
Array.isArray(include) && include.length > 0 ? "an includeSecurityInInput list" : void 0
|
|
461
|
+
].filter((source) => source !== void 0);
|
|
462
|
+
result.securityRiskScore = "high";
|
|
463
|
+
result.warnings.push(
|
|
464
|
+
`SECURITY WARNING: ${sources.join(" and ")} ${sources.length > 1 ? "are" : "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.`
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
return result;
|
|
468
|
+
}
|
|
469
|
+
function validateCredentialSources(tools, options) {
|
|
325
470
|
const result = {
|
|
326
471
|
valid: true,
|
|
327
472
|
missingMappings: [],
|
|
328
473
|
warnings: [],
|
|
329
474
|
securityRiskScore: "low"
|
|
330
475
|
};
|
|
331
|
-
const securitySchemes =
|
|
332
|
-
const
|
|
333
|
-
if (
|
|
476
|
+
const securitySchemes = describeSecuritySchemes(tools);
|
|
477
|
+
const inputSchemes = inputSecuritySchemes(options);
|
|
478
|
+
if (inputSchemes === "all") {
|
|
334
479
|
result.securityRiskScore = "high";
|
|
335
480
|
result.warnings.push(
|
|
336
|
-
"SECURITY WARNING: includeSecurityInInput is enabled. Users will provide authentication directly in tool inputs. This increases security risk as credentials may be logged or exposed."
|
|
481
|
+
"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."
|
|
337
482
|
);
|
|
338
483
|
return result;
|
|
339
484
|
}
|
|
@@ -351,21 +496,23 @@ function validateSecurityConfiguration(tools, options) {
|
|
|
351
496
|
);
|
|
352
497
|
return withPassthroughRisk(result, securitySchemes, options);
|
|
353
498
|
}
|
|
354
|
-
const schemesInInput =
|
|
499
|
+
const schemesInInput = inputSchemes;
|
|
355
500
|
if (options.authProviderMapper || schemesInInput.size > 0) {
|
|
356
|
-
result.securityRiskScore =
|
|
357
|
-
if (schemesInInput.size > 0) {
|
|
358
|
-
result.warnings.push(
|
|
359
|
-
`INFO: Per-scheme security control enabled. Schemes in input: ${Array.from(schemesInInput).join(", ")}`
|
|
360
|
-
);
|
|
361
|
-
}
|
|
501
|
+
result.securityRiskScore = "low";
|
|
362
502
|
const passthroughFallback = [];
|
|
363
|
-
|
|
503
|
+
const perRequestHeaders = [];
|
|
504
|
+
const schemeParameters = schemeParametersOf(tools);
|
|
505
|
+
for (const [scheme, security] of securitySchemes) {
|
|
364
506
|
if (schemesInInput.has(scheme)) {
|
|
365
507
|
continue;
|
|
366
508
|
}
|
|
367
509
|
if (!options.authProviderMapper?.[scheme]) {
|
|
368
|
-
|
|
510
|
+
const headerSource = headerCredentialSource(schemeParameters.get(scheme), options);
|
|
511
|
+
if (headerSource === "static") {
|
|
512
|
+
continue;
|
|
513
|
+
} else if (headerSource === "per-request") {
|
|
514
|
+
perRequestHeaders.push(scheme);
|
|
515
|
+
} else if (options.passthroughCallerToken === true && isBearerScheme(security)) {
|
|
369
516
|
passthroughFallback.push(scheme);
|
|
370
517
|
} else {
|
|
371
518
|
result.valid = false;
|
|
@@ -378,6 +525,11 @@ function validateSecurityConfiguration(tools, options) {
|
|
|
378
525
|
`ERROR: Missing auth provider mappings for security schemes: ${result.missingMappings.join(", ")}`
|
|
379
526
|
);
|
|
380
527
|
}
|
|
528
|
+
if (perRequestHeaders.length > 0) {
|
|
529
|
+
result.warnings.push(
|
|
530
|
+
`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.`
|
|
531
|
+
);
|
|
532
|
+
}
|
|
381
533
|
withPassthroughRisk(result, securitySchemes, options);
|
|
382
534
|
if (passthroughFallback.length > 0) {
|
|
383
535
|
result.warnings.push(
|
|
@@ -387,10 +539,17 @@ function validateSecurityConfiguration(tools, options) {
|
|
|
387
539
|
return result;
|
|
388
540
|
}
|
|
389
541
|
if (securitySchemes.size > 0 && options.passthroughCallerToken !== true) {
|
|
390
|
-
const schemesStr = Array.from(securitySchemes).join(", ");
|
|
542
|
+
const schemesStr = Array.from(securitySchemes.keys()).join(", ");
|
|
543
|
+
result.securityRiskScore = "medium";
|
|
544
|
+
result.warnings.push(
|
|
545
|
+
`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.`
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
const unfilled = [...securitySchemes].filter(([, security]) => !isBearerScheme(security)).map(([scheme]) => scheme);
|
|
549
|
+
if (options.passthroughCallerToken === true && unfilled.length > 0) {
|
|
391
550
|
result.securityRiskScore = "medium";
|
|
392
551
|
result.warnings.push(
|
|
393
|
-
`SECURITY WARNING:
|
|
552
|
+
`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.`
|
|
394
553
|
);
|
|
395
554
|
}
|
|
396
555
|
return withPassthroughRisk(result, securitySchemes, options);
|
|
@@ -399,10 +558,11 @@ function canPassThroughCallerToken(options) {
|
|
|
399
558
|
return options.passthroughCallerToken === true && !options.securityResolver && !hasStaticAuth(options);
|
|
400
559
|
}
|
|
401
560
|
function withPassthroughRisk(result, securitySchemes, options) {
|
|
402
|
-
|
|
561
|
+
const bearerSchemes = [...securitySchemes].filter(([, security]) => isBearerScheme(security));
|
|
562
|
+
if (bearerSchemes.length === 0 || !canPassThroughCallerToken(options)) {
|
|
403
563
|
return result;
|
|
404
564
|
}
|
|
405
|
-
const schemesStr =
|
|
565
|
+
const schemesStr = bearerSchemes.map(([scheme]) => scheme).join(", ");
|
|
406
566
|
const when = options.authProviderMapper ? " whenever no authProviderMapper function returns a credential" : "";
|
|
407
567
|
result.securityRiskScore = "high";
|
|
408
568
|
result.warnings.push(
|
|
@@ -410,33 +570,74 @@ function withPassthroughRisk(result, securitySchemes, options) {
|
|
|
410
570
|
);
|
|
411
571
|
return result;
|
|
412
572
|
}
|
|
413
|
-
async function resolveToolSecurity(tool2, ctx, options) {
|
|
573
|
+
async function resolveToolSecurity(tool2, ctx, options, request = {}) {
|
|
414
574
|
const securityResolver = new import_mcp_from_openapi2.SecurityResolver();
|
|
415
575
|
const securityContext = await createSecurityContextFromAuth(tool2, ctx, options);
|
|
416
576
|
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;
|
|
417
577
|
const requiresSecurity = tool2.mapper.some((m) => m.security && m.required === true);
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const firstScheme = uniqueSchemes[0] || "BearerAuth";
|
|
423
|
-
throw new Error(
|
|
424
|
-
`Authentication required for tool '${tool2.name}' but no auth configuration found.
|
|
425
|
-
Required security schemes: ${schemesStr}
|
|
426
|
-
Solutions:
|
|
427
|
-
1. Add authProviderMapper: { '${firstScheme}': (ctx) => ctx.authInfo.user?.token }
|
|
428
|
-
2. Add securityResolver: async (tool, ctx) => ({ jwt: await getApiToken(ctx) })
|
|
429
|
-
3. Add staticAuth: { jwt: process.env.API_TOKEN }
|
|
430
|
-
4. Set passthroughCallerToken: true, only if the API accepts the MCP client's own token
|
|
431
|
-
5. Set generateOptions.includeSecurityInInput: true (not recommended for production)`
|
|
432
|
-
);
|
|
578
|
+
const fromInput = request.input ? await resolveInputSecurity(tool2, request.input, options, securityResolver) : void 0;
|
|
579
|
+
const hasInputCredential = !!fromInput && Object.keys(fromInput.headers).length + Object.keys(fromInput.query).length + Object.keys(fromInput.cookies).length > 0;
|
|
580
|
+
if (requiresSecurity && !hasAuth && !hasInputCredential && !request.deferCredentialCheck) {
|
|
581
|
+
throw authenticationRequiredError(tool2);
|
|
433
582
|
}
|
|
434
|
-
|
|
583
|
+
const resolved = await securityResolver.resolve(tool2.mapper, securityContext);
|
|
584
|
+
if (!fromInput) return resolved;
|
|
585
|
+
return {
|
|
586
|
+
...resolved,
|
|
587
|
+
headers: { ...fromInput.headers, ...resolved.headers },
|
|
588
|
+
query: { ...fromInput.query, ...resolved.query },
|
|
589
|
+
cookies: { ...fromInput.cookies, ...resolved.cookies }
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
function inputSecuritySchemes(options) {
|
|
593
|
+
const include = options.generateOptions?.includeSecurityInInput;
|
|
594
|
+
if (include === true) return "all";
|
|
595
|
+
return /* @__PURE__ */ new Set([...Array.isArray(include) ? include : [], ...options.securitySchemesInInput ?? []]);
|
|
596
|
+
}
|
|
597
|
+
function isInputScheme(scheme, options) {
|
|
598
|
+
const schemes = inputSecuritySchemes(options);
|
|
599
|
+
return schemes === "all" || schemes.has(scheme);
|
|
600
|
+
}
|
|
601
|
+
function inputCredentialContext(security, value) {
|
|
602
|
+
const bare = value.replace(/^(?:bearer|basic)\s+/i, "");
|
|
603
|
+
if (security.type === "apiKey") {
|
|
604
|
+
return security.apiKeyName ? { apiKeys: { [security.apiKeyName]: value } } : { apiKey: value };
|
|
605
|
+
}
|
|
606
|
+
if (security.type === "oauth2" || security.type === "openIdConnect") return { oauth2Token: bare };
|
|
607
|
+
if (security.type === "http") {
|
|
608
|
+
const httpScheme = (security.httpScheme ?? "bearer").toLowerCase();
|
|
609
|
+
if (httpScheme === "bearer") return { jwt: bare };
|
|
610
|
+
if (httpScheme === "basic") return { basic: bare };
|
|
611
|
+
}
|
|
612
|
+
return void 0;
|
|
613
|
+
}
|
|
614
|
+
async function resolveInputSecurity(tool2, input, options, securityResolver) {
|
|
615
|
+
const resolved = { headers: {}, query: {}, cookies: {} };
|
|
616
|
+
for (const mapper of tool2.mapper) {
|
|
617
|
+
const security = mapper.security;
|
|
618
|
+
if (!security || !isInputScheme(security.scheme, options)) continue;
|
|
619
|
+
const value = input[mapper.inputKey];
|
|
620
|
+
if (typeof value !== "string" || value === "") continue;
|
|
621
|
+
if (/[\r\n\x00\f\v]/.test(value)) {
|
|
622
|
+
throw new import_sdk2.PublicMcpError(
|
|
623
|
+
`Invalid value for '${mapper.inputKey}': contains control characters (possible header injection attack)`,
|
|
624
|
+
"INVALID_HEADER_VALUE",
|
|
625
|
+
400
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
const context = inputCredentialContext(security, value);
|
|
629
|
+
if (!context) continue;
|
|
630
|
+
const one = await securityResolver.resolve([mapper], (0, import_mcp_from_openapi2.createSecurityContext)(context));
|
|
631
|
+
Object.assign(resolved.headers, one.headers);
|
|
632
|
+
Object.assign(resolved.query, one.query);
|
|
633
|
+
Object.assign(resolved.cookies, one.cookies);
|
|
634
|
+
}
|
|
635
|
+
return resolved;
|
|
435
636
|
}
|
|
436
637
|
|
|
437
638
|
// libs/adapters/src/openapi/openapi.tool.ts
|
|
438
639
|
var import_lazy_zod2 = require("@frontmcp/lazy-zod");
|
|
439
|
-
var
|
|
640
|
+
var import_sdk4 = require("@frontmcp/sdk");
|
|
440
641
|
var import_utils3 = require("@frontmcp/utils");
|
|
441
642
|
|
|
442
643
|
// libs/adapters/src/openapi/openapi.frontmcp-schema.ts
|
|
@@ -680,7 +881,7 @@ function formatZodIssues(issues, prefix) {
|
|
|
680
881
|
}
|
|
681
882
|
|
|
682
883
|
// libs/adapters/src/openapi/openapi.utils.ts
|
|
683
|
-
var
|
|
884
|
+
var import_sdk3 = require("@frontmcp/sdk");
|
|
684
885
|
var import_utils2 = require("@frontmcp/utils");
|
|
685
886
|
function coerceToString(value, paramName, location) {
|
|
686
887
|
if (value === null || value === void 0) {
|
|
@@ -738,7 +939,7 @@ function buildRequest(tool2, input, security, baseUrl) {
|
|
|
738
939
|
case "header": {
|
|
739
940
|
const headerValue = coerceToString(value, mapper.key, "header");
|
|
740
941
|
if (/[\r\n\x00\f\v]/.test(headerValue)) {
|
|
741
|
-
throw new
|
|
942
|
+
throw new import_sdk3.PublicMcpError(
|
|
742
943
|
`Invalid header value for '${mapper.key}': contains control characters (possible header injection attack)`,
|
|
743
944
|
"INVALID_HEADER_VALUE",
|
|
744
945
|
400
|
|
@@ -903,7 +1104,7 @@ function createOpenApiTool(openapiTool, options, logger) {
|
|
|
903
1104
|
if (toolTransform.ui) {
|
|
904
1105
|
toolMetadata["ui"] = toolTransform.ui;
|
|
905
1106
|
}
|
|
906
|
-
return (0,
|
|
1107
|
+
return (0, import_sdk4.tool)(toolMetadata)(async (input, toolCtx) => {
|
|
907
1108
|
const ctx = toolCtx.context;
|
|
908
1109
|
const transformContext = {
|
|
909
1110
|
ctx,
|
|
@@ -915,7 +1116,10 @@ function createOpenApiTool(openapiTool, options, logger) {
|
|
|
915
1116
|
inputTransforms,
|
|
916
1117
|
transformContext
|
|
917
1118
|
);
|
|
918
|
-
const security = await resolveToolSecurity(openapiTool, ctx, options
|
|
1119
|
+
const security = await resolveToolSecurity(openapiTool, ctx, options, {
|
|
1120
|
+
input: injectedInput,
|
|
1121
|
+
deferCredentialCheck: true
|
|
1122
|
+
});
|
|
919
1123
|
const { url, headers, body: requestBody } = buildRequest(openapiTool, injectedInput, security, options.baseUrl);
|
|
920
1124
|
applyAdditionalHeaders(headers, options.additionalHeaders);
|
|
921
1125
|
if (options.headersMapper) {
|
|
@@ -931,6 +1135,7 @@ function createOpenApiTool(openapiTool, options, logger) {
|
|
|
931
1135
|
throw new Error(`headersMapper failed for tool '${openapiTool.name}': ${errorMessage}`, { cause: err });
|
|
932
1136
|
}
|
|
933
1137
|
}
|
|
1138
|
+
assertRequestHasCredential(openapiTool, url, headers);
|
|
934
1139
|
let finalBody = requestBody;
|
|
935
1140
|
if (options.bodyMapper && requestBody) {
|
|
936
1141
|
try {
|
|
@@ -1152,7 +1357,7 @@ function createConsoleLogger(prefix) {
|
|
|
1152
1357
|
child: (childPrefix) => createConsoleLogger(`${prefix}:${childPrefix}`)
|
|
1153
1358
|
};
|
|
1154
1359
|
}
|
|
1155
|
-
var OpenapiAdapter = class extends
|
|
1360
|
+
var OpenapiAdapter = class extends import_sdk5.DynamicAdapter {
|
|
1156
1361
|
generator;
|
|
1157
1362
|
logger;
|
|
1158
1363
|
options;
|
|
@@ -1179,8 +1384,9 @@ var OpenapiAdapter = class extends import_sdk4.DynamicAdapter {
|
|
|
1179
1384
|
if (!this.generator) {
|
|
1180
1385
|
this.generator = await this.initializeGenerator();
|
|
1181
1386
|
}
|
|
1182
|
-
const
|
|
1183
|
-
const
|
|
1387
|
+
const inputSchemes = inputSecuritySchemes(this.options);
|
|
1388
|
+
const hasPerSchemeControl = inputSchemes !== "all" && inputSchemes.size > 0;
|
|
1389
|
+
const includeSecurityInInput = inputSchemes === "all" || hasPerSchemeControl;
|
|
1184
1390
|
let openapiTools = await this.generator.generateTools({
|
|
1185
1391
|
includeOperations: this.options.generateOptions?.includeOperations,
|
|
1186
1392
|
excludeOperations: this.options.generateOptions?.excludeOperations,
|
|
@@ -1196,7 +1402,7 @@ var OpenapiAdapter = class extends import_sdk4.DynamicAdapter {
|
|
|
1196
1402
|
formatResolvers: this.options.generateOptions?.formatResolvers
|
|
1197
1403
|
});
|
|
1198
1404
|
if (hasPerSchemeControl) {
|
|
1199
|
-
openapiTools = openapiTools.map((tool2) => this.filterSecuritySchemes(tool2));
|
|
1405
|
+
openapiTools = openapiTools.map((tool2) => this.filterSecuritySchemes(tool2, inputSchemes));
|
|
1200
1406
|
}
|
|
1201
1407
|
const validation = validateSecurityConfiguration(openapiTools, this.options);
|
|
1202
1408
|
this.logger.info("Security Analysis:");
|
|
@@ -1215,28 +1421,7 @@ var OpenapiAdapter = class extends import_sdk4.DynamicAdapter {
|
|
|
1215
1421
|
});
|
|
1216
1422
|
}
|
|
1217
1423
|
if (!validation.valid) {
|
|
1218
|
-
throw new Error(
|
|
1219
|
-
`[OpenAPI Adapter: ${this.options.name}] Invalid security configuration.
|
|
1220
|
-
Missing auth provider mappings for security schemes: ${validation.missingMappings.join(", ")}
|
|
1221
|
-
|
|
1222
|
-
Your OpenAPI spec requires these security schemes, but no auth configuration was provided.
|
|
1223
|
-
|
|
1224
|
-
Add one of the following to your adapter configuration:
|
|
1225
|
-
|
|
1226
|
-
1. authProviderMapper (recommended):
|
|
1227
|
-
authProviderMapper: {
|
|
1228
|
-
` + validation.missingMappings.map((s) => ` '${s}': (authInfo) => authInfo.user?.${s.toLowerCase()}Token,`).join("\n") + `
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
2. securityResolver:
|
|
1232
|
-
securityResolver: async (tool, ctx) => ({ jwt: await getApiToken(ctx) })
|
|
1233
|
-
|
|
1234
|
-
3. staticAuth:
|
|
1235
|
-
staticAuth: { jwt: process.env.API_TOKEN }
|
|
1236
|
-
|
|
1237
|
-
4. Include security in input (NOT recommended for production):
|
|
1238
|
-
generateOptions: { includeSecurityInInput: true }`
|
|
1239
|
-
);
|
|
1424
|
+
throw new Error(formatMissingSecurityMappingsError(this.options.name, openapiTools, validation.missingMappings));
|
|
1240
1425
|
}
|
|
1241
1426
|
let transformedTools = openapiTools;
|
|
1242
1427
|
if (this.options.descriptionMode && this.options.descriptionMode !== "summaryOnly") {
|
|
@@ -1528,12 +1713,12 @@ ${opDescription}`;
|
|
|
1528
1713
|
};
|
|
1529
1714
|
}
|
|
1530
1715
|
/**
|
|
1531
|
-
* Filter security schemes in tool input
|
|
1532
|
-
* Removes security inputs that should be resolved from context
|
|
1716
|
+
* Filter security schemes in tool input to the ones named for it (`securitySchemesInInput`, or an
|
|
1717
|
+
* `includeSecurityInInput` list). Removes security inputs that should be resolved from context
|
|
1718
|
+
* instead of user input.
|
|
1533
1719
|
* @private
|
|
1534
1720
|
*/
|
|
1535
|
-
filterSecuritySchemes(tool2) {
|
|
1536
|
-
const allowedSchemes = new Set(this.options.securitySchemesInInput || []);
|
|
1721
|
+
filterSecuritySchemes(tool2, allowedSchemes) {
|
|
1537
1722
|
if (allowedSchemes.size === 0) return tool2;
|
|
1538
1723
|
const schemesToRemove = /* @__PURE__ */ new Set();
|
|
1539
1724
|
const inputKeysToRemove = /* @__PURE__ */ new Set();
|
|
@@ -1890,7 +2075,7 @@ ${this.formatSchemaAsSummary(schema)}`;
|
|
|
1890
2075
|
}
|
|
1891
2076
|
};
|
|
1892
2077
|
OpenapiAdapter = __decorateClass([
|
|
1893
|
-
(0,
|
|
2078
|
+
(0, import_sdk5.Adapter)({
|
|
1894
2079
|
name: "openapi",
|
|
1895
2080
|
description: "OpenAPI adapter for FrontMCP - Automatically generates MCP tools from OpenAPI specifications"
|
|
1896
2081
|
})
|