@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/esm/index.mjs
CHANGED
|
@@ -183,7 +183,11 @@ var OpenApiSpecPoller = class {
|
|
|
183
183
|
};
|
|
184
184
|
|
|
185
185
|
// libs/adapters/src/openapi/openapi.security.ts
|
|
186
|
-
import {
|
|
186
|
+
import {
|
|
187
|
+
createSecurityContext,
|
|
188
|
+
SecurityResolver
|
|
189
|
+
} from "mcp-from-openapi";
|
|
190
|
+
import { PublicMcpError } from "@frontmcp/sdk";
|
|
187
191
|
async function createSecurityContextFromAuth(tool2, ctx, options) {
|
|
188
192
|
if (options.securityResolver) {
|
|
189
193
|
return await options.securityResolver(tool2, ctx);
|
|
@@ -238,7 +242,7 @@ async function createSecurityContextFromAuth(tool2, ctx, options) {
|
|
|
238
242
|
throw err;
|
|
239
243
|
}
|
|
240
244
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
241
|
-
throw new Error(`authProviderMapper['${scheme}'] threw an error: ${errorMessage}
|
|
245
|
+
throw new Error(`authProviderMapper['${scheme}'] threw an error: ${errorMessage}`, { cause: err });
|
|
242
246
|
}
|
|
243
247
|
}
|
|
244
248
|
}
|
|
@@ -286,19 +290,158 @@ function extractSecuritySchemes(tools) {
|
|
|
286
290
|
}
|
|
287
291
|
return schemes;
|
|
288
292
|
}
|
|
293
|
+
function describeSecuritySchemes(tools) {
|
|
294
|
+
const schemes = /* @__PURE__ */ new Map();
|
|
295
|
+
for (const tool2 of tools) {
|
|
296
|
+
for (const mapper of tool2.mapper) {
|
|
297
|
+
if (mapper.security?.scheme && !schemes.has(mapper.security.scheme)) {
|
|
298
|
+
schemes.set(mapper.security.scheme, mapper.security);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return schemes;
|
|
303
|
+
}
|
|
304
|
+
function schemeParametersOf(tools) {
|
|
305
|
+
const parameters = /* @__PURE__ */ new Map();
|
|
306
|
+
for (const tool2 of tools) {
|
|
307
|
+
for (const mapper of tool2.mapper) {
|
|
308
|
+
const scheme = mapper.security?.scheme;
|
|
309
|
+
if (scheme && !parameters.has(scheme)) parameters.set(scheme, mapper);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return parameters;
|
|
313
|
+
}
|
|
314
|
+
function headerCredentialSource(parameter, options) {
|
|
315
|
+
if (!parameter) return void 0;
|
|
316
|
+
const staticHeaders = new Headers(options.additionalHeaders ?? {});
|
|
317
|
+
if (carriesSchemeParameter(parameter, new URL("http://startup.invalid/"), staticHeaders)) return "static";
|
|
318
|
+
if (options.headersMapper && (parameter.type === "header" || parameter.type === "cookie")) return "per-request";
|
|
319
|
+
return void 0;
|
|
320
|
+
}
|
|
321
|
+
function isBearerScheme(security) {
|
|
322
|
+
if (!security || security.type !== "http") return false;
|
|
323
|
+
return (security.httpScheme ?? "bearer").toLowerCase() === "bearer";
|
|
324
|
+
}
|
|
325
|
+
function credentialFieldOf(security) {
|
|
326
|
+
if (security?.type === "apiKey") return "apiKey";
|
|
327
|
+
if (security?.type === "oauth2" || security?.type === "openIdConnect") return "oauth2Token";
|
|
328
|
+
if (security?.type === "http" && security.httpScheme?.toLowerCase() === "basic") return "basic";
|
|
329
|
+
return "jwt";
|
|
330
|
+
}
|
|
331
|
+
function formatMissingSecurityMappingsError(adapterName, tools, missingMappings) {
|
|
332
|
+
const schemes = describeSecuritySchemes(tools);
|
|
333
|
+
const fieldOf = (scheme) => credentialFieldOf(schemes.get(scheme));
|
|
334
|
+
const firstField = fieldOf(missingMappings[0] ?? "");
|
|
335
|
+
const bearerSchemes = missingMappings.filter((scheme) => isBearerScheme(schemes.get(scheme)));
|
|
336
|
+
const lines = [
|
|
337
|
+
`[OpenAPI Adapter: ${adapterName}] Invalid security configuration.`,
|
|
338
|
+
`Missing auth provider mappings for security schemes: ${missingMappings.join(", ")}`,
|
|
339
|
+
"",
|
|
340
|
+
"Your OpenAPI spec requires these security schemes, but no credential option covers them.",
|
|
341
|
+
"",
|
|
342
|
+
"Add one of the following to your adapter configuration:",
|
|
343
|
+
"",
|
|
344
|
+
"1. authProviderMapper (recommended): a function per scheme that returns the credential issued for the API:",
|
|
345
|
+
" authProviderMapper: {",
|
|
346
|
+
...missingMappings.map((scheme) => ` '${scheme}': (ctx) => getApiCredential(ctx), // the ${fieldOf(scheme)}`),
|
|
347
|
+
" }",
|
|
348
|
+
"",
|
|
349
|
+
"2. securityResolver:",
|
|
350
|
+
` securityResolver: async (tool, ctx) => ({ ${firstField}: await getApiCredential(ctx) })`,
|
|
351
|
+
"",
|
|
352
|
+
"3. staticAuth (one credential for every caller):",
|
|
353
|
+
` staticAuth: { ${firstField}: process.env.API_CREDENTIAL }`
|
|
354
|
+
];
|
|
355
|
+
if (bearerSchemes.length > 0) {
|
|
356
|
+
lines.push(
|
|
357
|
+
"",
|
|
358
|
+
`4. passthroughCallerToken: true, for the HTTP bearer schemes (${bearerSchemes.join(", ")}) only, and only if the API`,
|
|
359
|
+
" accepts the MCP client's own token (same issuer and audience). It sends nothing for other schemes."
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
return lines.join("\n");
|
|
363
|
+
}
|
|
364
|
+
function authenticationRequiredError(tool2) {
|
|
365
|
+
const required = requiredSecurityOf(tool2);
|
|
366
|
+
const schemes = [...new Map(required.map((security) => [security.scheme, security])).values()];
|
|
367
|
+
const schemesStr = schemes.map((security) => `${security.scheme} (${security.type})`).join(", ") || "unknown";
|
|
368
|
+
const first = schemes[0];
|
|
369
|
+
const firstScheme = first?.scheme ?? "BearerAuth";
|
|
370
|
+
const field = credentialFieldOf(first);
|
|
371
|
+
const bearerSchemes = schemes.filter(isBearerScheme).map((security) => security.scheme);
|
|
372
|
+
const solutions = [
|
|
373
|
+
` 1. Add authProviderMapper: { '${firstScheme}': (ctx) => getApiCredential(ctx) }, returning the ${field} issued for the API`,
|
|
374
|
+
` 2. Add securityResolver: async (tool, ctx) => ({ ${field}: await getApiCredential(ctx) })`,
|
|
375
|
+
` 3. Add staticAuth: { ${field}: process.env.API_CREDENTIAL }`
|
|
376
|
+
];
|
|
377
|
+
if (bearerSchemes.length > 0) {
|
|
378
|
+
solutions.push(
|
|
379
|
+
` 4. Set passthroughCallerToken: true, only if the API accepts the MCP client's own token (it fills ${bearerSchemes.join(", ")} only)`
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
return new Error(
|
|
383
|
+
`Authentication required for tool '${tool2.name}': no credential for its security schemes.
|
|
384
|
+
Required security schemes: ${schemesStr}
|
|
385
|
+
Solutions:
|
|
386
|
+
` + solutions.join("\n")
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
function requiredSecurityOf(tool2) {
|
|
390
|
+
return tool2.mapper.flatMap((mapper) => mapper.security && mapper.required === true ? [mapper.security] : []);
|
|
391
|
+
}
|
|
392
|
+
function authorizationSchemeOf(security) {
|
|
393
|
+
if (security?.type === "http") return (security.httpScheme ?? "bearer").toLowerCase();
|
|
394
|
+
if (security?.type === "oauth2" || security?.type === "openIdConnect") return "bearer";
|
|
395
|
+
return void 0;
|
|
396
|
+
}
|
|
397
|
+
function carriesSchemeParameter(mapper, url, headers) {
|
|
398
|
+
if (mapper.type === "query") return !!url.searchParams.get(mapper.key);
|
|
399
|
+
if (mapper.type === "cookie") {
|
|
400
|
+
return (headers.get("cookie") ?? "").split(";").some((pair) => {
|
|
401
|
+
const [name, ...value2] = pair.trim().split("=");
|
|
402
|
+
return name === mapper.key && value2.join("=") !== "";
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
if (mapper.type !== "header") return false;
|
|
406
|
+
const value = headers.get(mapper.key)?.trim();
|
|
407
|
+
if (!value) return false;
|
|
408
|
+
const scheme = authorizationSchemeOf(mapper.security);
|
|
409
|
+
if (!scheme) return true;
|
|
410
|
+
const space = value.indexOf(" ");
|
|
411
|
+
return space > 0 && value.slice(0, space).toLowerCase() === scheme && value.slice(space + 1).trim() !== "";
|
|
412
|
+
}
|
|
413
|
+
function assertRequestHasCredential(tool2, url, headers) {
|
|
414
|
+
const securityMappers = tool2.mapper.filter((mapper) => mapper.security && mapper.required === true);
|
|
415
|
+
if (securityMappers.length === 0) return;
|
|
416
|
+
const parsedUrl = new URL(url);
|
|
417
|
+
if (!securityMappers.some((mapper) => carriesSchemeParameter(mapper, parsedUrl, headers))) {
|
|
418
|
+
throw authenticationRequiredError(tool2);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
289
421
|
function validateSecurityConfiguration(tools, options) {
|
|
422
|
+
const result = validateCredentialSources(tools, options);
|
|
423
|
+
const schemesInInput = options.securitySchemesInInput ?? [];
|
|
424
|
+
if (options.generateOptions?.includeSecurityInInput !== true && schemesInInput.length > 0) {
|
|
425
|
+
result.securityRiskScore = "high";
|
|
426
|
+
result.warnings.push(
|
|
427
|
+
`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.`
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
return result;
|
|
431
|
+
}
|
|
432
|
+
function validateCredentialSources(tools, options) {
|
|
290
433
|
const result = {
|
|
291
434
|
valid: true,
|
|
292
435
|
missingMappings: [],
|
|
293
436
|
warnings: [],
|
|
294
437
|
securityRiskScore: "low"
|
|
295
438
|
};
|
|
296
|
-
const securitySchemes =
|
|
439
|
+
const securitySchemes = describeSecuritySchemes(tools);
|
|
297
440
|
const includeSecurityInInput = options.generateOptions?.includeSecurityInInput ?? false;
|
|
298
441
|
if (includeSecurityInInput) {
|
|
299
442
|
result.securityRiskScore = "high";
|
|
300
443
|
result.warnings.push(
|
|
301
|
-
"SECURITY WARNING: includeSecurityInInput is enabled. Users will provide authentication directly in tool inputs. This increases security risk as credentials may be logged or exposed."
|
|
444
|
+
"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."
|
|
302
445
|
);
|
|
303
446
|
return result;
|
|
304
447
|
}
|
|
@@ -318,19 +461,21 @@ function validateSecurityConfiguration(tools, options) {
|
|
|
318
461
|
}
|
|
319
462
|
const schemesInInput = new Set(options.securitySchemesInInput || []);
|
|
320
463
|
if (options.authProviderMapper || schemesInInput.size > 0) {
|
|
321
|
-
result.securityRiskScore =
|
|
322
|
-
if (schemesInInput.size > 0) {
|
|
323
|
-
result.warnings.push(
|
|
324
|
-
`INFO: Per-scheme security control enabled. Schemes in input: ${Array.from(schemesInInput).join(", ")}`
|
|
325
|
-
);
|
|
326
|
-
}
|
|
464
|
+
result.securityRiskScore = "low";
|
|
327
465
|
const passthroughFallback = [];
|
|
328
|
-
|
|
466
|
+
const perRequestHeaders = [];
|
|
467
|
+
const schemeParameters = schemeParametersOf(tools);
|
|
468
|
+
for (const [scheme, security] of securitySchemes) {
|
|
329
469
|
if (schemesInInput.has(scheme)) {
|
|
330
470
|
continue;
|
|
331
471
|
}
|
|
332
472
|
if (!options.authProviderMapper?.[scheme]) {
|
|
333
|
-
|
|
473
|
+
const headerSource = headerCredentialSource(schemeParameters.get(scheme), options);
|
|
474
|
+
if (headerSource === "static") {
|
|
475
|
+
continue;
|
|
476
|
+
} else if (headerSource === "per-request") {
|
|
477
|
+
perRequestHeaders.push(scheme);
|
|
478
|
+
} else if (options.passthroughCallerToken === true && isBearerScheme(security)) {
|
|
334
479
|
passthroughFallback.push(scheme);
|
|
335
480
|
} else {
|
|
336
481
|
result.valid = false;
|
|
@@ -343,6 +488,11 @@ function validateSecurityConfiguration(tools, options) {
|
|
|
343
488
|
`ERROR: Missing auth provider mappings for security schemes: ${result.missingMappings.join(", ")}`
|
|
344
489
|
);
|
|
345
490
|
}
|
|
491
|
+
if (perRequestHeaders.length > 0) {
|
|
492
|
+
result.warnings.push(
|
|
493
|
+
`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.`
|
|
494
|
+
);
|
|
495
|
+
}
|
|
346
496
|
withPassthroughRisk(result, securitySchemes, options);
|
|
347
497
|
if (passthroughFallback.length > 0) {
|
|
348
498
|
result.warnings.push(
|
|
@@ -352,10 +502,17 @@ function validateSecurityConfiguration(tools, options) {
|
|
|
352
502
|
return result;
|
|
353
503
|
}
|
|
354
504
|
if (securitySchemes.size > 0 && options.passthroughCallerToken !== true) {
|
|
355
|
-
const schemesStr = Array.from(securitySchemes).join(", ");
|
|
505
|
+
const schemesStr = Array.from(securitySchemes.keys()).join(", ");
|
|
356
506
|
result.securityRiskScore = "medium";
|
|
357
507
|
result.warnings.push(
|
|
358
|
-
`SECURITY WARNING: No auth configuration provided, so the adapter has no credentials for the API (security schemes: ${schemesStr}). Operations that require authentication
|
|
508
|
+
`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.`
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
const unfilled = [...securitySchemes].filter(([, security]) => !isBearerScheme(security)).map(([scheme]) => scheme);
|
|
512
|
+
if (options.passthroughCallerToken === true && unfilled.length > 0) {
|
|
513
|
+
result.securityRiskScore = "medium";
|
|
514
|
+
result.warnings.push(
|
|
515
|
+
`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.`
|
|
359
516
|
);
|
|
360
517
|
}
|
|
361
518
|
return withPassthroughRisk(result, securitySchemes, options);
|
|
@@ -364,10 +521,11 @@ function canPassThroughCallerToken(options) {
|
|
|
364
521
|
return options.passthroughCallerToken === true && !options.securityResolver && !hasStaticAuth(options);
|
|
365
522
|
}
|
|
366
523
|
function withPassthroughRisk(result, securitySchemes, options) {
|
|
367
|
-
|
|
524
|
+
const bearerSchemes = [...securitySchemes].filter(([, security]) => isBearerScheme(security));
|
|
525
|
+
if (bearerSchemes.length === 0 || !canPassThroughCallerToken(options)) {
|
|
368
526
|
return result;
|
|
369
527
|
}
|
|
370
|
-
const schemesStr =
|
|
528
|
+
const schemesStr = bearerSchemes.map(([scheme]) => scheme).join(", ");
|
|
371
529
|
const when = options.authProviderMapper ? " whenever no authProviderMapper function returns a credential" : "";
|
|
372
530
|
result.securityRiskScore = "high";
|
|
373
531
|
result.warnings.push(
|
|
@@ -375,28 +533,63 @@ function withPassthroughRisk(result, securitySchemes, options) {
|
|
|
375
533
|
);
|
|
376
534
|
return result;
|
|
377
535
|
}
|
|
378
|
-
async function resolveToolSecurity(tool2, ctx, options) {
|
|
536
|
+
async function resolveToolSecurity(tool2, ctx, options, request = {}) {
|
|
379
537
|
const securityResolver = new SecurityResolver();
|
|
380
538
|
const securityContext = await createSecurityContextFromAuth(tool2, ctx, options);
|
|
381
539
|
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;
|
|
382
540
|
const requiresSecurity = tool2.mapper.some((m) => m.security && m.required === true);
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
const firstScheme = uniqueSchemes[0] || "BearerAuth";
|
|
388
|
-
throw new Error(
|
|
389
|
-
`Authentication required for tool '${tool2.name}' but no auth configuration found.
|
|
390
|
-
Required security schemes: ${schemesStr}
|
|
391
|
-
Solutions:
|
|
392
|
-
1. Add authProviderMapper: { '${firstScheme}': (ctx) => ctx.authInfo.user?.token }
|
|
393
|
-
2. Add securityResolver: async (tool, ctx) => ({ jwt: await getApiToken(ctx) })
|
|
394
|
-
3. Add staticAuth: { jwt: process.env.API_TOKEN }
|
|
395
|
-
4. Set passthroughCallerToken: true, only if the API accepts the MCP client's own token
|
|
396
|
-
5. Set generateOptions.includeSecurityInInput: true (not recommended for production)`
|
|
397
|
-
);
|
|
541
|
+
const fromInput = request.input ? await resolveInputSecurity(tool2, request.input, options, securityResolver) : void 0;
|
|
542
|
+
const hasInputCredential = !!fromInput && Object.keys(fromInput.headers).length + Object.keys(fromInput.query).length + Object.keys(fromInput.cookies).length > 0;
|
|
543
|
+
if (requiresSecurity && !hasAuth && !hasInputCredential && !request.deferCredentialCheck) {
|
|
544
|
+
throw authenticationRequiredError(tool2);
|
|
398
545
|
}
|
|
399
|
-
|
|
546
|
+
const resolved = await securityResolver.resolve(tool2.mapper, securityContext);
|
|
547
|
+
if (!fromInput) return resolved;
|
|
548
|
+
return {
|
|
549
|
+
...resolved,
|
|
550
|
+
headers: { ...fromInput.headers, ...resolved.headers },
|
|
551
|
+
query: { ...fromInput.query, ...resolved.query },
|
|
552
|
+
cookies: { ...fromInput.cookies, ...resolved.cookies }
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
function isInputScheme(scheme, options) {
|
|
556
|
+
return options.generateOptions?.includeSecurityInInput === true || (options.securitySchemesInInput ?? []).includes(scheme);
|
|
557
|
+
}
|
|
558
|
+
function inputCredentialContext(security, value) {
|
|
559
|
+
const bare = value.replace(/^(?:bearer|basic)\s+/i, "");
|
|
560
|
+
if (security.type === "apiKey") {
|
|
561
|
+
return security.apiKeyName ? { apiKeys: { [security.apiKeyName]: value } } : { apiKey: value };
|
|
562
|
+
}
|
|
563
|
+
if (security.type === "oauth2" || security.type === "openIdConnect") return { oauth2Token: bare };
|
|
564
|
+
if (security.type === "http") {
|
|
565
|
+
const httpScheme = (security.httpScheme ?? "bearer").toLowerCase();
|
|
566
|
+
if (httpScheme === "bearer") return { jwt: bare };
|
|
567
|
+
if (httpScheme === "basic") return { basic: bare };
|
|
568
|
+
}
|
|
569
|
+
return void 0;
|
|
570
|
+
}
|
|
571
|
+
async function resolveInputSecurity(tool2, input, options, securityResolver) {
|
|
572
|
+
const resolved = { headers: {}, query: {}, cookies: {} };
|
|
573
|
+
for (const mapper of tool2.mapper) {
|
|
574
|
+
const security = mapper.security;
|
|
575
|
+
if (!security || !isInputScheme(security.scheme, options)) continue;
|
|
576
|
+
const value = input[mapper.inputKey];
|
|
577
|
+
if (typeof value !== "string" || value === "") continue;
|
|
578
|
+
if (/[\r\n\x00\f\v]/.test(value)) {
|
|
579
|
+
throw new PublicMcpError(
|
|
580
|
+
`Invalid value for '${mapper.inputKey}': contains control characters (possible header injection attack)`,
|
|
581
|
+
"INVALID_HEADER_VALUE",
|
|
582
|
+
400
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
const context = inputCredentialContext(security, value);
|
|
586
|
+
if (!context) continue;
|
|
587
|
+
const one = await securityResolver.resolve([mapper], createSecurityContext(context));
|
|
588
|
+
Object.assign(resolved.headers, one.headers);
|
|
589
|
+
Object.assign(resolved.query, one.query);
|
|
590
|
+
Object.assign(resolved.cookies, one.cookies);
|
|
591
|
+
}
|
|
592
|
+
return resolved;
|
|
400
593
|
}
|
|
401
594
|
|
|
402
595
|
// libs/adapters/src/openapi/openapi.tool.ts
|
|
@@ -645,7 +838,7 @@ function formatZodIssues(issues, prefix) {
|
|
|
645
838
|
}
|
|
646
839
|
|
|
647
840
|
// libs/adapters/src/openapi/openapi.utils.ts
|
|
648
|
-
import { PublicMcpError } from "@frontmcp/sdk";
|
|
841
|
+
import { PublicMcpError as PublicMcpError2 } from "@frontmcp/sdk";
|
|
649
842
|
import { trimTrailing, validateBaseUrl } from "@frontmcp/utils";
|
|
650
843
|
function coerceToString(value, paramName, location) {
|
|
651
844
|
if (value === null || value === void 0) {
|
|
@@ -703,7 +896,7 @@ function buildRequest(tool2, input, security, baseUrl) {
|
|
|
703
896
|
case "header": {
|
|
704
897
|
const headerValue = coerceToString(value, mapper.key, "header");
|
|
705
898
|
if (/[\r\n\x00\f\v]/.test(headerValue)) {
|
|
706
|
-
throw new
|
|
899
|
+
throw new PublicMcpError2(
|
|
707
900
|
`Invalid header value for '${mapper.key}': contains control characters (possible header injection attack)`,
|
|
708
901
|
"INVALID_HEADER_VALUE",
|
|
709
902
|
400
|
|
@@ -880,7 +1073,10 @@ function createOpenApiTool(openapiTool, options, logger) {
|
|
|
880
1073
|
inputTransforms,
|
|
881
1074
|
transformContext
|
|
882
1075
|
);
|
|
883
|
-
const security = await resolveToolSecurity(openapiTool, ctx, options
|
|
1076
|
+
const security = await resolveToolSecurity(openapiTool, ctx, options, {
|
|
1077
|
+
input: injectedInput,
|
|
1078
|
+
deferCredentialCheck: true
|
|
1079
|
+
});
|
|
884
1080
|
const { url, headers, body: requestBody } = buildRequest(openapiTool, injectedInput, security, options.baseUrl);
|
|
885
1081
|
applyAdditionalHeaders(headers, options.additionalHeaders);
|
|
886
1082
|
if (options.headersMapper) {
|
|
@@ -896,6 +1092,7 @@ function createOpenApiTool(openapiTool, options, logger) {
|
|
|
896
1092
|
throw new Error(`headersMapper failed for tool '${openapiTool.name}': ${errorMessage}`, { cause: err });
|
|
897
1093
|
}
|
|
898
1094
|
}
|
|
1095
|
+
assertRequestHasCredential(openapiTool, url, headers);
|
|
899
1096
|
let finalBody = requestBody;
|
|
900
1097
|
if (options.bodyMapper && requestBody) {
|
|
901
1098
|
try {
|
|
@@ -1180,28 +1377,7 @@ var OpenapiAdapter = class extends DynamicAdapter {
|
|
|
1180
1377
|
});
|
|
1181
1378
|
}
|
|
1182
1379
|
if (!validation.valid) {
|
|
1183
|
-
throw new Error(
|
|
1184
|
-
`[OpenAPI Adapter: ${this.options.name}] Invalid security configuration.
|
|
1185
|
-
Missing auth provider mappings for security schemes: ${validation.missingMappings.join(", ")}
|
|
1186
|
-
|
|
1187
|
-
Your OpenAPI spec requires these security schemes, but no auth configuration was provided.
|
|
1188
|
-
|
|
1189
|
-
Add one of the following to your adapter configuration:
|
|
1190
|
-
|
|
1191
|
-
1. authProviderMapper (recommended):
|
|
1192
|
-
authProviderMapper: {
|
|
1193
|
-
` + validation.missingMappings.map((s) => ` '${s}': (authInfo) => authInfo.user?.${s.toLowerCase()}Token,`).join("\n") + `
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
2. securityResolver:
|
|
1197
|
-
securityResolver: async (tool, ctx) => ({ jwt: await getApiToken(ctx) })
|
|
1198
|
-
|
|
1199
|
-
3. staticAuth:
|
|
1200
|
-
staticAuth: { jwt: process.env.API_TOKEN }
|
|
1201
|
-
|
|
1202
|
-
4. Include security in input (NOT recommended for production):
|
|
1203
|
-
generateOptions: { includeSecurityInInput: true }`
|
|
1204
|
-
);
|
|
1380
|
+
throw new Error(formatMissingSecurityMappingsError(this.options.name, openapiTools, validation.missingMappings));
|
|
1205
1381
|
}
|
|
1206
1382
|
let transformedTools = openapiTools;
|
|
1207
1383
|
if (this.options.descriptionMode && this.options.descriptionMode !== "summaryOnly") {
|