@frontmcp/adapters 1.8.3 → 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/esm/index.mjs +234 -58
- package/esm/openapi/index.mjs +234 -58
- package/esm/package.json +6 -6
- package/index.js +235 -62
- package/openapi/index.js +235 -62
- package/openapi/openapi.adapter.d.ts.map +1 -1
- package/openapi/openapi.security.d.ts +43 -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,158 @@ 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 = 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) {
|
|
325
465
|
const result = {
|
|
326
466
|
valid: true,
|
|
327
467
|
missingMappings: [],
|
|
328
468
|
warnings: [],
|
|
329
469
|
securityRiskScore: "low"
|
|
330
470
|
};
|
|
331
|
-
const securitySchemes =
|
|
471
|
+
const securitySchemes = describeSecuritySchemes(tools);
|
|
332
472
|
const includeSecurityInInput = options.generateOptions?.includeSecurityInInput ?? false;
|
|
333
473
|
if (includeSecurityInInput) {
|
|
334
474
|
result.securityRiskScore = "high";
|
|
335
475
|
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."
|
|
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."
|
|
337
477
|
);
|
|
338
478
|
return result;
|
|
339
479
|
}
|
|
@@ -353,19 +493,21 @@ function validateSecurityConfiguration(tools, options) {
|
|
|
353
493
|
}
|
|
354
494
|
const schemesInInput = new Set(options.securitySchemesInInput || []);
|
|
355
495
|
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
|
-
}
|
|
496
|
+
result.securityRiskScore = "low";
|
|
362
497
|
const passthroughFallback = [];
|
|
363
|
-
|
|
498
|
+
const perRequestHeaders = [];
|
|
499
|
+
const schemeParameters = schemeParametersOf(tools);
|
|
500
|
+
for (const [scheme, security] of securitySchemes) {
|
|
364
501
|
if (schemesInInput.has(scheme)) {
|
|
365
502
|
continue;
|
|
366
503
|
}
|
|
367
504
|
if (!options.authProviderMapper?.[scheme]) {
|
|
368
|
-
|
|
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)) {
|
|
369
511
|
passthroughFallback.push(scheme);
|
|
370
512
|
} else {
|
|
371
513
|
result.valid = false;
|
|
@@ -378,6 +520,11 @@ function validateSecurityConfiguration(tools, options) {
|
|
|
378
520
|
`ERROR: Missing auth provider mappings for security schemes: ${result.missingMappings.join(", ")}`
|
|
379
521
|
);
|
|
380
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
|
+
}
|
|
381
528
|
withPassthroughRisk(result, securitySchemes, options);
|
|
382
529
|
if (passthroughFallback.length > 0) {
|
|
383
530
|
result.warnings.push(
|
|
@@ -387,10 +534,17 @@ function validateSecurityConfiguration(tools, options) {
|
|
|
387
534
|
return result;
|
|
388
535
|
}
|
|
389
536
|
if (securitySchemes.size > 0 && options.passthroughCallerToken !== true) {
|
|
390
|
-
const schemesStr = Array.from(securitySchemes).join(", ");
|
|
537
|
+
const schemesStr = Array.from(securitySchemes.keys()).join(", ");
|
|
538
|
+
result.securityRiskScore = "medium";
|
|
539
|
+
result.warnings.push(
|
|
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.`
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
const unfilled = [...securitySchemes].filter(([, security]) => !isBearerScheme(security)).map(([scheme]) => scheme);
|
|
544
|
+
if (options.passthroughCallerToken === true && unfilled.length > 0) {
|
|
391
545
|
result.securityRiskScore = "medium";
|
|
392
546
|
result.warnings.push(
|
|
393
|
-
`SECURITY WARNING:
|
|
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.`
|
|
394
548
|
);
|
|
395
549
|
}
|
|
396
550
|
return withPassthroughRisk(result, securitySchemes, options);
|
|
@@ -399,10 +553,11 @@ function canPassThroughCallerToken(options) {
|
|
|
399
553
|
return options.passthroughCallerToken === true && !options.securityResolver && !hasStaticAuth(options);
|
|
400
554
|
}
|
|
401
555
|
function withPassthroughRisk(result, securitySchemes, options) {
|
|
402
|
-
|
|
556
|
+
const bearerSchemes = [...securitySchemes].filter(([, security]) => isBearerScheme(security));
|
|
557
|
+
if (bearerSchemes.length === 0 || !canPassThroughCallerToken(options)) {
|
|
403
558
|
return result;
|
|
404
559
|
}
|
|
405
|
-
const schemesStr =
|
|
560
|
+
const schemesStr = bearerSchemes.map(([scheme]) => scheme).join(", ");
|
|
406
561
|
const when = options.authProviderMapper ? " whenever no authProviderMapper function returns a credential" : "";
|
|
407
562
|
result.securityRiskScore = "high";
|
|
408
563
|
result.warnings.push(
|
|
@@ -410,33 +565,68 @@ function withPassthroughRisk(result, securitySchemes, options) {
|
|
|
410
565
|
);
|
|
411
566
|
return result;
|
|
412
567
|
}
|
|
413
|
-
async function resolveToolSecurity(tool2, ctx, options) {
|
|
568
|
+
async function resolveToolSecurity(tool2, ctx, options, request = {}) {
|
|
414
569
|
const securityResolver = new import_mcp_from_openapi2.SecurityResolver();
|
|
415
570
|
const securityContext = await createSecurityContextFromAuth(tool2, ctx, options);
|
|
416
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;
|
|
417
572
|
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
|
-
);
|
|
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);
|
|
433
577
|
}
|
|
434
|
-
|
|
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;
|
|
435
625
|
}
|
|
436
626
|
|
|
437
627
|
// libs/adapters/src/openapi/openapi.tool.ts
|
|
438
628
|
var import_lazy_zod2 = require("@frontmcp/lazy-zod");
|
|
439
|
-
var
|
|
629
|
+
var import_sdk4 = require("@frontmcp/sdk");
|
|
440
630
|
var import_utils3 = require("@frontmcp/utils");
|
|
441
631
|
|
|
442
632
|
// libs/adapters/src/openapi/openapi.frontmcp-schema.ts
|
|
@@ -680,7 +870,7 @@ function formatZodIssues(issues, prefix) {
|
|
|
680
870
|
}
|
|
681
871
|
|
|
682
872
|
// libs/adapters/src/openapi/openapi.utils.ts
|
|
683
|
-
var
|
|
873
|
+
var import_sdk3 = require("@frontmcp/sdk");
|
|
684
874
|
var import_utils2 = require("@frontmcp/utils");
|
|
685
875
|
function coerceToString(value, paramName, location) {
|
|
686
876
|
if (value === null || value === void 0) {
|
|
@@ -738,7 +928,7 @@ function buildRequest(tool2, input, security, baseUrl) {
|
|
|
738
928
|
case "header": {
|
|
739
929
|
const headerValue = coerceToString(value, mapper.key, "header");
|
|
740
930
|
if (/[\r\n\x00\f\v]/.test(headerValue)) {
|
|
741
|
-
throw new
|
|
931
|
+
throw new import_sdk3.PublicMcpError(
|
|
742
932
|
`Invalid header value for '${mapper.key}': contains control characters (possible header injection attack)`,
|
|
743
933
|
"INVALID_HEADER_VALUE",
|
|
744
934
|
400
|
|
@@ -903,7 +1093,7 @@ function createOpenApiTool(openapiTool, options, logger) {
|
|
|
903
1093
|
if (toolTransform.ui) {
|
|
904
1094
|
toolMetadata["ui"] = toolTransform.ui;
|
|
905
1095
|
}
|
|
906
|
-
return (0,
|
|
1096
|
+
return (0, import_sdk4.tool)(toolMetadata)(async (input, toolCtx) => {
|
|
907
1097
|
const ctx = toolCtx.context;
|
|
908
1098
|
const transformContext = {
|
|
909
1099
|
ctx,
|
|
@@ -915,7 +1105,10 @@ function createOpenApiTool(openapiTool, options, logger) {
|
|
|
915
1105
|
inputTransforms,
|
|
916
1106
|
transformContext
|
|
917
1107
|
);
|
|
918
|
-
const security = await resolveToolSecurity(openapiTool, ctx, options
|
|
1108
|
+
const security = await resolveToolSecurity(openapiTool, ctx, options, {
|
|
1109
|
+
input: injectedInput,
|
|
1110
|
+
deferCredentialCheck: true
|
|
1111
|
+
});
|
|
919
1112
|
const { url, headers, body: requestBody } = buildRequest(openapiTool, injectedInput, security, options.baseUrl);
|
|
920
1113
|
applyAdditionalHeaders(headers, options.additionalHeaders);
|
|
921
1114
|
if (options.headersMapper) {
|
|
@@ -931,6 +1124,7 @@ function createOpenApiTool(openapiTool, options, logger) {
|
|
|
931
1124
|
throw new Error(`headersMapper failed for tool '${openapiTool.name}': ${errorMessage}`, { cause: err });
|
|
932
1125
|
}
|
|
933
1126
|
}
|
|
1127
|
+
assertRequestHasCredential(openapiTool, url, headers);
|
|
934
1128
|
let finalBody = requestBody;
|
|
935
1129
|
if (options.bodyMapper && requestBody) {
|
|
936
1130
|
try {
|
|
@@ -1152,7 +1346,7 @@ function createConsoleLogger(prefix) {
|
|
|
1152
1346
|
child: (childPrefix) => createConsoleLogger(`${prefix}:${childPrefix}`)
|
|
1153
1347
|
};
|
|
1154
1348
|
}
|
|
1155
|
-
var OpenapiAdapter = class extends
|
|
1349
|
+
var OpenapiAdapter = class extends import_sdk5.DynamicAdapter {
|
|
1156
1350
|
generator;
|
|
1157
1351
|
logger;
|
|
1158
1352
|
options;
|
|
@@ -1215,28 +1409,7 @@ var OpenapiAdapter = class extends import_sdk4.DynamicAdapter {
|
|
|
1215
1409
|
});
|
|
1216
1410
|
}
|
|
1217
1411
|
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
|
-
);
|
|
1412
|
+
throw new Error(formatMissingSecurityMappingsError(this.options.name, openapiTools, validation.missingMappings));
|
|
1240
1413
|
}
|
|
1241
1414
|
let transformedTools = openapiTools;
|
|
1242
1415
|
if (this.options.descriptionMode && this.options.descriptionMode !== "summaryOnly") {
|
|
@@ -1890,7 +2063,7 @@ ${this.formatSchemaAsSummary(schema)}`;
|
|
|
1890
2063
|
}
|
|
1891
2064
|
};
|
|
1892
2065
|
OpenapiAdapter = __decorateClass([
|
|
1893
|
-
(0,
|
|
2066
|
+
(0, import_sdk5.Adapter)({
|
|
1894
2067
|
name: "openapi",
|
|
1895
2068
|
description: "OpenAPI adapter for FrontMCP - Automatically generates MCP tools from OpenAPI specifications"
|
|
1896
2069
|
})
|