@frontmcp/adapters 0.7.2 → 0.8.0
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/openapi/index.js
CHANGED
|
@@ -29,7 +29,9 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
29
29
|
var openapi_exports = {};
|
|
30
30
|
__export(openapi_exports, {
|
|
31
31
|
FRONTMCP_EXTENSION_KEY: () => FRONTMCP_EXTENSION_KEY,
|
|
32
|
-
default: () => OpenapiAdapter
|
|
32
|
+
default: () => OpenapiAdapter,
|
|
33
|
+
forceJwtSecurity: () => forceJwtSecurity,
|
|
34
|
+
removeSecurityFromOperations: () => removeSecurityFromOperations
|
|
33
35
|
});
|
|
34
36
|
module.exports = __toCommonJS(openapi_exports);
|
|
35
37
|
|
|
@@ -149,9 +151,6 @@ function applyAdditionalHeaders(headers, additionalHeaders) {
|
|
|
149
151
|
var DEFAULT_MAX_RESPONSE_SIZE = 10 * 1024 * 1024;
|
|
150
152
|
async function parseResponse(response, options) {
|
|
151
153
|
const maxSize = options?.maxResponseSize ?? DEFAULT_MAX_RESPONSE_SIZE;
|
|
152
|
-
if (!response.ok) {
|
|
153
|
-
throw new Error(`API request failed: ${response.status}`);
|
|
154
|
-
}
|
|
155
154
|
const contentLength = response.headers.get("content-length");
|
|
156
155
|
if (contentLength) {
|
|
157
156
|
const length = parseInt(contentLength, 10);
|
|
@@ -165,14 +164,29 @@ async function parseResponse(response, options) {
|
|
|
165
164
|
throw new Error(`Response size (${byteSize} bytes) exceeds maximum allowed (${maxSize} bytes)`);
|
|
166
165
|
}
|
|
167
166
|
const contentType = response.headers.get("content-type");
|
|
167
|
+
let data;
|
|
168
168
|
if (contentType?.toLowerCase().includes("application/json")) {
|
|
169
169
|
try {
|
|
170
|
-
|
|
170
|
+
data = JSON.parse(text);
|
|
171
171
|
} catch {
|
|
172
|
-
|
|
172
|
+
data = text;
|
|
173
173
|
}
|
|
174
|
+
} else {
|
|
175
|
+
data = text;
|
|
176
|
+
}
|
|
177
|
+
if (!response.ok) {
|
|
178
|
+
return {
|
|
179
|
+
status: response.status,
|
|
180
|
+
ok: false,
|
|
181
|
+
data,
|
|
182
|
+
error: typeof data === "object" && data !== null && "message" in data ? String(data.message) : typeof data === "string" ? data : `HTTP ${response.status} error`
|
|
183
|
+
};
|
|
174
184
|
}
|
|
175
|
-
return {
|
|
185
|
+
return {
|
|
186
|
+
status: response.status,
|
|
187
|
+
ok: true,
|
|
188
|
+
data
|
|
189
|
+
};
|
|
176
190
|
}
|
|
177
191
|
|
|
178
192
|
// libs/adapters/src/openapi/openapi.security.ts
|
|
@@ -598,15 +612,32 @@ function createOpenApiTool(openapiTool, options, logger) {
|
|
|
598
612
|
const metadata = openapiTool.metadata;
|
|
599
613
|
const inputTransforms = metadata.adapter?.inputTransforms ?? [];
|
|
600
614
|
const toolTransform = metadata.adapter?.toolTransform ?? {};
|
|
615
|
+
const postToolTransform = metadata.adapter?.postToolTransform;
|
|
601
616
|
const frontmcpValidation = validateFrontMcpExtension(metadata.frontmcp, openapiTool.name, logger);
|
|
602
617
|
const frontmcpExt = frontmcpValidation.data;
|
|
603
618
|
const schemaResult = getZodSchemaFromJsonSchema(openapiTool.inputSchema, openapiTool.name, logger);
|
|
619
|
+
let wrappedOutputSchema;
|
|
620
|
+
if (openapiTool.outputSchema !== void 0) {
|
|
621
|
+
const baseOutputSchema = openapiTool.outputSchema ?? { type: "string" };
|
|
622
|
+
wrappedOutputSchema = {
|
|
623
|
+
type: "object",
|
|
624
|
+
properties: {
|
|
625
|
+
status: { type: "number", description: "HTTP status code" },
|
|
626
|
+
ok: { type: "boolean", description: "Whether the response was successful" },
|
|
627
|
+
data: baseOutputSchema,
|
|
628
|
+
error: { type: "string", description: "Error message for non-ok responses" }
|
|
629
|
+
},
|
|
630
|
+
required: ["status", "ok"]
|
|
631
|
+
};
|
|
632
|
+
}
|
|
604
633
|
const toolMetadata = {
|
|
605
634
|
id: openapiTool.name,
|
|
606
635
|
name: openapiTool.name,
|
|
607
636
|
description: openapiTool.description,
|
|
608
637
|
inputSchema: schemaResult.schema.shape || {},
|
|
609
|
-
rawInputSchema: openapiTool.inputSchema
|
|
638
|
+
rawInputSchema: openapiTool.inputSchema,
|
|
639
|
+
// Add output schema for tool/list to expose (only if not moved to description)
|
|
640
|
+
...wrappedOutputSchema && { rawOutputSchema: wrappedOutputSchema }
|
|
610
641
|
};
|
|
611
642
|
if (schemaResult.conversionFailed) {
|
|
612
643
|
toolMetadata["_schemaConversionFailed"] = true;
|
|
@@ -719,11 +750,71 @@ function createOpenApiTool(openapiTool, options, logger) {
|
|
|
719
750
|
body: serializedBody,
|
|
720
751
|
signal: controller.signal
|
|
721
752
|
});
|
|
722
|
-
|
|
753
|
+
const apiResponse = await parseResponse(response, { maxResponseSize: options.maxResponseSize });
|
|
754
|
+
let transformedData = apiResponse.data;
|
|
755
|
+
if (postToolTransform) {
|
|
756
|
+
const transformCtx = {
|
|
757
|
+
ctx,
|
|
758
|
+
tool: openapiTool,
|
|
759
|
+
status: apiResponse.status,
|
|
760
|
+
ok: apiResponse.ok,
|
|
761
|
+
adapterOptions: options
|
|
762
|
+
};
|
|
763
|
+
const shouldTransform = postToolTransform.filter ? postToolTransform.filter(transformCtx) : true;
|
|
764
|
+
if (shouldTransform) {
|
|
765
|
+
try {
|
|
766
|
+
transformedData = await postToolTransform.transform(apiResponse.data, transformCtx);
|
|
767
|
+
} catch (err) {
|
|
768
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
769
|
+
const errorStack = err instanceof Error ? err.stack : void 0;
|
|
770
|
+
logger.error(`[${openapiTool.name}] Post-tool output transform failed`, {
|
|
771
|
+
error: errorMessage,
|
|
772
|
+
stack: errorStack,
|
|
773
|
+
status: apiResponse.status,
|
|
774
|
+
ok: apiResponse.ok
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
if (!apiResponse.ok) {
|
|
780
|
+
return {
|
|
781
|
+
content: [
|
|
782
|
+
{
|
|
783
|
+
type: "text",
|
|
784
|
+
text: JSON.stringify({
|
|
785
|
+
status: apiResponse.status,
|
|
786
|
+
error: apiResponse.error,
|
|
787
|
+
data: transformedData
|
|
788
|
+
})
|
|
789
|
+
}
|
|
790
|
+
],
|
|
791
|
+
isError: true,
|
|
792
|
+
_meta: {
|
|
793
|
+
status: apiResponse.status,
|
|
794
|
+
errorCode: "OPENAPI_ERROR"
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
return {
|
|
799
|
+
status: apiResponse.status,
|
|
800
|
+
ok: true,
|
|
801
|
+
data: transformedData
|
|
802
|
+
};
|
|
723
803
|
} catch (err) {
|
|
724
804
|
if (err instanceof Error && err.name === "AbortError") {
|
|
725
|
-
|
|
805
|
+
const timeoutError = new Error(`Request timeout after ${requestTimeout}ms for tool '${openapiTool.name}'`);
|
|
806
|
+
logger.error(`[${openapiTool.name}] API request timeout`, {
|
|
807
|
+
timeout: requestTimeout,
|
|
808
|
+
url,
|
|
809
|
+
method: openapiTool.metadata.method.toUpperCase()
|
|
810
|
+
});
|
|
811
|
+
throw timeoutError;
|
|
726
812
|
}
|
|
813
|
+
logger.error(`[${openapiTool.name}] API request failed`, {
|
|
814
|
+
url,
|
|
815
|
+
method: openapiTool.metadata.method.toUpperCase(),
|
|
816
|
+
error: err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : err
|
|
817
|
+
});
|
|
727
818
|
throw err;
|
|
728
819
|
} finally {
|
|
729
820
|
clearTimeout(timeoutId);
|
|
@@ -894,6 +985,15 @@ Add one of the following to your adapter configuration:
|
|
|
894
985
|
if (this.options.inputTransforms) {
|
|
895
986
|
transformedTools = transformedTools.map((tool2) => this.applyInputTransforms(tool2));
|
|
896
987
|
}
|
|
988
|
+
if (this.options.schemaTransforms) {
|
|
989
|
+
transformedTools = transformedTools.map((tool2) => this.applySchemaTransforms(tool2));
|
|
990
|
+
}
|
|
991
|
+
const dataTransforms = this.options.dataTransforms || this.options.outputTransforms;
|
|
992
|
+
if (this.options.outputSchema || dataTransforms) {
|
|
993
|
+
transformedTools = await Promise.all(
|
|
994
|
+
transformedTools.map((tool2) => this.applyOutputSchemaOptions(tool2, dataTransforms))
|
|
995
|
+
);
|
|
996
|
+
}
|
|
897
997
|
const tools = transformedTools.map((openapiTool) => createOpenApiTool(openapiTool, this.options, this.logger));
|
|
898
998
|
return { tools };
|
|
899
999
|
}
|
|
@@ -1163,6 +1263,251 @@ ${opDescription}`;
|
|
|
1163
1263
|
}
|
|
1164
1264
|
};
|
|
1165
1265
|
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Apply schema transforms to an OpenAPI tool.
|
|
1268
|
+
* Modifies input and output schema definitions.
|
|
1269
|
+
* @private
|
|
1270
|
+
*/
|
|
1271
|
+
applySchemaTransforms(tool2) {
|
|
1272
|
+
const opts = this.options.schemaTransforms;
|
|
1273
|
+
if (!opts) return tool2;
|
|
1274
|
+
const ctx = {
|
|
1275
|
+
tool: tool2,
|
|
1276
|
+
adapterOptions: this.options
|
|
1277
|
+
};
|
|
1278
|
+
let newInputSchema = tool2.inputSchema;
|
|
1279
|
+
let newOutputSchema = tool2.outputSchema;
|
|
1280
|
+
if (opts.input) {
|
|
1281
|
+
const inputTransform = this.collectInputSchemaTransform(tool2);
|
|
1282
|
+
if (inputTransform) {
|
|
1283
|
+
newInputSchema = inputTransform(newInputSchema, ctx);
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
if (opts.output) {
|
|
1287
|
+
const outputTransform = this.collectOutputSchemaTransform(tool2);
|
|
1288
|
+
if (outputTransform) {
|
|
1289
|
+
newOutputSchema = outputTransform(newOutputSchema, ctx);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
if (newInputSchema === tool2.inputSchema && newOutputSchema === tool2.outputSchema) {
|
|
1293
|
+
return tool2;
|
|
1294
|
+
}
|
|
1295
|
+
this.logger.debug(`Applied schema transforms to '${tool2.name}'`);
|
|
1296
|
+
return {
|
|
1297
|
+
...tool2,
|
|
1298
|
+
inputSchema: newInputSchema,
|
|
1299
|
+
outputSchema: newOutputSchema
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* Collect input schema transform for a specific tool.
|
|
1304
|
+
* @private
|
|
1305
|
+
*/
|
|
1306
|
+
collectInputSchemaTransform(tool2) {
|
|
1307
|
+
const opts = this.options.schemaTransforms?.input;
|
|
1308
|
+
if (!opts) return void 0;
|
|
1309
|
+
if (opts.generator) {
|
|
1310
|
+
const generated = opts.generator(tool2);
|
|
1311
|
+
if (generated) return generated;
|
|
1312
|
+
}
|
|
1313
|
+
if (opts.perTool?.[tool2.name]) {
|
|
1314
|
+
return opts.perTool[tool2.name];
|
|
1315
|
+
}
|
|
1316
|
+
return opts.global;
|
|
1317
|
+
}
|
|
1318
|
+
/**
|
|
1319
|
+
* Collect output schema transform for a specific tool.
|
|
1320
|
+
* @private
|
|
1321
|
+
*/
|
|
1322
|
+
collectOutputSchemaTransform(tool2) {
|
|
1323
|
+
const opts = this.options.schemaTransforms?.output;
|
|
1324
|
+
if (!opts) return void 0;
|
|
1325
|
+
if (opts.generator) {
|
|
1326
|
+
const generated = opts.generator(tool2);
|
|
1327
|
+
if (generated) return generated;
|
|
1328
|
+
}
|
|
1329
|
+
if (opts.perTool?.[tool2.name]) {
|
|
1330
|
+
return opts.perTool[tool2.name];
|
|
1331
|
+
}
|
|
1332
|
+
return opts.global;
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Apply output schema options to an OpenAPI tool.
|
|
1336
|
+
* Handles output schema mode and async description formatting.
|
|
1337
|
+
* @private
|
|
1338
|
+
*/
|
|
1339
|
+
async applyOutputSchemaOptions(tool2, dataTransforms) {
|
|
1340
|
+
const outputSchemaOpts = this.options.outputSchema;
|
|
1341
|
+
let newDescription = tool2.description;
|
|
1342
|
+
let newOutputSchema = tool2.outputSchema;
|
|
1343
|
+
const mode = outputSchemaOpts?.mode ?? "definition";
|
|
1344
|
+
if (newOutputSchema && (mode === "description" || mode === "both")) {
|
|
1345
|
+
const descriptionFormat = outputSchemaOpts?.descriptionFormat ?? "summary";
|
|
1346
|
+
const formatter = outputSchemaOpts?.descriptionFormatter;
|
|
1347
|
+
const formatterCtx = {
|
|
1348
|
+
tool: tool2,
|
|
1349
|
+
adapterOptions: this.options,
|
|
1350
|
+
originalDescription: tool2.description
|
|
1351
|
+
};
|
|
1352
|
+
let schemaText;
|
|
1353
|
+
if (formatter) {
|
|
1354
|
+
schemaText = await Promise.resolve(formatter(newOutputSchema, formatterCtx));
|
|
1355
|
+
} else {
|
|
1356
|
+
schemaText = this.formatSchemaForDescription(newOutputSchema, descriptionFormat);
|
|
1357
|
+
}
|
|
1358
|
+
newDescription = tool2.description + schemaText;
|
|
1359
|
+
}
|
|
1360
|
+
if (mode === "description") {
|
|
1361
|
+
newOutputSchema = void 0;
|
|
1362
|
+
}
|
|
1363
|
+
const preTransform = this.collectPreToolTransformsFromDataTransforms(tool2, dataTransforms);
|
|
1364
|
+
if (preTransform) {
|
|
1365
|
+
const ctx = {
|
|
1366
|
+
tool: tool2,
|
|
1367
|
+
adapterOptions: this.options
|
|
1368
|
+
};
|
|
1369
|
+
if (preTransform.transformSchema) {
|
|
1370
|
+
newOutputSchema = preTransform.transformSchema(newOutputSchema, ctx);
|
|
1371
|
+
}
|
|
1372
|
+
if (preTransform.transformDescription) {
|
|
1373
|
+
newDescription = preTransform.transformDescription(newDescription, newOutputSchema, ctx);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
const postTransform = this.collectPostToolTransformsFromDataTransforms(tool2, dataTransforms);
|
|
1377
|
+
if (newDescription === tool2.description && newOutputSchema === tool2.outputSchema && !postTransform) {
|
|
1378
|
+
return tool2;
|
|
1379
|
+
}
|
|
1380
|
+
this.logger.debug(`Applied output schema options to '${tool2.name}' (mode: ${mode})`);
|
|
1381
|
+
const metadataRecord = tool2.metadata;
|
|
1382
|
+
const existingAdapter = metadataRecord["adapter"];
|
|
1383
|
+
return {
|
|
1384
|
+
...tool2,
|
|
1385
|
+
description: newDescription,
|
|
1386
|
+
outputSchema: newOutputSchema,
|
|
1387
|
+
metadata: {
|
|
1388
|
+
...tool2.metadata,
|
|
1389
|
+
adapter: {
|
|
1390
|
+
...existingAdapter || {},
|
|
1391
|
+
...postTransform && { postToolTransform: postTransform }
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
/**
|
|
1397
|
+
* Format schema for description based on mode.
|
|
1398
|
+
* @private
|
|
1399
|
+
*/
|
|
1400
|
+
formatSchemaForDescription(schema, mode) {
|
|
1401
|
+
switch (mode) {
|
|
1402
|
+
case "jsonSchema":
|
|
1403
|
+
return `
|
|
1404
|
+
|
|
1405
|
+
## Output Schema
|
|
1406
|
+
\`\`\`json
|
|
1407
|
+
${JSON.stringify(schema, null, 2)}
|
|
1408
|
+
\`\`\``;
|
|
1409
|
+
case "summary":
|
|
1410
|
+
return `
|
|
1411
|
+
|
|
1412
|
+
## Returns
|
|
1413
|
+
${this.formatSchemaAsSummary(schema)}`;
|
|
1414
|
+
default:
|
|
1415
|
+
return `
|
|
1416
|
+
|
|
1417
|
+
## Returns
|
|
1418
|
+
${this.formatSchemaAsSummary(schema)}`;
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Collect pre-tool transforms from dataTransforms options.
|
|
1423
|
+
* @private
|
|
1424
|
+
*/
|
|
1425
|
+
collectPreToolTransformsFromDataTransforms(tool2, dataTransforms) {
|
|
1426
|
+
const opts = dataTransforms?.preToolTransforms;
|
|
1427
|
+
if (!opts) return void 0;
|
|
1428
|
+
let result;
|
|
1429
|
+
if (opts.global) {
|
|
1430
|
+
result = { ...opts.global };
|
|
1431
|
+
}
|
|
1432
|
+
if (opts.perTool?.[tool2.name]) {
|
|
1433
|
+
result = { ...result, ...opts.perTool[tool2.name] };
|
|
1434
|
+
}
|
|
1435
|
+
if (opts.generator) {
|
|
1436
|
+
const generated = opts.generator(tool2);
|
|
1437
|
+
if (generated) {
|
|
1438
|
+
result = { ...result, ...generated };
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
return result;
|
|
1442
|
+
}
|
|
1443
|
+
/**
|
|
1444
|
+
* Collect post-tool transforms from dataTransforms options.
|
|
1445
|
+
* @private
|
|
1446
|
+
*/
|
|
1447
|
+
collectPostToolTransformsFromDataTransforms(tool2, dataTransforms) {
|
|
1448
|
+
const opts = dataTransforms?.postToolTransforms;
|
|
1449
|
+
if (!opts) return void 0;
|
|
1450
|
+
let result;
|
|
1451
|
+
if (opts.global) {
|
|
1452
|
+
result = { ...opts.global };
|
|
1453
|
+
}
|
|
1454
|
+
if (opts.perTool?.[tool2.name]) {
|
|
1455
|
+
const perTool = opts.perTool[tool2.name];
|
|
1456
|
+
result = result ? {
|
|
1457
|
+
transform: perTool.transform,
|
|
1458
|
+
filter: perTool.filter ?? result.filter
|
|
1459
|
+
} : perTool;
|
|
1460
|
+
}
|
|
1461
|
+
if (opts.generator) {
|
|
1462
|
+
const generated = opts.generator(tool2);
|
|
1463
|
+
if (generated) {
|
|
1464
|
+
result = result ? {
|
|
1465
|
+
transform: generated.transform,
|
|
1466
|
+
filter: generated.filter ?? result.filter
|
|
1467
|
+
} : generated;
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
return result;
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* Format JSON Schema as human-readable summary.
|
|
1474
|
+
* @private
|
|
1475
|
+
*/
|
|
1476
|
+
formatSchemaAsSummary(schema) {
|
|
1477
|
+
const lines = [];
|
|
1478
|
+
if (schema.type === "object" && schema.properties) {
|
|
1479
|
+
const required = new Set(schema.required || []);
|
|
1480
|
+
for (const [name, propSchema] of Object.entries(schema.properties)) {
|
|
1481
|
+
const isRequired = required.has(name);
|
|
1482
|
+
const typeStr = this.getSchemaTypeString(propSchema);
|
|
1483
|
+
const desc = propSchema.description ? ` - ${propSchema.description}` : "";
|
|
1484
|
+
const reqStr = isRequired ? " (required)" : " (optional)";
|
|
1485
|
+
lines.push(`- **${name}**: ${typeStr}${reqStr}${desc}`);
|
|
1486
|
+
}
|
|
1487
|
+
} else if (schema.type === "array" && schema.items) {
|
|
1488
|
+
const itemType = this.getSchemaTypeString(schema.items);
|
|
1489
|
+
lines.push(`Array of ${itemType}`);
|
|
1490
|
+
} else {
|
|
1491
|
+
lines.push(this.getSchemaTypeString(schema));
|
|
1492
|
+
}
|
|
1493
|
+
return lines.join("\n");
|
|
1494
|
+
}
|
|
1495
|
+
/**
|
|
1496
|
+
* Get human-readable type string from JSON Schema.
|
|
1497
|
+
* @private
|
|
1498
|
+
*/
|
|
1499
|
+
getSchemaTypeString(schema) {
|
|
1500
|
+
if (schema.type === "array") {
|
|
1501
|
+
return schema.items ? `${this.getSchemaTypeString(schema.items)}[]` : "array";
|
|
1502
|
+
}
|
|
1503
|
+
if (schema.type === "object") {
|
|
1504
|
+
return schema.title || "object";
|
|
1505
|
+
}
|
|
1506
|
+
if (Array.isArray(schema.type)) {
|
|
1507
|
+
return schema.type.join(" | ");
|
|
1508
|
+
}
|
|
1509
|
+
return schema.type || "any";
|
|
1510
|
+
}
|
|
1166
1511
|
};
|
|
1167
1512
|
OpenapiAdapter = __decorateClass([
|
|
1168
1513
|
(0, import_sdk2.Adapter)({
|
|
@@ -1173,7 +1518,110 @@ OpenapiAdapter = __decorateClass([
|
|
|
1173
1518
|
|
|
1174
1519
|
// libs/adapters/src/openapi/openapi.types.ts
|
|
1175
1520
|
var FRONTMCP_EXTENSION_KEY = "x-frontmcp";
|
|
1521
|
+
|
|
1522
|
+
// libs/adapters/src/openapi/openapi.spec-utils.ts
|
|
1523
|
+
function deepClone(obj) {
|
|
1524
|
+
return JSON.parse(JSON.stringify(obj));
|
|
1525
|
+
}
|
|
1526
|
+
function forceJwtSecurity(spec, options = {}) {
|
|
1527
|
+
const result = deepClone(spec);
|
|
1528
|
+
const {
|
|
1529
|
+
schemeName = "BearerAuth",
|
|
1530
|
+
schemeType = "bearer",
|
|
1531
|
+
apiKeyIn = "header",
|
|
1532
|
+
apiKeyName = "X-API-Key",
|
|
1533
|
+
operations,
|
|
1534
|
+
description
|
|
1535
|
+
} = options;
|
|
1536
|
+
if (!result.components) {
|
|
1537
|
+
result.components = {};
|
|
1538
|
+
}
|
|
1539
|
+
if (!result.components.securitySchemes) {
|
|
1540
|
+
result.components.securitySchemes = {};
|
|
1541
|
+
}
|
|
1542
|
+
let securityScheme;
|
|
1543
|
+
switch (schemeType) {
|
|
1544
|
+
case "bearer":
|
|
1545
|
+
securityScheme = {
|
|
1546
|
+
type: "http",
|
|
1547
|
+
scheme: "bearer",
|
|
1548
|
+
bearerFormat: "JWT",
|
|
1549
|
+
description: description ?? "JWT Bearer token authentication"
|
|
1550
|
+
};
|
|
1551
|
+
break;
|
|
1552
|
+
case "apiKey":
|
|
1553
|
+
securityScheme = {
|
|
1554
|
+
type: "apiKey",
|
|
1555
|
+
in: apiKeyIn,
|
|
1556
|
+
name: apiKeyName,
|
|
1557
|
+
description: description ?? `API Key authentication via ${apiKeyIn}`
|
|
1558
|
+
};
|
|
1559
|
+
break;
|
|
1560
|
+
case "basic":
|
|
1561
|
+
securityScheme = {
|
|
1562
|
+
type: "http",
|
|
1563
|
+
scheme: "basic",
|
|
1564
|
+
description: description ?? "HTTP Basic authentication"
|
|
1565
|
+
};
|
|
1566
|
+
break;
|
|
1567
|
+
default:
|
|
1568
|
+
throw new Error(`Unsupported scheme type: ${schemeType}`);
|
|
1569
|
+
}
|
|
1570
|
+
result.components.securitySchemes[schemeName] = securityScheme;
|
|
1571
|
+
const securityRequirement = {
|
|
1572
|
+
[schemeName]: []
|
|
1573
|
+
};
|
|
1574
|
+
if (!result.paths) {
|
|
1575
|
+
return result;
|
|
1576
|
+
}
|
|
1577
|
+
const operationSet = operations ? new Set(operations) : null;
|
|
1578
|
+
for (const [, pathItem] of Object.entries(result.paths)) {
|
|
1579
|
+
if (!pathItem) continue;
|
|
1580
|
+
const methods = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
|
|
1581
|
+
for (const method of methods) {
|
|
1582
|
+
const operation = pathItem[method];
|
|
1583
|
+
if (!operation) continue;
|
|
1584
|
+
if (operationSet) {
|
|
1585
|
+
if (!operation.operationId || !operationSet.has(operation.operationId)) {
|
|
1586
|
+
continue;
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
if (!operation.security) {
|
|
1590
|
+
operation.security = [];
|
|
1591
|
+
}
|
|
1592
|
+
const hasSecurityRequirement = operation.security.some((req) => Object.keys(req).includes(schemeName));
|
|
1593
|
+
if (!hasSecurityRequirement) {
|
|
1594
|
+
operation.security.push(securityRequirement);
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
return result;
|
|
1599
|
+
}
|
|
1600
|
+
function removeSecurityFromOperations(spec, operations) {
|
|
1601
|
+
const result = deepClone(spec);
|
|
1602
|
+
if (!result.paths) {
|
|
1603
|
+
return result;
|
|
1604
|
+
}
|
|
1605
|
+
const operationSet = operations ? new Set(operations) : null;
|
|
1606
|
+
for (const [, pathItem] of Object.entries(result.paths)) {
|
|
1607
|
+
if (!pathItem) continue;
|
|
1608
|
+
const methods = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
|
|
1609
|
+
for (const method of methods) {
|
|
1610
|
+
const operation = pathItem[method];
|
|
1611
|
+
if (!operation) continue;
|
|
1612
|
+
if (operationSet) {
|
|
1613
|
+
if (!operation.operationId || !operationSet.has(operation.operationId)) {
|
|
1614
|
+
continue;
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
operation.security = [];
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
return result;
|
|
1621
|
+
}
|
|
1176
1622
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1177
1623
|
0 && (module.exports = {
|
|
1178
|
-
FRONTMCP_EXTENSION_KEY
|
|
1624
|
+
FRONTMCP_EXTENSION_KEY,
|
|
1625
|
+
forceJwtSecurity,
|
|
1626
|
+
removeSecurityFromOperations
|
|
1179
1627
|
});
|
|
@@ -48,5 +48,52 @@ export default class OpenapiAdapter extends DynamicAdapter<OpenApiAdapterOptions
|
|
|
48
48
|
* @private
|
|
49
49
|
*/
|
|
50
50
|
private filterSecuritySchemes;
|
|
51
|
+
/**
|
|
52
|
+
* Apply schema transforms to an OpenAPI tool.
|
|
53
|
+
* Modifies input and output schema definitions.
|
|
54
|
+
* @private
|
|
55
|
+
*/
|
|
56
|
+
private applySchemaTransforms;
|
|
57
|
+
/**
|
|
58
|
+
* Collect input schema transform for a specific tool.
|
|
59
|
+
* @private
|
|
60
|
+
*/
|
|
61
|
+
private collectInputSchemaTransform;
|
|
62
|
+
/**
|
|
63
|
+
* Collect output schema transform for a specific tool.
|
|
64
|
+
* @private
|
|
65
|
+
*/
|
|
66
|
+
private collectOutputSchemaTransform;
|
|
67
|
+
/**
|
|
68
|
+
* Apply output schema options to an OpenAPI tool.
|
|
69
|
+
* Handles output schema mode and async description formatting.
|
|
70
|
+
* @private
|
|
71
|
+
*/
|
|
72
|
+
private applyOutputSchemaOptions;
|
|
73
|
+
/**
|
|
74
|
+
* Format schema for description based on mode.
|
|
75
|
+
* @private
|
|
76
|
+
*/
|
|
77
|
+
private formatSchemaForDescription;
|
|
78
|
+
/**
|
|
79
|
+
* Collect pre-tool transforms from dataTransforms options.
|
|
80
|
+
* @private
|
|
81
|
+
*/
|
|
82
|
+
private collectPreToolTransformsFromDataTransforms;
|
|
83
|
+
/**
|
|
84
|
+
* Collect post-tool transforms from dataTransforms options.
|
|
85
|
+
* @private
|
|
86
|
+
*/
|
|
87
|
+
private collectPostToolTransformsFromDataTransforms;
|
|
88
|
+
/**
|
|
89
|
+
* Format JSON Schema as human-readable summary.
|
|
90
|
+
* @private
|
|
91
|
+
*/
|
|
92
|
+
private formatSchemaAsSummary;
|
|
93
|
+
/**
|
|
94
|
+
* Get human-readable type string from JSON Schema.
|
|
95
|
+
* @private
|
|
96
|
+
*/
|
|
97
|
+
private getSchemaTypeString;
|
|
51
98
|
}
|
|
52
99
|
//# sourceMappingURL=openapi.adapter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openapi.adapter.d.ts","sourceRoot":"","sources":["../../src/openapi/openapi.adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,cAAc,EAAE,uBAAuB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACjG,OAAO,
|
|
1
|
+
{"version":3,"file":"openapi.adapter.d.ts","sourceRoot":"","sources":["../../src/openapi/openapi.adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,cAAc,EAAE,uBAAuB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACjG,OAAO,EACL,qBAAqB,EAWtB,MAAM,iBAAiB,CAAC;AA2BzB,MAAM,CAAC,OAAO,OAAO,cAAe,SAAQ,cAAc,CAAC,qBAAqB,CAAC;IAC/E,OAAO,CAAC,SAAS,CAAC,CAAuB;IACzC,OAAO,CAAC,MAAM,CAAiB;IACxB,OAAO,EAAE,qBAAqB,CAAC;gBAE1B,OAAO,EAAE,qBAAqB;IAO1C;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI;IAIjC,KAAK,IAAI,OAAO,CAAC,uBAAuB,CAAC;IAgH/C;;;OAGG;YACW,mBAAmB;IAqBjC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IA0C5B;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IA4D7B;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAsC3B;;;OAGG;IACH,OAAO,CAAC,wBAAwB;IAuBhC;;;;;OAKG;IACH,OAAO,CAAC,oBAAoB;IAoD5B;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IA2D7B;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IA0C7B;;;OAGG;IACH,OAAO,CAAC,2BAA2B;IAiBnC;;;OAGG;IACH,OAAO,CAAC,4BAA4B;IAmBpC;;;;OAIG;YACW,wBAAwB;IAiFtC;;;OAGG;IACH,OAAO,CAAC,0BAA0B;IAgBlC;;;OAGG;IACH,OAAO,CAAC,0CAA0C;IA8BlD;;;OAGG;IACH,OAAO,CAAC,2CAA2C;IAyCnD;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAuB7B;;;OAGG;IACH,OAAO,CAAC,mBAAmB;CAY5B"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';
|
|
2
|
+
/**
|
|
3
|
+
* Options for forcing security on OpenAPI operations
|
|
4
|
+
*/
|
|
5
|
+
export interface ForceSecurityOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Security scheme name to add/override.
|
|
8
|
+
* @default 'BearerAuth'
|
|
9
|
+
*/
|
|
10
|
+
schemeName?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Security scheme type.
|
|
13
|
+
* - 'bearer': HTTP Bearer authentication (JWT)
|
|
14
|
+
* - 'apiKey': API Key authentication
|
|
15
|
+
* - 'basic': HTTP Basic authentication
|
|
16
|
+
* @default 'bearer'
|
|
17
|
+
*/
|
|
18
|
+
schemeType?: 'bearer' | 'apiKey' | 'basic';
|
|
19
|
+
/**
|
|
20
|
+
* For apiKey type: where to send the API key.
|
|
21
|
+
* @default 'header'
|
|
22
|
+
*/
|
|
23
|
+
apiKeyIn?: 'header' | 'query' | 'cookie';
|
|
24
|
+
/**
|
|
25
|
+
* For apiKey type: the name of the header/query/cookie.
|
|
26
|
+
* @default 'X-API-Key'
|
|
27
|
+
*/
|
|
28
|
+
apiKeyName?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Apply security only to these operation IDs.
|
|
31
|
+
* If not specified, applies to all operations.
|
|
32
|
+
*/
|
|
33
|
+
operations?: string[];
|
|
34
|
+
/**
|
|
35
|
+
* Description for the security scheme.
|
|
36
|
+
*/
|
|
37
|
+
description?: string;
|
|
38
|
+
}
|
|
39
|
+
type OpenAPIDocument = OpenAPIV3.Document | OpenAPIV3_1.Document;
|
|
40
|
+
/**
|
|
41
|
+
* Modify OpenAPI spec to force JWT/Bearer security on operations.
|
|
42
|
+
* Returns a new spec object (does not mutate input).
|
|
43
|
+
*
|
|
44
|
+
* @param spec - Original OpenAPI specification
|
|
45
|
+
* @param options - Security configuration options
|
|
46
|
+
* @returns Modified OpenAPI specification with security applied
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* import { forceJwtSecurity } from '@frontmcp/adapters/openapi';
|
|
51
|
+
*
|
|
52
|
+
* // Force Bearer auth on all operations
|
|
53
|
+
* const securedSpec = forceJwtSecurity(originalSpec);
|
|
54
|
+
*
|
|
55
|
+
* // Force Bearer auth with custom scheme name
|
|
56
|
+
* const securedSpec = forceJwtSecurity(originalSpec, {
|
|
57
|
+
* schemeName: 'JWTAuth',
|
|
58
|
+
* description: 'JWT Bearer token authentication'
|
|
59
|
+
* });
|
|
60
|
+
*
|
|
61
|
+
* // Force auth on specific operations only
|
|
62
|
+
* const securedSpec = forceJwtSecurity(originalSpec, {
|
|
63
|
+
* operations: ['createUser', 'updateUser', 'deleteUser']
|
|
64
|
+
* });
|
|
65
|
+
*
|
|
66
|
+
* // Force API Key auth
|
|
67
|
+
* const securedSpec = forceJwtSecurity(originalSpec, {
|
|
68
|
+
* schemeName: 'ApiKeyAuth',
|
|
69
|
+
* schemeType: 'apiKey',
|
|
70
|
+
* apiKeyIn: 'header',
|
|
71
|
+
* apiKeyName: 'X-API-Key'
|
|
72
|
+
* });
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export declare function forceJwtSecurity(spec: OpenAPIDocument, options?: ForceSecurityOptions): OpenAPIDocument;
|
|
76
|
+
/**
|
|
77
|
+
* Remove security requirements from specific operations.
|
|
78
|
+
* Returns a new spec object (does not mutate input).
|
|
79
|
+
*
|
|
80
|
+
* @param spec - Original OpenAPI specification
|
|
81
|
+
* @param operations - Operation IDs to remove security from. If not specified, removes from all.
|
|
82
|
+
* @returns Modified OpenAPI specification
|
|
83
|
+
*/
|
|
84
|
+
export declare function removeSecurityFromOperations(spec: OpenAPIDocument, operations?: string[]): OpenAPIDocument;
|
|
85
|
+
export {};
|
|
86
|
+
//# sourceMappingURL=openapi.spec-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi.spec-utils.d.ts","sourceRoot":"","sources":["../../src/openapi/openapi.spec-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5D;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;IAE3C;;;OAGG;IACH,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;IAEzC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAEtB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,KAAK,eAAe,GAAG,SAAS,CAAC,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC;AAUjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,EAAE,OAAO,GAAE,oBAAyB,GAAG,eAAe,CAuG3G;AAED;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,eAAe,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,eAAe,CAgC1G"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openapi.tool.d.ts","sourceRoot":"","sources":["../../src/openapi/openapi.tool.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,cAAc,EAAE,MAAM,eAAe,CAAC;AAErD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EACV,qBAAqB,
|
|
1
|
+
{"version":3,"file":"openapi.tool.d.ts","sourceRoot":"","sources":["../../src/openapi/openapi.tool.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,cAAc,EAAE,MAAM,eAAe,CAAC;AAErD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EACV,qBAAqB,EAKtB,MAAM,iBAAiB,CAAC;AASzB;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,cAAc,EAAE,OAAO,EAAE,qBAAqB,EAAE,MAAM,EAAE,cAAc,cA+QpH"}
|