@frontmcp/adapters 0.7.2 → 0.8.1
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 +456 -10
- package/esm/openapi/index.mjs +456 -10
- package/esm/package.json +6 -4
- package/index.js +459 -11
- package/openapi/index.d.ts +2 -0
- package/openapi/index.d.ts.map +1 -1
- package/openapi/index.js +459 -11
- package/openapi/openapi.adapter.d.ts +47 -0
- package/openapi/openapi.adapter.d.ts.map +1 -1
- package/openapi/openapi.spec-utils.d.ts +86 -0
- package/openapi/openapi.spec-utils.d.ts.map +1 -0
- package/openapi/openapi.tool.d.ts.map +1 -1
- package/openapi/openapi.types.d.ts +298 -0
- package/openapi/openapi.types.d.ts.map +1 -1
- package/openapi/openapi.utils.d.ts +18 -5
- package/openapi/openapi.utils.d.ts.map +1 -1
- package/package.json +6 -4
package/esm/index.mjs
CHANGED
|
@@ -125,9 +125,6 @@ function applyAdditionalHeaders(headers, additionalHeaders) {
|
|
|
125
125
|
var DEFAULT_MAX_RESPONSE_SIZE = 10 * 1024 * 1024;
|
|
126
126
|
async function parseResponse(response, options) {
|
|
127
127
|
const maxSize = options?.maxResponseSize ?? DEFAULT_MAX_RESPONSE_SIZE;
|
|
128
|
-
if (!response.ok) {
|
|
129
|
-
throw new Error(`API request failed: ${response.status}`);
|
|
130
|
-
}
|
|
131
128
|
const contentLength = response.headers.get("content-length");
|
|
132
129
|
if (contentLength) {
|
|
133
130
|
const length = parseInt(contentLength, 10);
|
|
@@ -141,14 +138,29 @@ async function parseResponse(response, options) {
|
|
|
141
138
|
throw new Error(`Response size (${byteSize} bytes) exceeds maximum allowed (${maxSize} bytes)`);
|
|
142
139
|
}
|
|
143
140
|
const contentType = response.headers.get("content-type");
|
|
141
|
+
let data;
|
|
144
142
|
if (contentType?.toLowerCase().includes("application/json")) {
|
|
145
143
|
try {
|
|
146
|
-
|
|
144
|
+
data = JSON.parse(text);
|
|
147
145
|
} catch {
|
|
148
|
-
|
|
146
|
+
data = text;
|
|
149
147
|
}
|
|
148
|
+
} else {
|
|
149
|
+
data = text;
|
|
150
|
+
}
|
|
151
|
+
if (!response.ok) {
|
|
152
|
+
return {
|
|
153
|
+
status: response.status,
|
|
154
|
+
ok: false,
|
|
155
|
+
data,
|
|
156
|
+
error: typeof data === "object" && data !== null && "message" in data ? String(data.message) : typeof data === "string" ? data : `HTTP ${response.status} error`
|
|
157
|
+
};
|
|
150
158
|
}
|
|
151
|
-
return {
|
|
159
|
+
return {
|
|
160
|
+
status: response.status,
|
|
161
|
+
ok: true,
|
|
162
|
+
data
|
|
163
|
+
};
|
|
152
164
|
}
|
|
153
165
|
|
|
154
166
|
// libs/adapters/src/openapi/openapi.security.ts
|
|
@@ -574,15 +586,32 @@ function createOpenApiTool(openapiTool, options, logger) {
|
|
|
574
586
|
const metadata = openapiTool.metadata;
|
|
575
587
|
const inputTransforms = metadata.adapter?.inputTransforms ?? [];
|
|
576
588
|
const toolTransform = metadata.adapter?.toolTransform ?? {};
|
|
589
|
+
const postToolTransform = metadata.adapter?.postToolTransform;
|
|
577
590
|
const frontmcpValidation = validateFrontMcpExtension(metadata.frontmcp, openapiTool.name, logger);
|
|
578
591
|
const frontmcpExt = frontmcpValidation.data;
|
|
579
592
|
const schemaResult = getZodSchemaFromJsonSchema(openapiTool.inputSchema, openapiTool.name, logger);
|
|
593
|
+
let wrappedOutputSchema;
|
|
594
|
+
if (openapiTool.outputSchema !== void 0) {
|
|
595
|
+
const baseOutputSchema = openapiTool.outputSchema ?? { type: "string" };
|
|
596
|
+
wrappedOutputSchema = {
|
|
597
|
+
type: "object",
|
|
598
|
+
properties: {
|
|
599
|
+
status: { type: "number", description: "HTTP status code" },
|
|
600
|
+
ok: { type: "boolean", description: "Whether the response was successful" },
|
|
601
|
+
data: baseOutputSchema,
|
|
602
|
+
error: { type: "string", description: "Error message for non-ok responses" }
|
|
603
|
+
},
|
|
604
|
+
required: ["status", "ok"]
|
|
605
|
+
};
|
|
606
|
+
}
|
|
580
607
|
const toolMetadata = {
|
|
581
608
|
id: openapiTool.name,
|
|
582
609
|
name: openapiTool.name,
|
|
583
610
|
description: openapiTool.description,
|
|
584
611
|
inputSchema: schemaResult.schema.shape || {},
|
|
585
|
-
rawInputSchema: openapiTool.inputSchema
|
|
612
|
+
rawInputSchema: openapiTool.inputSchema,
|
|
613
|
+
// Add output schema for tool/list to expose (only if not moved to description)
|
|
614
|
+
...wrappedOutputSchema && { rawOutputSchema: wrappedOutputSchema }
|
|
586
615
|
};
|
|
587
616
|
if (schemaResult.conversionFailed) {
|
|
588
617
|
toolMetadata["_schemaConversionFailed"] = true;
|
|
@@ -695,11 +724,71 @@ function createOpenApiTool(openapiTool, options, logger) {
|
|
|
695
724
|
body: serializedBody,
|
|
696
725
|
signal: controller.signal
|
|
697
726
|
});
|
|
698
|
-
|
|
727
|
+
const apiResponse = await parseResponse(response, { maxResponseSize: options.maxResponseSize });
|
|
728
|
+
let transformedData = apiResponse.data;
|
|
729
|
+
if (postToolTransform) {
|
|
730
|
+
const transformCtx = {
|
|
731
|
+
ctx,
|
|
732
|
+
tool: openapiTool,
|
|
733
|
+
status: apiResponse.status,
|
|
734
|
+
ok: apiResponse.ok,
|
|
735
|
+
adapterOptions: options
|
|
736
|
+
};
|
|
737
|
+
const shouldTransform = postToolTransform.filter ? postToolTransform.filter(transformCtx) : true;
|
|
738
|
+
if (shouldTransform) {
|
|
739
|
+
try {
|
|
740
|
+
transformedData = await postToolTransform.transform(apiResponse.data, transformCtx);
|
|
741
|
+
} catch (err) {
|
|
742
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
743
|
+
const errorStack = err instanceof Error ? err.stack : void 0;
|
|
744
|
+
logger.error(`[${openapiTool.name}] Post-tool output transform failed`, {
|
|
745
|
+
error: errorMessage,
|
|
746
|
+
stack: errorStack,
|
|
747
|
+
status: apiResponse.status,
|
|
748
|
+
ok: apiResponse.ok
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
if (!apiResponse.ok) {
|
|
754
|
+
return {
|
|
755
|
+
content: [
|
|
756
|
+
{
|
|
757
|
+
type: "text",
|
|
758
|
+
text: JSON.stringify({
|
|
759
|
+
status: apiResponse.status,
|
|
760
|
+
error: apiResponse.error,
|
|
761
|
+
data: transformedData
|
|
762
|
+
})
|
|
763
|
+
}
|
|
764
|
+
],
|
|
765
|
+
isError: true,
|
|
766
|
+
_meta: {
|
|
767
|
+
status: apiResponse.status,
|
|
768
|
+
errorCode: "OPENAPI_ERROR"
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
return {
|
|
773
|
+
status: apiResponse.status,
|
|
774
|
+
ok: true,
|
|
775
|
+
data: transformedData
|
|
776
|
+
};
|
|
699
777
|
} catch (err) {
|
|
700
778
|
if (err instanceof Error && err.name === "AbortError") {
|
|
701
|
-
|
|
779
|
+
const timeoutError = new Error(`Request timeout after ${requestTimeout}ms for tool '${openapiTool.name}'`);
|
|
780
|
+
logger.error(`[${openapiTool.name}] API request timeout`, {
|
|
781
|
+
timeout: requestTimeout,
|
|
782
|
+
url,
|
|
783
|
+
method: openapiTool.metadata.method.toUpperCase()
|
|
784
|
+
});
|
|
785
|
+
throw timeoutError;
|
|
702
786
|
}
|
|
787
|
+
logger.error(`[${openapiTool.name}] API request failed`, {
|
|
788
|
+
url,
|
|
789
|
+
method: openapiTool.metadata.method.toUpperCase(),
|
|
790
|
+
error: err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : err
|
|
791
|
+
});
|
|
703
792
|
throw err;
|
|
704
793
|
} finally {
|
|
705
794
|
clearTimeout(timeoutId);
|
|
@@ -870,6 +959,15 @@ Add one of the following to your adapter configuration:
|
|
|
870
959
|
if (this.options.inputTransforms) {
|
|
871
960
|
transformedTools = transformedTools.map((tool2) => this.applyInputTransforms(tool2));
|
|
872
961
|
}
|
|
962
|
+
if (this.options.schemaTransforms) {
|
|
963
|
+
transformedTools = transformedTools.map((tool2) => this.applySchemaTransforms(tool2));
|
|
964
|
+
}
|
|
965
|
+
const dataTransforms = this.options.dataTransforms || this.options.outputTransforms;
|
|
966
|
+
if (this.options.outputSchema || dataTransforms) {
|
|
967
|
+
transformedTools = await Promise.all(
|
|
968
|
+
transformedTools.map((tool2) => this.applyOutputSchemaOptions(tool2, dataTransforms))
|
|
969
|
+
);
|
|
970
|
+
}
|
|
873
971
|
const tools = transformedTools.map((openapiTool) => createOpenApiTool(openapiTool, this.options, this.logger));
|
|
874
972
|
return { tools };
|
|
875
973
|
}
|
|
@@ -1139,6 +1237,251 @@ ${opDescription}`;
|
|
|
1139
1237
|
}
|
|
1140
1238
|
};
|
|
1141
1239
|
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Apply schema transforms to an OpenAPI tool.
|
|
1242
|
+
* Modifies input and output schema definitions.
|
|
1243
|
+
* @private
|
|
1244
|
+
*/
|
|
1245
|
+
applySchemaTransforms(tool2) {
|
|
1246
|
+
const opts = this.options.schemaTransforms;
|
|
1247
|
+
if (!opts) return tool2;
|
|
1248
|
+
const ctx = {
|
|
1249
|
+
tool: tool2,
|
|
1250
|
+
adapterOptions: this.options
|
|
1251
|
+
};
|
|
1252
|
+
let newInputSchema = tool2.inputSchema;
|
|
1253
|
+
let newOutputSchema = tool2.outputSchema;
|
|
1254
|
+
if (opts.input) {
|
|
1255
|
+
const inputTransform = this.collectInputSchemaTransform(tool2);
|
|
1256
|
+
if (inputTransform) {
|
|
1257
|
+
newInputSchema = inputTransform(newInputSchema, ctx);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
if (opts.output) {
|
|
1261
|
+
const outputTransform = this.collectOutputSchemaTransform(tool2);
|
|
1262
|
+
if (outputTransform) {
|
|
1263
|
+
newOutputSchema = outputTransform(newOutputSchema, ctx);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
if (newInputSchema === tool2.inputSchema && newOutputSchema === tool2.outputSchema) {
|
|
1267
|
+
return tool2;
|
|
1268
|
+
}
|
|
1269
|
+
this.logger.debug(`Applied schema transforms to '${tool2.name}'`);
|
|
1270
|
+
return {
|
|
1271
|
+
...tool2,
|
|
1272
|
+
inputSchema: newInputSchema,
|
|
1273
|
+
outputSchema: newOutputSchema
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Collect input schema transform for a specific tool.
|
|
1278
|
+
* @private
|
|
1279
|
+
*/
|
|
1280
|
+
collectInputSchemaTransform(tool2) {
|
|
1281
|
+
const opts = this.options.schemaTransforms?.input;
|
|
1282
|
+
if (!opts) return void 0;
|
|
1283
|
+
if (opts.generator) {
|
|
1284
|
+
const generated = opts.generator(tool2);
|
|
1285
|
+
if (generated) return generated;
|
|
1286
|
+
}
|
|
1287
|
+
if (opts.perTool?.[tool2.name]) {
|
|
1288
|
+
return opts.perTool[tool2.name];
|
|
1289
|
+
}
|
|
1290
|
+
return opts.global;
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Collect output schema transform for a specific tool.
|
|
1294
|
+
* @private
|
|
1295
|
+
*/
|
|
1296
|
+
collectOutputSchemaTransform(tool2) {
|
|
1297
|
+
const opts = this.options.schemaTransforms?.output;
|
|
1298
|
+
if (!opts) return void 0;
|
|
1299
|
+
if (opts.generator) {
|
|
1300
|
+
const generated = opts.generator(tool2);
|
|
1301
|
+
if (generated) return generated;
|
|
1302
|
+
}
|
|
1303
|
+
if (opts.perTool?.[tool2.name]) {
|
|
1304
|
+
return opts.perTool[tool2.name];
|
|
1305
|
+
}
|
|
1306
|
+
return opts.global;
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* Apply output schema options to an OpenAPI tool.
|
|
1310
|
+
* Handles output schema mode and async description formatting.
|
|
1311
|
+
* @private
|
|
1312
|
+
*/
|
|
1313
|
+
async applyOutputSchemaOptions(tool2, dataTransforms) {
|
|
1314
|
+
const outputSchemaOpts = this.options.outputSchema;
|
|
1315
|
+
let newDescription = tool2.description;
|
|
1316
|
+
let newOutputSchema = tool2.outputSchema;
|
|
1317
|
+
const mode = outputSchemaOpts?.mode ?? "definition";
|
|
1318
|
+
if (newOutputSchema && (mode === "description" || mode === "both")) {
|
|
1319
|
+
const descriptionFormat = outputSchemaOpts?.descriptionFormat ?? "summary";
|
|
1320
|
+
const formatter = outputSchemaOpts?.descriptionFormatter;
|
|
1321
|
+
const formatterCtx = {
|
|
1322
|
+
tool: tool2,
|
|
1323
|
+
adapterOptions: this.options,
|
|
1324
|
+
originalDescription: tool2.description
|
|
1325
|
+
};
|
|
1326
|
+
let schemaText;
|
|
1327
|
+
if (formatter) {
|
|
1328
|
+
schemaText = await Promise.resolve(formatter(newOutputSchema, formatterCtx));
|
|
1329
|
+
} else {
|
|
1330
|
+
schemaText = this.formatSchemaForDescription(newOutputSchema, descriptionFormat);
|
|
1331
|
+
}
|
|
1332
|
+
newDescription = tool2.description + schemaText;
|
|
1333
|
+
}
|
|
1334
|
+
if (mode === "description") {
|
|
1335
|
+
newOutputSchema = void 0;
|
|
1336
|
+
}
|
|
1337
|
+
const preTransform = this.collectPreToolTransformsFromDataTransforms(tool2, dataTransforms);
|
|
1338
|
+
if (preTransform) {
|
|
1339
|
+
const ctx = {
|
|
1340
|
+
tool: tool2,
|
|
1341
|
+
adapterOptions: this.options
|
|
1342
|
+
};
|
|
1343
|
+
if (preTransform.transformSchema) {
|
|
1344
|
+
newOutputSchema = preTransform.transformSchema(newOutputSchema, ctx);
|
|
1345
|
+
}
|
|
1346
|
+
if (preTransform.transformDescription) {
|
|
1347
|
+
newDescription = preTransform.transformDescription(newDescription, newOutputSchema, ctx);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
const postTransform = this.collectPostToolTransformsFromDataTransforms(tool2, dataTransforms);
|
|
1351
|
+
if (newDescription === tool2.description && newOutputSchema === tool2.outputSchema && !postTransform) {
|
|
1352
|
+
return tool2;
|
|
1353
|
+
}
|
|
1354
|
+
this.logger.debug(`Applied output schema options to '${tool2.name}' (mode: ${mode})`);
|
|
1355
|
+
const metadataRecord = tool2.metadata;
|
|
1356
|
+
const existingAdapter = metadataRecord["adapter"];
|
|
1357
|
+
return {
|
|
1358
|
+
...tool2,
|
|
1359
|
+
description: newDescription,
|
|
1360
|
+
outputSchema: newOutputSchema,
|
|
1361
|
+
metadata: {
|
|
1362
|
+
...tool2.metadata,
|
|
1363
|
+
adapter: {
|
|
1364
|
+
...existingAdapter || {},
|
|
1365
|
+
...postTransform && { postToolTransform: postTransform }
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Format schema for description based on mode.
|
|
1372
|
+
* @private
|
|
1373
|
+
*/
|
|
1374
|
+
formatSchemaForDescription(schema, mode) {
|
|
1375
|
+
switch (mode) {
|
|
1376
|
+
case "jsonSchema":
|
|
1377
|
+
return `
|
|
1378
|
+
|
|
1379
|
+
## Output Schema
|
|
1380
|
+
\`\`\`json
|
|
1381
|
+
${JSON.stringify(schema, null, 2)}
|
|
1382
|
+
\`\`\``;
|
|
1383
|
+
case "summary":
|
|
1384
|
+
return `
|
|
1385
|
+
|
|
1386
|
+
## Returns
|
|
1387
|
+
${this.formatSchemaAsSummary(schema)}`;
|
|
1388
|
+
default:
|
|
1389
|
+
return `
|
|
1390
|
+
|
|
1391
|
+
## Returns
|
|
1392
|
+
${this.formatSchemaAsSummary(schema)}`;
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Collect pre-tool transforms from dataTransforms options.
|
|
1397
|
+
* @private
|
|
1398
|
+
*/
|
|
1399
|
+
collectPreToolTransformsFromDataTransforms(tool2, dataTransforms) {
|
|
1400
|
+
const opts = dataTransforms?.preToolTransforms;
|
|
1401
|
+
if (!opts) return void 0;
|
|
1402
|
+
let result;
|
|
1403
|
+
if (opts.global) {
|
|
1404
|
+
result = { ...opts.global };
|
|
1405
|
+
}
|
|
1406
|
+
if (opts.perTool?.[tool2.name]) {
|
|
1407
|
+
result = { ...result, ...opts.perTool[tool2.name] };
|
|
1408
|
+
}
|
|
1409
|
+
if (opts.generator) {
|
|
1410
|
+
const generated = opts.generator(tool2);
|
|
1411
|
+
if (generated) {
|
|
1412
|
+
result = { ...result, ...generated };
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
return result;
|
|
1416
|
+
}
|
|
1417
|
+
/**
|
|
1418
|
+
* Collect post-tool transforms from dataTransforms options.
|
|
1419
|
+
* @private
|
|
1420
|
+
*/
|
|
1421
|
+
collectPostToolTransformsFromDataTransforms(tool2, dataTransforms) {
|
|
1422
|
+
const opts = dataTransforms?.postToolTransforms;
|
|
1423
|
+
if (!opts) return void 0;
|
|
1424
|
+
let result;
|
|
1425
|
+
if (opts.global) {
|
|
1426
|
+
result = { ...opts.global };
|
|
1427
|
+
}
|
|
1428
|
+
if (opts.perTool?.[tool2.name]) {
|
|
1429
|
+
const perTool = opts.perTool[tool2.name];
|
|
1430
|
+
result = result ? {
|
|
1431
|
+
transform: perTool.transform,
|
|
1432
|
+
filter: perTool.filter ?? result.filter
|
|
1433
|
+
} : perTool;
|
|
1434
|
+
}
|
|
1435
|
+
if (opts.generator) {
|
|
1436
|
+
const generated = opts.generator(tool2);
|
|
1437
|
+
if (generated) {
|
|
1438
|
+
result = result ? {
|
|
1439
|
+
transform: generated.transform,
|
|
1440
|
+
filter: generated.filter ?? result.filter
|
|
1441
|
+
} : generated;
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
return result;
|
|
1445
|
+
}
|
|
1446
|
+
/**
|
|
1447
|
+
* Format JSON Schema as human-readable summary.
|
|
1448
|
+
* @private
|
|
1449
|
+
*/
|
|
1450
|
+
formatSchemaAsSummary(schema) {
|
|
1451
|
+
const lines = [];
|
|
1452
|
+
if (schema.type === "object" && schema.properties) {
|
|
1453
|
+
const required = new Set(schema.required || []);
|
|
1454
|
+
for (const [name, propSchema] of Object.entries(schema.properties)) {
|
|
1455
|
+
const isRequired = required.has(name);
|
|
1456
|
+
const typeStr = this.getSchemaTypeString(propSchema);
|
|
1457
|
+
const desc = propSchema.description ? ` - ${propSchema.description}` : "";
|
|
1458
|
+
const reqStr = isRequired ? " (required)" : " (optional)";
|
|
1459
|
+
lines.push(`- **${name}**: ${typeStr}${reqStr}${desc}`);
|
|
1460
|
+
}
|
|
1461
|
+
} else if (schema.type === "array" && schema.items) {
|
|
1462
|
+
const itemType = this.getSchemaTypeString(schema.items);
|
|
1463
|
+
lines.push(`Array of ${itemType}`);
|
|
1464
|
+
} else {
|
|
1465
|
+
lines.push(this.getSchemaTypeString(schema));
|
|
1466
|
+
}
|
|
1467
|
+
return lines.join("\n");
|
|
1468
|
+
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Get human-readable type string from JSON Schema.
|
|
1471
|
+
* @private
|
|
1472
|
+
*/
|
|
1473
|
+
getSchemaTypeString(schema) {
|
|
1474
|
+
if (schema.type === "array") {
|
|
1475
|
+
return schema.items ? `${this.getSchemaTypeString(schema.items)}[]` : "array";
|
|
1476
|
+
}
|
|
1477
|
+
if (schema.type === "object") {
|
|
1478
|
+
return schema.title || "object";
|
|
1479
|
+
}
|
|
1480
|
+
if (Array.isArray(schema.type)) {
|
|
1481
|
+
return schema.type.join(" | ");
|
|
1482
|
+
}
|
|
1483
|
+
return schema.type || "any";
|
|
1484
|
+
}
|
|
1142
1485
|
};
|
|
1143
1486
|
OpenapiAdapter = __decorateClass([
|
|
1144
1487
|
Adapter({
|
|
@@ -1149,7 +1492,110 @@ OpenapiAdapter = __decorateClass([
|
|
|
1149
1492
|
|
|
1150
1493
|
// libs/adapters/src/openapi/openapi.types.ts
|
|
1151
1494
|
var FRONTMCP_EXTENSION_KEY = "x-frontmcp";
|
|
1495
|
+
|
|
1496
|
+
// libs/adapters/src/openapi/openapi.spec-utils.ts
|
|
1497
|
+
function deepClone(obj) {
|
|
1498
|
+
return JSON.parse(JSON.stringify(obj));
|
|
1499
|
+
}
|
|
1500
|
+
function forceJwtSecurity(spec, options = {}) {
|
|
1501
|
+
const result = deepClone(spec);
|
|
1502
|
+
const {
|
|
1503
|
+
schemeName = "BearerAuth",
|
|
1504
|
+
schemeType = "bearer",
|
|
1505
|
+
apiKeyIn = "header",
|
|
1506
|
+
apiKeyName = "X-API-Key",
|
|
1507
|
+
operations,
|
|
1508
|
+
description
|
|
1509
|
+
} = options;
|
|
1510
|
+
if (!result.components) {
|
|
1511
|
+
result.components = {};
|
|
1512
|
+
}
|
|
1513
|
+
if (!result.components.securitySchemes) {
|
|
1514
|
+
result.components.securitySchemes = {};
|
|
1515
|
+
}
|
|
1516
|
+
let securityScheme;
|
|
1517
|
+
switch (schemeType) {
|
|
1518
|
+
case "bearer":
|
|
1519
|
+
securityScheme = {
|
|
1520
|
+
type: "http",
|
|
1521
|
+
scheme: "bearer",
|
|
1522
|
+
bearerFormat: "JWT",
|
|
1523
|
+
description: description ?? "JWT Bearer token authentication"
|
|
1524
|
+
};
|
|
1525
|
+
break;
|
|
1526
|
+
case "apiKey":
|
|
1527
|
+
securityScheme = {
|
|
1528
|
+
type: "apiKey",
|
|
1529
|
+
in: apiKeyIn,
|
|
1530
|
+
name: apiKeyName,
|
|
1531
|
+
description: description ?? `API Key authentication via ${apiKeyIn}`
|
|
1532
|
+
};
|
|
1533
|
+
break;
|
|
1534
|
+
case "basic":
|
|
1535
|
+
securityScheme = {
|
|
1536
|
+
type: "http",
|
|
1537
|
+
scheme: "basic",
|
|
1538
|
+
description: description ?? "HTTP Basic authentication"
|
|
1539
|
+
};
|
|
1540
|
+
break;
|
|
1541
|
+
default:
|
|
1542
|
+
throw new Error(`Unsupported scheme type: ${schemeType}`);
|
|
1543
|
+
}
|
|
1544
|
+
result.components.securitySchemes[schemeName] = securityScheme;
|
|
1545
|
+
const securityRequirement = {
|
|
1546
|
+
[schemeName]: []
|
|
1547
|
+
};
|
|
1548
|
+
if (!result.paths) {
|
|
1549
|
+
return result;
|
|
1550
|
+
}
|
|
1551
|
+
const operationSet = operations ? new Set(operations) : null;
|
|
1552
|
+
for (const [, pathItem] of Object.entries(result.paths)) {
|
|
1553
|
+
if (!pathItem) continue;
|
|
1554
|
+
const methods = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
|
|
1555
|
+
for (const method of methods) {
|
|
1556
|
+
const operation = pathItem[method];
|
|
1557
|
+
if (!operation) continue;
|
|
1558
|
+
if (operationSet) {
|
|
1559
|
+
if (!operation.operationId || !operationSet.has(operation.operationId)) {
|
|
1560
|
+
continue;
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
if (!operation.security) {
|
|
1564
|
+
operation.security = [];
|
|
1565
|
+
}
|
|
1566
|
+
const hasSecurityRequirement = operation.security.some((req) => Object.keys(req).includes(schemeName));
|
|
1567
|
+
if (!hasSecurityRequirement) {
|
|
1568
|
+
operation.security.push(securityRequirement);
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
return result;
|
|
1573
|
+
}
|
|
1574
|
+
function removeSecurityFromOperations(spec, operations) {
|
|
1575
|
+
const result = deepClone(spec);
|
|
1576
|
+
if (!result.paths) {
|
|
1577
|
+
return result;
|
|
1578
|
+
}
|
|
1579
|
+
const operationSet = operations ? new Set(operations) : null;
|
|
1580
|
+
for (const [, pathItem] of Object.entries(result.paths)) {
|
|
1581
|
+
if (!pathItem) continue;
|
|
1582
|
+
const methods = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
|
|
1583
|
+
for (const method of methods) {
|
|
1584
|
+
const operation = pathItem[method];
|
|
1585
|
+
if (!operation) continue;
|
|
1586
|
+
if (operationSet) {
|
|
1587
|
+
if (!operation.operationId || !operationSet.has(operation.operationId)) {
|
|
1588
|
+
continue;
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
operation.security = [];
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
return result;
|
|
1595
|
+
}
|
|
1152
1596
|
export {
|
|
1153
1597
|
FRONTMCP_EXTENSION_KEY,
|
|
1154
|
-
OpenapiAdapter
|
|
1598
|
+
OpenapiAdapter,
|
|
1599
|
+
forceJwtSecurity,
|
|
1600
|
+
removeSecurityFromOperations
|
|
1155
1601
|
};
|