mcp-from-openapi 2.1.0 → 2.1.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/CHANGELOG.md +7 -0
- package/README.md +1 -1
- package/esm/index.mjs +1561 -0
- package/esm/package.json +66 -0
- package/index.js +1612 -0
- package/package.json +18 -10
- package/src/errors.js +0 -67
- package/src/errors.js.map +0 -1
- package/src/generator.js +0 -381
- package/src/generator.js.map +0 -1
- package/src/index.js +0 -30
- package/src/index.js.map +0 -1
- package/src/parameter-resolver.js +0 -294
- package/src/parameter-resolver.js.map +0 -1
- package/src/response-builder.js +0 -149
- package/src/response-builder.js.map +0 -1
- package/src/schema-builder.js +0 -291
- package/src/schema-builder.js.map +0 -1
- package/src/security-resolver.js +0 -324
- package/src/security-resolver.js.map +0 -1
- package/src/types.js +0 -99
- package/src/types.js.map +0 -1
- package/src/validator.js +0 -213
- package/src/validator.js.map +0 -1
- /package/{src/errors.d.ts → errors.d.ts} +0 -0
- /package/{src/generator.d.ts → generator.d.ts} +0 -0
- /package/{src/index.d.ts → index.d.ts} +0 -0
- /package/{src/parameter-resolver.d.ts → parameter-resolver.d.ts} +0 -0
- /package/{src/response-builder.d.ts → response-builder.d.ts} +0 -0
- /package/{src/schema-builder.d.ts → schema-builder.d.ts} +0 -0
- /package/{src/security-resolver.d.ts → security-resolver.d.ts} +0 -0
- /package/{src/types.d.ts → types.d.ts} +0 -0
- /package/{src/validator.d.ts → validator.d.ts} +0 -0
package/esm/index.mjs
ADDED
|
@@ -0,0 +1,1561 @@
|
|
|
1
|
+
// libs/mcp-from-openapi/src/generator.ts
|
|
2
|
+
import * as yaml from "yaml";
|
|
3
|
+
import * as fs from "fs/promises";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import $RefParser from "@apidevtools/json-schema-ref-parser";
|
|
6
|
+
|
|
7
|
+
// libs/mcp-from-openapi/src/types.ts
|
|
8
|
+
function isReferenceObject(obj) {
|
|
9
|
+
return obj && typeof obj === "object" && "$ref" in obj;
|
|
10
|
+
}
|
|
11
|
+
function toJsonSchema(schema) {
|
|
12
|
+
if (isReferenceObject(schema)) {
|
|
13
|
+
return { $ref: schema.$ref };
|
|
14
|
+
}
|
|
15
|
+
const { exclusiveMaximum, exclusiveMinimum, maximum, minimum, ...rest } = schema;
|
|
16
|
+
const result = { ...rest };
|
|
17
|
+
if (typeof exclusiveMaximum === "boolean") {
|
|
18
|
+
if (exclusiveMaximum && maximum !== void 0) {
|
|
19
|
+
result["exclusiveMaximum"] = maximum;
|
|
20
|
+
} else if (maximum !== void 0) {
|
|
21
|
+
result["maximum"] = maximum;
|
|
22
|
+
}
|
|
23
|
+
} else if (exclusiveMaximum !== void 0) {
|
|
24
|
+
result["exclusiveMaximum"] = exclusiveMaximum;
|
|
25
|
+
if (maximum !== void 0) {
|
|
26
|
+
result["maximum"] = maximum;
|
|
27
|
+
}
|
|
28
|
+
} else if (maximum !== void 0) {
|
|
29
|
+
result["maximum"] = maximum;
|
|
30
|
+
}
|
|
31
|
+
if (typeof exclusiveMinimum === "boolean") {
|
|
32
|
+
if (exclusiveMinimum && minimum !== void 0) {
|
|
33
|
+
result["exclusiveMinimum"] = minimum;
|
|
34
|
+
} else if (minimum !== void 0) {
|
|
35
|
+
result["minimum"] = minimum;
|
|
36
|
+
}
|
|
37
|
+
} else if (exclusiveMinimum !== void 0) {
|
|
38
|
+
result["exclusiveMinimum"] = exclusiveMinimum;
|
|
39
|
+
if (minimum !== void 0) {
|
|
40
|
+
result["minimum"] = minimum;
|
|
41
|
+
}
|
|
42
|
+
} else if (minimum !== void 0) {
|
|
43
|
+
result["minimum"] = minimum;
|
|
44
|
+
}
|
|
45
|
+
if (result["properties"] && typeof result["properties"] === "object") {
|
|
46
|
+
const props = {};
|
|
47
|
+
for (const [key, value] of Object.entries(result["properties"])) {
|
|
48
|
+
props[key] = toJsonSchema(value);
|
|
49
|
+
}
|
|
50
|
+
result["properties"] = props;
|
|
51
|
+
}
|
|
52
|
+
if (result["items"]) {
|
|
53
|
+
if (Array.isArray(result["items"])) {
|
|
54
|
+
result["items"] = result["items"].map(toJsonSchema);
|
|
55
|
+
} else {
|
|
56
|
+
result["items"] = toJsonSchema(result["items"]);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (result["additionalProperties"] && typeof result["additionalProperties"] === "object") {
|
|
60
|
+
result["additionalProperties"] = toJsonSchema(result["additionalProperties"]);
|
|
61
|
+
}
|
|
62
|
+
for (const key of ["allOf", "anyOf", "oneOf"]) {
|
|
63
|
+
if (result[key] && Array.isArray(result[key])) {
|
|
64
|
+
result[key] = result[key].map(toJsonSchema);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (result["not"]) {
|
|
68
|
+
result["not"] = toJsonSchema(result["not"]);
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// libs/mcp-from-openapi/src/parameter-resolver.ts
|
|
74
|
+
var ParameterResolver = class {
|
|
75
|
+
namingStrategy;
|
|
76
|
+
constructor(namingStrategy) {
|
|
77
|
+
this.namingStrategy = namingStrategy ?? {
|
|
78
|
+
conflictResolver: this.defaultConflictResolver
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Default conflict resolver: prefix with location
|
|
83
|
+
*/
|
|
84
|
+
defaultConflictResolver(paramName, location, index) {
|
|
85
|
+
const locationPrefix = {
|
|
86
|
+
path: "path",
|
|
87
|
+
query: "query",
|
|
88
|
+
header: "header",
|
|
89
|
+
cookie: "cookie",
|
|
90
|
+
body: "body"
|
|
91
|
+
}[location];
|
|
92
|
+
return `${locationPrefix}${paramName.charAt(0).toUpperCase()}${paramName.slice(1)}`;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Resolve all parameters for an operation
|
|
96
|
+
*/
|
|
97
|
+
resolve(operation, pathParameters, securityRequirements, includeSecurityInInput) {
|
|
98
|
+
const allParameters = [...pathParameters ?? [], ...operation.parameters ?? []];
|
|
99
|
+
const requestBody = operation.requestBody;
|
|
100
|
+
const parametersByName = /* @__PURE__ */ new Map();
|
|
101
|
+
allParameters.forEach((param) => {
|
|
102
|
+
const info = {
|
|
103
|
+
name: param.name,
|
|
104
|
+
location: param.in,
|
|
105
|
+
required: param.required ?? param.in === "path",
|
|
106
|
+
schema: param.schema ?? { type: "string" },
|
|
107
|
+
description: param.description,
|
|
108
|
+
style: param.style,
|
|
109
|
+
explode: param.explode,
|
|
110
|
+
allowReserved: param.allowReserved,
|
|
111
|
+
deprecated: param.deprecated
|
|
112
|
+
};
|
|
113
|
+
if (!parametersByName.has(param.name)) {
|
|
114
|
+
parametersByName.set(param.name, []);
|
|
115
|
+
}
|
|
116
|
+
parametersByName.get(param.name).push(info);
|
|
117
|
+
});
|
|
118
|
+
if (requestBody?.content) {
|
|
119
|
+
const contentType = this.selectContentType(requestBody.content);
|
|
120
|
+
const mediaType = requestBody.content[contentType];
|
|
121
|
+
if (mediaType?.schema) {
|
|
122
|
+
this.extractBodyParameters(mediaType.schema, parametersByName, requestBody.required ?? false, contentType);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const properties = {};
|
|
126
|
+
const required = [];
|
|
127
|
+
const mapper = [];
|
|
128
|
+
for (const [originalName, params] of parametersByName.entries()) {
|
|
129
|
+
if (params.length === 1) {
|
|
130
|
+
const param = params[0];
|
|
131
|
+
const inputKey = originalName;
|
|
132
|
+
properties[inputKey] = this.buildParameterSchema(param);
|
|
133
|
+
if (param.required) {
|
|
134
|
+
required.push(inputKey);
|
|
135
|
+
}
|
|
136
|
+
mapper.push({
|
|
137
|
+
inputKey,
|
|
138
|
+
type: param.location,
|
|
139
|
+
key: originalName,
|
|
140
|
+
required: param.required,
|
|
141
|
+
style: param.style,
|
|
142
|
+
explode: param.explode,
|
|
143
|
+
serialization: param.serialization
|
|
144
|
+
});
|
|
145
|
+
} else {
|
|
146
|
+
params.forEach((param, index) => {
|
|
147
|
+
const inputKey = this.namingStrategy.conflictResolver(originalName, param.location, index);
|
|
148
|
+
properties[inputKey] = this.buildParameterSchema(param);
|
|
149
|
+
if (param.required) {
|
|
150
|
+
required.push(inputKey);
|
|
151
|
+
}
|
|
152
|
+
mapper.push({
|
|
153
|
+
inputKey,
|
|
154
|
+
type: param.location,
|
|
155
|
+
key: originalName,
|
|
156
|
+
required: param.required,
|
|
157
|
+
style: param.style,
|
|
158
|
+
explode: param.explode,
|
|
159
|
+
serialization: param.serialization
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (securityRequirements && securityRequirements.length > 0) {
|
|
165
|
+
this.processSecurityRequirements(
|
|
166
|
+
securityRequirements,
|
|
167
|
+
properties,
|
|
168
|
+
required,
|
|
169
|
+
mapper,
|
|
170
|
+
includeSecurityInInput ?? false
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
const inputSchema = {
|
|
174
|
+
type: "object",
|
|
175
|
+
properties,
|
|
176
|
+
...required.length > 0 && { required },
|
|
177
|
+
additionalProperties: false
|
|
178
|
+
};
|
|
179
|
+
return { inputSchema, mapper };
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Extract parameters from request body schema
|
|
183
|
+
*/
|
|
184
|
+
extractBodyParameters(schema, parametersByName, required, contentType, prefix = "") {
|
|
185
|
+
if (!schema || typeof schema !== "object") return;
|
|
186
|
+
const jsonSchema = toJsonSchema(schema);
|
|
187
|
+
if (jsonSchema.type === "object" && jsonSchema.properties) {
|
|
188
|
+
const requiredFields = new Set(jsonSchema.required ?? []);
|
|
189
|
+
for (const [propName, propSchema] of Object.entries(jsonSchema.properties)) {
|
|
190
|
+
const fullName = prefix ? `${prefix}.${propName}` : propName;
|
|
191
|
+
const isRequired = required && requiredFields.has(propName);
|
|
192
|
+
if (typeof propSchema === "object") {
|
|
193
|
+
const info = {
|
|
194
|
+
name: fullName,
|
|
195
|
+
location: "body",
|
|
196
|
+
required: isRequired,
|
|
197
|
+
schema: propSchema,
|
|
198
|
+
description: propSchema.description,
|
|
199
|
+
serialization: {
|
|
200
|
+
contentType
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
if (!parametersByName.has(fullName)) {
|
|
204
|
+
parametersByName.set(fullName, []);
|
|
205
|
+
}
|
|
206
|
+
parametersByName.get(fullName).push(info);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
const bodyParamName = prefix || "body";
|
|
211
|
+
const info = {
|
|
212
|
+
name: bodyParamName,
|
|
213
|
+
location: "body",
|
|
214
|
+
required,
|
|
215
|
+
schema,
|
|
216
|
+
serialization: {
|
|
217
|
+
contentType
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
if (!parametersByName.has(bodyParamName)) {
|
|
221
|
+
parametersByName.set(bodyParamName, []);
|
|
222
|
+
}
|
|
223
|
+
parametersByName.get(bodyParamName).push(info);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Build JSON Schema for a parameter
|
|
228
|
+
*/
|
|
229
|
+
buildParameterSchema(param) {
|
|
230
|
+
const schema = toJsonSchema(param.schema);
|
|
231
|
+
if (param.description) {
|
|
232
|
+
schema.description = param.description;
|
|
233
|
+
}
|
|
234
|
+
if (param.deprecated) {
|
|
235
|
+
schema["deprecated"] = true;
|
|
236
|
+
}
|
|
237
|
+
schema["x-parameter-location"] = param.location;
|
|
238
|
+
if (param.style) {
|
|
239
|
+
schema["x-parameter-style"] = param.style;
|
|
240
|
+
}
|
|
241
|
+
if (param.explode !== void 0) {
|
|
242
|
+
schema["x-parameter-explode"] = param.explode;
|
|
243
|
+
}
|
|
244
|
+
return schema;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Select the most appropriate content type
|
|
248
|
+
*/
|
|
249
|
+
selectContentType(content) {
|
|
250
|
+
const preferences = [
|
|
251
|
+
"application/json",
|
|
252
|
+
"application/x-www-form-urlencoded",
|
|
253
|
+
"multipart/form-data",
|
|
254
|
+
"application/xml",
|
|
255
|
+
"text/plain"
|
|
256
|
+
];
|
|
257
|
+
for (const pref of preferences) {
|
|
258
|
+
if (content[pref]) return pref;
|
|
259
|
+
}
|
|
260
|
+
const firstKey = Object.keys(content)[0];
|
|
261
|
+
if (!firstKey) {
|
|
262
|
+
throw new Error("No content type available in request body");
|
|
263
|
+
}
|
|
264
|
+
return firstKey;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Process security requirements and add to mapper/inputSchema
|
|
268
|
+
*/
|
|
269
|
+
processSecurityRequirements(securityRequirements, properties, required, mapper, includeInInput) {
|
|
270
|
+
for (const secReq of securityRequirements) {
|
|
271
|
+
const { scheme, type, name: apiKeyName, in: apiKeyIn, scopes } = secReq;
|
|
272
|
+
const securityInfo = {
|
|
273
|
+
scheme,
|
|
274
|
+
type,
|
|
275
|
+
scopes
|
|
276
|
+
};
|
|
277
|
+
let inputKey;
|
|
278
|
+
let headerKey;
|
|
279
|
+
let paramLocation;
|
|
280
|
+
let description;
|
|
281
|
+
let schema;
|
|
282
|
+
if (type === "http") {
|
|
283
|
+
inputKey = scheme;
|
|
284
|
+
headerKey = "Authorization";
|
|
285
|
+
paramLocation = "header";
|
|
286
|
+
const httpScheme = "httpScheme" in secReq && secReq.httpScheme ? secReq.httpScheme : "bearer";
|
|
287
|
+
const bearerFormat = "bearerFormat" in secReq ? secReq.bearerFormat : void 0;
|
|
288
|
+
securityInfo.httpScheme = httpScheme;
|
|
289
|
+
if (bearerFormat) {
|
|
290
|
+
securityInfo.bearerFormat = bearerFormat;
|
|
291
|
+
}
|
|
292
|
+
description = `${httpScheme.charAt(0).toUpperCase()}${httpScheme.slice(1)} authentication token`;
|
|
293
|
+
if (bearerFormat) {
|
|
294
|
+
description += ` (${bearerFormat})`;
|
|
295
|
+
}
|
|
296
|
+
schema = {
|
|
297
|
+
type: "string",
|
|
298
|
+
description
|
|
299
|
+
};
|
|
300
|
+
} else if (type === "apiKey") {
|
|
301
|
+
inputKey = scheme;
|
|
302
|
+
headerKey = apiKeyName || "X-API-Key";
|
|
303
|
+
paramLocation = apiKeyIn || "header";
|
|
304
|
+
securityInfo.apiKeyName = apiKeyName;
|
|
305
|
+
securityInfo.apiKeyIn = apiKeyIn;
|
|
306
|
+
description = `API key for ${scheme}`;
|
|
307
|
+
schema = {
|
|
308
|
+
type: "string",
|
|
309
|
+
description
|
|
310
|
+
};
|
|
311
|
+
} else if (type === "oauth2" || type === "openIdConnect") {
|
|
312
|
+
inputKey = scheme;
|
|
313
|
+
headerKey = "Authorization";
|
|
314
|
+
paramLocation = "header";
|
|
315
|
+
description = `OAuth2 access token${scopes && scopes.length > 0 ? ` (scopes: ${scopes.join(", ")})` : ""}`;
|
|
316
|
+
schema = {
|
|
317
|
+
type: "string",
|
|
318
|
+
description
|
|
319
|
+
};
|
|
320
|
+
} else {
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
mapper.push({
|
|
324
|
+
inputKey,
|
|
325
|
+
type: paramLocation,
|
|
326
|
+
key: headerKey,
|
|
327
|
+
required: true,
|
|
328
|
+
security: securityInfo
|
|
329
|
+
});
|
|
330
|
+
if (includeInInput) {
|
|
331
|
+
properties[inputKey] = schema;
|
|
332
|
+
required.push(inputKey);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// libs/mcp-from-openapi/src/response-builder.ts
|
|
339
|
+
var ResponseBuilder = class {
|
|
340
|
+
preferredStatusCodes;
|
|
341
|
+
includeAllResponses;
|
|
342
|
+
constructor(options = {}) {
|
|
343
|
+
this.preferredStatusCodes = options.preferredStatusCodes ?? [200, 201, 204, 202, 203, 206];
|
|
344
|
+
this.includeAllResponses = options.includeAllResponses ?? true;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Build output schema from responses
|
|
348
|
+
*/
|
|
349
|
+
build(responses) {
|
|
350
|
+
if (!responses || Object.keys(responses).length === 0) {
|
|
351
|
+
return void 0;
|
|
352
|
+
}
|
|
353
|
+
const schemas = this.extractResponseSchemas(responses);
|
|
354
|
+
if (schemas.length === 0) {
|
|
355
|
+
return void 0;
|
|
356
|
+
}
|
|
357
|
+
if (schemas.length === 1) {
|
|
358
|
+
return schemas[0].schema;
|
|
359
|
+
}
|
|
360
|
+
if (this.includeAllResponses) {
|
|
361
|
+
return {
|
|
362
|
+
oneOf: schemas.map((s) => s.schema),
|
|
363
|
+
description: "Response can be one of multiple status codes"
|
|
364
|
+
};
|
|
365
|
+
} else {
|
|
366
|
+
const preferred = this.selectPreferredSchema(schemas);
|
|
367
|
+
return preferred.schema;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Extract schemas from all responses
|
|
372
|
+
*/
|
|
373
|
+
extractResponseSchemas(responses) {
|
|
374
|
+
const schemas = [];
|
|
375
|
+
for (const [statusCode, response] of Object.entries(responses)) {
|
|
376
|
+
if (isReferenceObject(response)) continue;
|
|
377
|
+
if (statusCode === "default") continue;
|
|
378
|
+
const code = parseInt(statusCode, 10);
|
|
379
|
+
if (isNaN(code)) continue;
|
|
380
|
+
const schema = this.extractResponseSchema(response, code);
|
|
381
|
+
if (schema) {
|
|
382
|
+
schemas.push(schema);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (schemas.length === 0 && responses["default"]) {
|
|
386
|
+
const defaultResponse = responses["default"];
|
|
387
|
+
if (!isReferenceObject(defaultResponse)) {
|
|
388
|
+
const schema = this.extractResponseSchema(defaultResponse, 0);
|
|
389
|
+
if (schema) {
|
|
390
|
+
schemas.push(schema);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return schemas;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Extract schema from a single response
|
|
398
|
+
*/
|
|
399
|
+
extractResponseSchema(response, statusCode) {
|
|
400
|
+
if (!response.content) {
|
|
401
|
+
return {
|
|
402
|
+
statusCode,
|
|
403
|
+
schema: {
|
|
404
|
+
type: "null",
|
|
405
|
+
description: response.description,
|
|
406
|
+
"x-status-code": statusCode
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
const contentType = this.selectContentType(response.content);
|
|
411
|
+
const mediaType = response.content[contentType];
|
|
412
|
+
if (!mediaType?.schema) {
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
const schema = {
|
|
416
|
+
...toJsonSchema(mediaType.schema),
|
|
417
|
+
"x-status-code": statusCode
|
|
418
|
+
};
|
|
419
|
+
if (!schema.description && response.description) {
|
|
420
|
+
schema.description = response.description;
|
|
421
|
+
}
|
|
422
|
+
schema["x-content-type"] = contentType;
|
|
423
|
+
return { statusCode, schema };
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Select the most appropriate content type
|
|
427
|
+
*/
|
|
428
|
+
selectContentType(content) {
|
|
429
|
+
const preferences = [
|
|
430
|
+
"application/json",
|
|
431
|
+
"application/hal+json",
|
|
432
|
+
"application/problem+json",
|
|
433
|
+
"application/xml",
|
|
434
|
+
"text/plain",
|
|
435
|
+
"text/html"
|
|
436
|
+
];
|
|
437
|
+
for (const pref of preferences) {
|
|
438
|
+
if (content[pref]) return pref;
|
|
439
|
+
}
|
|
440
|
+
return Object.keys(content)[0];
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Select the preferred schema based on status code preferences
|
|
444
|
+
*/
|
|
445
|
+
selectPreferredSchema(schemas) {
|
|
446
|
+
for (const preferredCode of this.preferredStatusCodes) {
|
|
447
|
+
const found = schemas.find((s) => s.statusCode === preferredCode);
|
|
448
|
+
if (found) return found;
|
|
449
|
+
}
|
|
450
|
+
const success = schemas.find((s) => s.statusCode >= 200 && s.statusCode < 300);
|
|
451
|
+
if (success) return success;
|
|
452
|
+
const redirect = schemas.find((s) => s.statusCode >= 300 && s.statusCode < 400);
|
|
453
|
+
if (redirect) return redirect;
|
|
454
|
+
return schemas[0];
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
// libs/mcp-from-openapi/src/validator.ts
|
|
459
|
+
var Validator = class {
|
|
460
|
+
/**
|
|
461
|
+
* Validate an OpenAPI document
|
|
462
|
+
*/
|
|
463
|
+
async validate(document) {
|
|
464
|
+
const errors = [];
|
|
465
|
+
const warnings = [];
|
|
466
|
+
if (!document.openapi) {
|
|
467
|
+
errors.push({
|
|
468
|
+
message: "Missing required field: openapi",
|
|
469
|
+
path: "/openapi",
|
|
470
|
+
code: "MISSING_OPENAPI_VERSION"
|
|
471
|
+
});
|
|
472
|
+
} else if (!this.isValidOpenAPIVersion(document.openapi)) {
|
|
473
|
+
errors.push({
|
|
474
|
+
message: `Unsupported OpenAPI version: ${document.openapi}. Expected 3.0.x or 3.1.x`,
|
|
475
|
+
path: "/openapi",
|
|
476
|
+
code: "INVALID_OPENAPI_VERSION"
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
if (!document.info) {
|
|
480
|
+
errors.push({
|
|
481
|
+
message: "Missing required field: info",
|
|
482
|
+
path: "/info",
|
|
483
|
+
code: "MISSING_INFO"
|
|
484
|
+
});
|
|
485
|
+
} else {
|
|
486
|
+
if (!document.info.title) {
|
|
487
|
+
errors.push({
|
|
488
|
+
message: "Missing required field: info.title",
|
|
489
|
+
path: "/info/title",
|
|
490
|
+
code: "MISSING_TITLE"
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
if (!document.info.version) {
|
|
494
|
+
errors.push({
|
|
495
|
+
message: "Missing required field: info.version",
|
|
496
|
+
path: "/info/version",
|
|
497
|
+
code: "MISSING_VERSION"
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
if (!document.paths || Object.keys(document.paths).length === 0) {
|
|
502
|
+
warnings.push({
|
|
503
|
+
message: "No paths defined in OpenAPI document",
|
|
504
|
+
path: "/paths",
|
|
505
|
+
code: "NO_PATHS"
|
|
506
|
+
});
|
|
507
|
+
} else {
|
|
508
|
+
this.validatePaths(document.paths, errors, warnings);
|
|
509
|
+
}
|
|
510
|
+
if (!document.servers || document.servers.length === 0) {
|
|
511
|
+
warnings.push({
|
|
512
|
+
message: "No servers defined. You may need to provide a baseUrl option.",
|
|
513
|
+
path: "/servers",
|
|
514
|
+
code: "NO_SERVERS"
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
if (document.security && !document.components?.securitySchemes) {
|
|
518
|
+
warnings.push({
|
|
519
|
+
message: "Security requirements defined but no security schemes found",
|
|
520
|
+
path: "/security",
|
|
521
|
+
code: "NO_SECURITY_SCHEMES"
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
return {
|
|
525
|
+
valid: errors.length === 0,
|
|
526
|
+
errors: errors.length > 0 ? errors : void 0,
|
|
527
|
+
warnings: warnings.length > 0 ? warnings : void 0
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Check if OpenAPI version is valid
|
|
532
|
+
*/
|
|
533
|
+
isValidOpenAPIVersion(version) {
|
|
534
|
+
return /^3\.[01]\.\d+$/.test(version);
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Validate paths
|
|
538
|
+
*/
|
|
539
|
+
validatePaths(paths, errors, warnings) {
|
|
540
|
+
for (const [path2, pathItem] of Object.entries(paths)) {
|
|
541
|
+
if (!pathItem) continue;
|
|
542
|
+
if (!path2.startsWith("/")) {
|
|
543
|
+
errors.push({
|
|
544
|
+
message: `Path must start with '/': ${path2}`,
|
|
545
|
+
path: `/paths/${path2}`,
|
|
546
|
+
code: "INVALID_PATH_FORMAT"
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
const methods = ["get", "post", "put", "patch", "delete", "head", "options", "trace"];
|
|
550
|
+
let hasOperations = false;
|
|
551
|
+
for (const method of methods) {
|
|
552
|
+
const operation = pathItem[method];
|
|
553
|
+
if (operation) {
|
|
554
|
+
hasOperations = true;
|
|
555
|
+
this.validateOperation(operation, path2, method, errors, warnings);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
if (!hasOperations && !pathItem.$ref) {
|
|
559
|
+
warnings.push({
|
|
560
|
+
message: `Path has no operations: ${path2}`,
|
|
561
|
+
path: `/paths/${path2}`,
|
|
562
|
+
code: "NO_OPERATIONS"
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Validate an operation
|
|
569
|
+
*/
|
|
570
|
+
validateOperation(operation, path2, method, errors, warnings) {
|
|
571
|
+
const basePath = `/paths/${path2}/${method}`;
|
|
572
|
+
if (!operation.operationId) {
|
|
573
|
+
warnings.push({
|
|
574
|
+
message: `Operation missing operationId: ${method.toUpperCase()} ${path2}`,
|
|
575
|
+
path: `${basePath}/operationId`,
|
|
576
|
+
code: "NO_OPERATION_ID"
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
if (!operation.responses || Object.keys(operation.responses).length === 0) {
|
|
580
|
+
errors.push({
|
|
581
|
+
message: `Operation missing responses: ${method.toUpperCase()} ${path2}`,
|
|
582
|
+
path: `${basePath}/responses`,
|
|
583
|
+
code: "NO_RESPONSES"
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
if (operation.parameters) {
|
|
587
|
+
this.validateParameters(operation.parameters, path2, method, errors, warnings);
|
|
588
|
+
}
|
|
589
|
+
const pathParams = path2.match(/\{([^}]+)\}/g)?.map((p) => p.slice(1, -1)) ?? [];
|
|
590
|
+
const definedPathParams = new Set(
|
|
591
|
+
operation.parameters?.filter((p) => p.in === "path").map((p) => p.name) ?? []
|
|
592
|
+
);
|
|
593
|
+
for (const param of pathParams) {
|
|
594
|
+
if (!definedPathParams.has(param)) {
|
|
595
|
+
errors.push({
|
|
596
|
+
message: `Path parameter '${param}' not defined in parameters: ${method.toUpperCase()} ${path2}`,
|
|
597
|
+
path: `${basePath}/parameters`,
|
|
598
|
+
code: "MISSING_PATH_PARAMETER"
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Validate parameters
|
|
605
|
+
*/
|
|
606
|
+
validateParameters(parameters, path2, method, errors, warnings) {
|
|
607
|
+
const basePath = `/paths/${path2}/${method}/parameters`;
|
|
608
|
+
for (let i = 0; i < parameters.length; i++) {
|
|
609
|
+
const param = parameters[i];
|
|
610
|
+
const paramPath = `${basePath}/${i}`;
|
|
611
|
+
if (!param.name) {
|
|
612
|
+
errors.push({
|
|
613
|
+
message: "Parameter missing name",
|
|
614
|
+
path: `${paramPath}/name`,
|
|
615
|
+
code: "MISSING_PARAMETER_NAME"
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
if (!param.in) {
|
|
619
|
+
errors.push({
|
|
620
|
+
message: 'Parameter missing "in" field',
|
|
621
|
+
path: `${paramPath}/in`,
|
|
622
|
+
code: "MISSING_PARAMETER_IN"
|
|
623
|
+
});
|
|
624
|
+
} else if (!["path", "query", "header", "cookie"].includes(param.in)) {
|
|
625
|
+
errors.push({
|
|
626
|
+
message: `Invalid parameter location: ${param.in}`,
|
|
627
|
+
path: `${paramPath}/in`,
|
|
628
|
+
code: "INVALID_PARAMETER_IN"
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
if (param.in === "path" && !param.required) {
|
|
632
|
+
errors.push({
|
|
633
|
+
message: `Path parameter '${param.name}' must be required`,
|
|
634
|
+
path: `${paramPath}/required`,
|
|
635
|
+
code: "PATH_PARAMETER_NOT_REQUIRED"
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
if (!param.schema && !param.content) {
|
|
639
|
+
errors.push({
|
|
640
|
+
message: `Parameter '${param.name}' missing schema or content`,
|
|
641
|
+
path: `${paramPath}`,
|
|
642
|
+
code: "MISSING_PARAMETER_SCHEMA"
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
// libs/mcp-from-openapi/src/errors.ts
|
|
650
|
+
var OpenAPIToolError = class extends Error {
|
|
651
|
+
context;
|
|
652
|
+
constructor(message, context) {
|
|
653
|
+
super(message);
|
|
654
|
+
this.name = this.constructor.name;
|
|
655
|
+
this.context = context;
|
|
656
|
+
if (Error.captureStackTrace) {
|
|
657
|
+
Error.captureStackTrace(this, this.constructor);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
var LoadError = class extends OpenAPIToolError {
|
|
662
|
+
constructor(message, context) {
|
|
663
|
+
super(message, context);
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
var ParseError = class extends OpenAPIToolError {
|
|
667
|
+
constructor(message, context) {
|
|
668
|
+
super(message, context);
|
|
669
|
+
}
|
|
670
|
+
};
|
|
671
|
+
var ValidationError = class extends OpenAPIToolError {
|
|
672
|
+
errors;
|
|
673
|
+
constructor(message, context) {
|
|
674
|
+
super(message, context);
|
|
675
|
+
this.errors = context?.["errors"];
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
var GenerationError = class extends OpenAPIToolError {
|
|
679
|
+
constructor(message, context) {
|
|
680
|
+
super(message, context);
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
var SchemaError = class extends OpenAPIToolError {
|
|
684
|
+
constructor(message, context) {
|
|
685
|
+
super(message, context);
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
// libs/mcp-from-openapi/src/generator.ts
|
|
690
|
+
var OpenAPIToolGenerator = class _OpenAPIToolGenerator {
|
|
691
|
+
document;
|
|
692
|
+
dereferencedDocument;
|
|
693
|
+
options;
|
|
694
|
+
/**
|
|
695
|
+
* Private constructor - use static factory methods to create instances
|
|
696
|
+
*/
|
|
697
|
+
constructor(document, options = {}) {
|
|
698
|
+
this.document = document;
|
|
699
|
+
this.options = {
|
|
700
|
+
dereference: options.dereference ?? true,
|
|
701
|
+
baseUrl: options.baseUrl ?? "",
|
|
702
|
+
headers: options.headers ?? {},
|
|
703
|
+
timeout: options.timeout ?? 3e4,
|
|
704
|
+
validate: options.validate ?? true,
|
|
705
|
+
followRedirects: options.followRedirects ?? true
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Create generator from a URL
|
|
710
|
+
*/
|
|
711
|
+
static async fromURL(url, options = {}) {
|
|
712
|
+
try {
|
|
713
|
+
const controller = new AbortController();
|
|
714
|
+
const timeout = setTimeout(() => controller.abort(), options.timeout ?? 3e4);
|
|
715
|
+
const response = await fetch(url, {
|
|
716
|
+
headers: options.headers,
|
|
717
|
+
signal: controller.signal,
|
|
718
|
+
redirect: options.followRedirects ?? true ? "follow" : "manual"
|
|
719
|
+
});
|
|
720
|
+
clearTimeout(timeout);
|
|
721
|
+
if (!response.ok) {
|
|
722
|
+
throw new LoadError(`Failed to fetch OpenAPI spec from URL: ${response.status} ${response.statusText}`, {
|
|
723
|
+
url,
|
|
724
|
+
status: response.status
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
const contentType = response.headers.get("content-type") || "";
|
|
728
|
+
const text = await response.text();
|
|
729
|
+
let document;
|
|
730
|
+
if (contentType.includes("yaml") || contentType.includes("yml") || url.match(/\.ya?ml$/i)) {
|
|
731
|
+
document = yaml.parse(text);
|
|
732
|
+
} else {
|
|
733
|
+
document = JSON.parse(text);
|
|
734
|
+
}
|
|
735
|
+
return new _OpenAPIToolGenerator(document, options);
|
|
736
|
+
} catch (error) {
|
|
737
|
+
if (error instanceof LoadError) {
|
|
738
|
+
throw error;
|
|
739
|
+
}
|
|
740
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
741
|
+
throw new LoadError(`Failed to load OpenAPI spec from URL: ${errorMessage}`, {
|
|
742
|
+
url,
|
|
743
|
+
originalError: error
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Create generator from a file path
|
|
749
|
+
*/
|
|
750
|
+
static async fromFile(filePath, options = {}) {
|
|
751
|
+
try {
|
|
752
|
+
const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
|
|
753
|
+
const content = await fs.readFile(absolutePath, "utf-8");
|
|
754
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
755
|
+
let document;
|
|
756
|
+
if (ext === ".yaml" || ext === ".yml") {
|
|
757
|
+
document = yaml.parse(content);
|
|
758
|
+
} else if (ext === ".json") {
|
|
759
|
+
document = JSON.parse(content);
|
|
760
|
+
} else {
|
|
761
|
+
try {
|
|
762
|
+
document = JSON.parse(content);
|
|
763
|
+
} catch {
|
|
764
|
+
document = yaml.parse(content);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
return new _OpenAPIToolGenerator(document, options);
|
|
768
|
+
} catch (error) {
|
|
769
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
770
|
+
throw new LoadError(`Failed to load OpenAPI spec from file: ${errorMessage}`, {
|
|
771
|
+
filePath,
|
|
772
|
+
originalError: error
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Create generator from a YAML string
|
|
778
|
+
*/
|
|
779
|
+
static async fromYAML(yamlString, options = {}) {
|
|
780
|
+
try {
|
|
781
|
+
const document = yaml.parse(yamlString);
|
|
782
|
+
return new _OpenAPIToolGenerator(document, options);
|
|
783
|
+
} catch (error) {
|
|
784
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
785
|
+
throw new ParseError(`Failed to parse YAML: ${errorMessage}`, {
|
|
786
|
+
originalError: error
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Create generator from a JSON object
|
|
792
|
+
*/
|
|
793
|
+
static async fromJSON(json, options = {}) {
|
|
794
|
+
const document = JSON.parse(JSON.stringify(json));
|
|
795
|
+
return new _OpenAPIToolGenerator(document, options);
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Get the OpenAPI document
|
|
799
|
+
*/
|
|
800
|
+
getDocument() {
|
|
801
|
+
return this.dereferencedDocument ?? this.document;
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Validate the OpenAPI document
|
|
805
|
+
*/
|
|
806
|
+
async validate() {
|
|
807
|
+
const validator = new Validator();
|
|
808
|
+
return validator.validate(this.document);
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Initialize the generator (dereference if needed)
|
|
812
|
+
*/
|
|
813
|
+
async initialize() {
|
|
814
|
+
if (this.options.validate) {
|
|
815
|
+
const result = await this.validate();
|
|
816
|
+
if (!result.valid) {
|
|
817
|
+
throw new ParseError("Invalid OpenAPI document", { errors: result.errors });
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
if (this.options.dereference && !this.dereferencedDocument) {
|
|
821
|
+
try {
|
|
822
|
+
this.dereferencedDocument = await $RefParser.dereference(
|
|
823
|
+
JSON.parse(JSON.stringify(this.document))
|
|
824
|
+
);
|
|
825
|
+
} catch (error) {
|
|
826
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
827
|
+
throw new ParseError(`Failed to dereference OpenAPI document: ${errorMessage}`, {
|
|
828
|
+
originalError: error
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Generate all tools from the OpenAPI specification
|
|
835
|
+
*/
|
|
836
|
+
async generateTools(options = {}) {
|
|
837
|
+
await this.initialize();
|
|
838
|
+
const document = this.getDocument();
|
|
839
|
+
const tools = [];
|
|
840
|
+
if (!document.paths) {
|
|
841
|
+
return tools;
|
|
842
|
+
}
|
|
843
|
+
for (const [pathStr, pathItem] of Object.entries(document.paths)) {
|
|
844
|
+
if (!pathItem || "$ref" in pathItem) continue;
|
|
845
|
+
const methods = ["get", "post", "put", "patch", "delete", "head", "options", "trace"];
|
|
846
|
+
for (const method of methods) {
|
|
847
|
+
const operation = pathItem[method];
|
|
848
|
+
if (!operation) continue;
|
|
849
|
+
if (!this.shouldIncludeOperation(operation, pathStr, method, options)) {
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
try {
|
|
853
|
+
const tool = await this.generateTool(pathStr, method, options);
|
|
854
|
+
tools.push(tool);
|
|
855
|
+
} catch (error) {
|
|
856
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
857
|
+
console.warn(`Failed to generate tool for ${method.toUpperCase()} ${pathStr}:`, errorMessage);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
return tools;
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Generate a specific tool for a path and method
|
|
865
|
+
*/
|
|
866
|
+
async generateTool(pathStr, method, options = {}) {
|
|
867
|
+
await this.initialize();
|
|
868
|
+
const document = this.getDocument();
|
|
869
|
+
if (!document.paths) {
|
|
870
|
+
throw new Error("No paths defined in OpenAPI document");
|
|
871
|
+
}
|
|
872
|
+
const pathItem = document.paths[pathStr];
|
|
873
|
+
const operation = pathItem?.[method.toLowerCase()];
|
|
874
|
+
if (!operation) {
|
|
875
|
+
throw new Error(`Operation not found: ${method.toUpperCase()} ${pathStr}`);
|
|
876
|
+
}
|
|
877
|
+
const parameterResolver = new ParameterResolver(options.namingStrategy);
|
|
878
|
+
let pathParameters = void 0;
|
|
879
|
+
if (pathItem.parameters) {
|
|
880
|
+
pathParameters = pathItem.parameters.filter(
|
|
881
|
+
(p) => !isReferenceObject(p)
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
let securityRequirements = void 0;
|
|
885
|
+
const securitySpec = operation.security ?? document.security;
|
|
886
|
+
if (securitySpec) {
|
|
887
|
+
securityRequirements = this.extractSecurityRequirements(securitySpec, document);
|
|
888
|
+
}
|
|
889
|
+
const { inputSchema, mapper } = parameterResolver.resolve(
|
|
890
|
+
operation,
|
|
891
|
+
pathParameters,
|
|
892
|
+
securityRequirements,
|
|
893
|
+
options.includeSecurityInInput
|
|
894
|
+
);
|
|
895
|
+
const responseBuilder = new ResponseBuilder(options);
|
|
896
|
+
const outputSchema = responseBuilder.build(operation.responses);
|
|
897
|
+
const name = this.generateToolName(pathStr, method, operation.operationId, options);
|
|
898
|
+
const description = operation.summary || operation.description || `${method.toUpperCase()} ${pathStr}`;
|
|
899
|
+
const metadata = this.extractMetadata(pathStr, method, operation, document, outputSchema);
|
|
900
|
+
return {
|
|
901
|
+
name,
|
|
902
|
+
description,
|
|
903
|
+
inputSchema,
|
|
904
|
+
outputSchema,
|
|
905
|
+
mapper,
|
|
906
|
+
metadata
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Check if an operation should be included
|
|
911
|
+
*/
|
|
912
|
+
shouldIncludeOperation(operation, path2, method, options) {
|
|
913
|
+
if (operation.deprecated && !options.includeDeprecated) {
|
|
914
|
+
return false;
|
|
915
|
+
}
|
|
916
|
+
if (options.includeOperations && operation.operationId) {
|
|
917
|
+
if (!options.includeOperations.includes(operation.operationId)) {
|
|
918
|
+
return false;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
if (options.excludeOperations && operation.operationId) {
|
|
922
|
+
if (options.excludeOperations.includes(operation.operationId)) {
|
|
923
|
+
return false;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
if (options.filterFn) {
|
|
927
|
+
return options.filterFn({
|
|
928
|
+
...operation,
|
|
929
|
+
path: path2,
|
|
930
|
+
method
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
return true;
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Generate a tool name
|
|
937
|
+
*/
|
|
938
|
+
generateToolName(path2, method, operationId, options = {}) {
|
|
939
|
+
if (options.namingStrategy?.toolNameGenerator) {
|
|
940
|
+
return options.namingStrategy.toolNameGenerator(path2, method, operationId);
|
|
941
|
+
}
|
|
942
|
+
if (operationId) {
|
|
943
|
+
return operationId;
|
|
944
|
+
}
|
|
945
|
+
const sanitized = path2.replace(/\{([^}]+)\}/g, "By_$1").replace(/[^a-zA-Z0-9_]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
|
|
946
|
+
return `${method}_${sanitized}`;
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Extract metadata from operation
|
|
950
|
+
*/
|
|
951
|
+
extractMetadata(path2, method, operation, document, outputSchema) {
|
|
952
|
+
const metadata = {
|
|
953
|
+
path: path2,
|
|
954
|
+
method,
|
|
955
|
+
operationId: operation.operationId,
|
|
956
|
+
operationSummary: operation.summary,
|
|
957
|
+
operationDescription: operation.description,
|
|
958
|
+
tags: operation.tags,
|
|
959
|
+
deprecated: operation.deprecated
|
|
960
|
+
};
|
|
961
|
+
if (operation.security || document.security) {
|
|
962
|
+
metadata.security = this.extractSecurityRequirements(
|
|
963
|
+
operation.security ?? document.security,
|
|
964
|
+
document
|
|
965
|
+
);
|
|
966
|
+
}
|
|
967
|
+
const servers = operation.servers ?? document.servers;
|
|
968
|
+
if (servers) {
|
|
969
|
+
metadata.servers = servers.map((server) => ({
|
|
970
|
+
url: this.options.baseUrl || server.url,
|
|
971
|
+
description: server.description,
|
|
972
|
+
variables: server.variables
|
|
973
|
+
}));
|
|
974
|
+
} else if (this.options.baseUrl) {
|
|
975
|
+
metadata.servers = [{ url: this.options.baseUrl }];
|
|
976
|
+
}
|
|
977
|
+
const schemaObj = outputSchema;
|
|
978
|
+
if (schemaObj && Array.isArray(schemaObj["oneOf"])) {
|
|
979
|
+
const codes = schemaObj["oneOf"].map((schema) => schema["x-status-code"]).filter((code) => code !== void 0 && code !== null);
|
|
980
|
+
if (codes.length > 0) {
|
|
981
|
+
metadata.responseStatusCodes = codes;
|
|
982
|
+
}
|
|
983
|
+
} else if (schemaObj && schemaObj["x-status-code"] !== void 0 && schemaObj["x-status-code"] !== null) {
|
|
984
|
+
metadata.responseStatusCodes = [schemaObj["x-status-code"]];
|
|
985
|
+
}
|
|
986
|
+
if (operation.externalDocs) {
|
|
987
|
+
metadata.externalDocs = operation.externalDocs;
|
|
988
|
+
}
|
|
989
|
+
const operationWithExt = operation;
|
|
990
|
+
if (operationWithExt["x-frontmcp"]) {
|
|
991
|
+
metadata.frontmcp = operationWithExt["x-frontmcp"];
|
|
992
|
+
}
|
|
993
|
+
return metadata;
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Extract security requirements
|
|
997
|
+
*/
|
|
998
|
+
extractSecurityRequirements(security, document) {
|
|
999
|
+
if (!security || !document.components?.securitySchemes) {
|
|
1000
|
+
return [];
|
|
1001
|
+
}
|
|
1002
|
+
return security.flatMap(
|
|
1003
|
+
(req) => Object.entries(req).map(([scheme, scopes]) => {
|
|
1004
|
+
const securityScheme = document.components.securitySchemes[scheme];
|
|
1005
|
+
if (isReferenceObject(securityScheme)) {
|
|
1006
|
+
return { scheme, type: "http", scopes };
|
|
1007
|
+
}
|
|
1008
|
+
const apiKeyIn = "in" in securityScheme ? securityScheme.in : void 0;
|
|
1009
|
+
const result = {
|
|
1010
|
+
scheme,
|
|
1011
|
+
type: securityScheme.type,
|
|
1012
|
+
scopes,
|
|
1013
|
+
name: "name" in securityScheme ? securityScheme.name : void 0,
|
|
1014
|
+
in: apiKeyIn && (apiKeyIn === "query" || apiKeyIn === "header" || apiKeyIn === "cookie") ? apiKeyIn : void 0
|
|
1015
|
+
};
|
|
1016
|
+
if (securityScheme.type === "http") {
|
|
1017
|
+
result.httpScheme = "scheme" in securityScheme ? securityScheme.scheme : void 0;
|
|
1018
|
+
result.bearerFormat = "bearerFormat" in securityScheme ? securityScheme.bearerFormat : void 0;
|
|
1019
|
+
}
|
|
1020
|
+
result.description = "description" in securityScheme ? securityScheme.description : void 0;
|
|
1021
|
+
return result;
|
|
1022
|
+
})
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
|
|
1027
|
+
// libs/mcp-from-openapi/src/schema-builder.ts
|
|
1028
|
+
var SchemaBuilder = class {
|
|
1029
|
+
/**
|
|
1030
|
+
* Merge multiple schemas into one
|
|
1031
|
+
*/
|
|
1032
|
+
static merge(schemas) {
|
|
1033
|
+
if (schemas.length === 0) {
|
|
1034
|
+
return { type: "object" };
|
|
1035
|
+
}
|
|
1036
|
+
if (schemas.length === 1) {
|
|
1037
|
+
return schemas[0];
|
|
1038
|
+
}
|
|
1039
|
+
const merged = {
|
|
1040
|
+
type: "object",
|
|
1041
|
+
properties: {},
|
|
1042
|
+
required: []
|
|
1043
|
+
};
|
|
1044
|
+
const allRequired = /* @__PURE__ */ new Set();
|
|
1045
|
+
for (const schema of schemas) {
|
|
1046
|
+
if (schema.properties) {
|
|
1047
|
+
merged.properties = {
|
|
1048
|
+
...merged.properties,
|
|
1049
|
+
...schema.properties
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
if (schema.required) {
|
|
1053
|
+
schema.required.forEach((field) => allRequired.add(field));
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
if (allRequired.size > 0) {
|
|
1057
|
+
merged.required = Array.from(allRequired);
|
|
1058
|
+
}
|
|
1059
|
+
return merged;
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Create a union schema (oneOf)
|
|
1063
|
+
*/
|
|
1064
|
+
static union(schemas) {
|
|
1065
|
+
if (schemas.length === 0) {
|
|
1066
|
+
return {};
|
|
1067
|
+
}
|
|
1068
|
+
if (schemas.length === 1) {
|
|
1069
|
+
return schemas[0];
|
|
1070
|
+
}
|
|
1071
|
+
return {
|
|
1072
|
+
oneOf: schemas
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* Deep clone a schema
|
|
1077
|
+
*/
|
|
1078
|
+
static clone(schema) {
|
|
1079
|
+
return JSON.parse(JSON.stringify(schema));
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* Remove $ref from schema (assumes already dereferenced)
|
|
1083
|
+
*/
|
|
1084
|
+
static removeRefs(schema) {
|
|
1085
|
+
const cloned = this.clone(schema);
|
|
1086
|
+
this.removeRefsRecursive(cloned);
|
|
1087
|
+
return cloned;
|
|
1088
|
+
}
|
|
1089
|
+
static removeRefsRecursive(obj) {
|
|
1090
|
+
if (!obj || typeof obj !== "object") return;
|
|
1091
|
+
if (obj.$ref) {
|
|
1092
|
+
delete obj.$ref;
|
|
1093
|
+
}
|
|
1094
|
+
for (const key in obj) {
|
|
1095
|
+
if (key in obj) {
|
|
1096
|
+
const value = obj[key];
|
|
1097
|
+
if (value && typeof value === "object") {
|
|
1098
|
+
this.removeRefsRecursive(value);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Add description to schema
|
|
1105
|
+
*/
|
|
1106
|
+
static withDescription(schema, description) {
|
|
1107
|
+
return {
|
|
1108
|
+
...schema,
|
|
1109
|
+
description
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
* Add example to schema
|
|
1114
|
+
*/
|
|
1115
|
+
static withExample(schema, example) {
|
|
1116
|
+
const existingExamples = Array.isArray(schema.examples) ? schema.examples : [];
|
|
1117
|
+
return {
|
|
1118
|
+
...schema,
|
|
1119
|
+
examples: [...existingExamples, example]
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Add default value to schema
|
|
1124
|
+
*/
|
|
1125
|
+
static withDefault(schema, defaultValue) {
|
|
1126
|
+
return {
|
|
1127
|
+
...schema,
|
|
1128
|
+
default: defaultValue
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Add format to schema
|
|
1133
|
+
*/
|
|
1134
|
+
static withFormat(schema, format) {
|
|
1135
|
+
return {
|
|
1136
|
+
...schema,
|
|
1137
|
+
format
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Add pattern to schema
|
|
1142
|
+
*/
|
|
1143
|
+
static withPattern(schema, pattern) {
|
|
1144
|
+
return {
|
|
1145
|
+
...schema,
|
|
1146
|
+
pattern
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Add enum to schema
|
|
1151
|
+
*/
|
|
1152
|
+
static withEnum(schema, values) {
|
|
1153
|
+
return {
|
|
1154
|
+
...schema,
|
|
1155
|
+
enum: values
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
1159
|
+
* Add minimum/maximum constraints
|
|
1160
|
+
*/
|
|
1161
|
+
static withRange(schema, min, max, options = {}) {
|
|
1162
|
+
const result = { ...schema };
|
|
1163
|
+
if (min !== void 0) {
|
|
1164
|
+
if (options.exclusive) {
|
|
1165
|
+
result.exclusiveMinimum = min;
|
|
1166
|
+
} else {
|
|
1167
|
+
result.minimum = min;
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
if (max !== void 0) {
|
|
1171
|
+
if (options.exclusive) {
|
|
1172
|
+
result.exclusiveMaximum = max;
|
|
1173
|
+
} else {
|
|
1174
|
+
result.maximum = max;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
return result;
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Add minLength/maxLength constraints
|
|
1181
|
+
*/
|
|
1182
|
+
static withLength(schema, minLength, maxLength) {
|
|
1183
|
+
const result = { ...schema };
|
|
1184
|
+
if (minLength !== void 0) {
|
|
1185
|
+
result.minLength = minLength;
|
|
1186
|
+
}
|
|
1187
|
+
if (maxLength !== void 0) {
|
|
1188
|
+
result.maxLength = maxLength;
|
|
1189
|
+
}
|
|
1190
|
+
return result;
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Create object schema
|
|
1194
|
+
*/
|
|
1195
|
+
static object(properties, required) {
|
|
1196
|
+
return {
|
|
1197
|
+
type: "object",
|
|
1198
|
+
properties,
|
|
1199
|
+
...required && required.length > 0 && { required },
|
|
1200
|
+
additionalProperties: false
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
/**
|
|
1204
|
+
* Create array schema
|
|
1205
|
+
*/
|
|
1206
|
+
static array(items, constraints) {
|
|
1207
|
+
return {
|
|
1208
|
+
type: "array",
|
|
1209
|
+
items,
|
|
1210
|
+
...constraints
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Create string schema
|
|
1215
|
+
*/
|
|
1216
|
+
static string(constraints) {
|
|
1217
|
+
return {
|
|
1218
|
+
type: "string",
|
|
1219
|
+
...constraints
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
/**
|
|
1223
|
+
* Create number schema
|
|
1224
|
+
*/
|
|
1225
|
+
static number(constraints) {
|
|
1226
|
+
return {
|
|
1227
|
+
type: "number",
|
|
1228
|
+
...constraints
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Create integer schema
|
|
1233
|
+
*/
|
|
1234
|
+
static integer(constraints) {
|
|
1235
|
+
return {
|
|
1236
|
+
type: "integer",
|
|
1237
|
+
...constraints
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Create boolean schema
|
|
1242
|
+
*/
|
|
1243
|
+
static boolean() {
|
|
1244
|
+
return {
|
|
1245
|
+
type: "boolean"
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Create null schema
|
|
1250
|
+
*/
|
|
1251
|
+
static null() {
|
|
1252
|
+
return {
|
|
1253
|
+
type: "null"
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Flatten nested oneOf/anyOf/allOf schemas
|
|
1258
|
+
*/
|
|
1259
|
+
static flatten(schema, maxDepth = 10) {
|
|
1260
|
+
if (maxDepth <= 0) return schema;
|
|
1261
|
+
const cloned = this.clone(schema);
|
|
1262
|
+
if (cloned.oneOf) {
|
|
1263
|
+
const flattened = cloned.oneOf.flatMap((s) => {
|
|
1264
|
+
const sub = this.flatten(s, maxDepth - 1);
|
|
1265
|
+
return sub.oneOf ? sub.oneOf : [sub];
|
|
1266
|
+
});
|
|
1267
|
+
cloned.oneOf = flattened;
|
|
1268
|
+
}
|
|
1269
|
+
if (cloned.anyOf) {
|
|
1270
|
+
const flattened = cloned.anyOf.flatMap((s) => {
|
|
1271
|
+
const sub = this.flatten(s, maxDepth - 1);
|
|
1272
|
+
return sub.anyOf ? sub.anyOf : [sub];
|
|
1273
|
+
});
|
|
1274
|
+
cloned.anyOf = flattened;
|
|
1275
|
+
}
|
|
1276
|
+
if (cloned.allOf) {
|
|
1277
|
+
const flattened = cloned.allOf.flatMap((s) => {
|
|
1278
|
+
const sub = this.flatten(s, maxDepth - 1);
|
|
1279
|
+
return sub.allOf ? sub.allOf : [sub];
|
|
1280
|
+
});
|
|
1281
|
+
cloned.allOf = flattened;
|
|
1282
|
+
}
|
|
1283
|
+
return cloned;
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* Simplify schema by removing unnecessary fields
|
|
1287
|
+
*/
|
|
1288
|
+
static simplify(schema) {
|
|
1289
|
+
const cloned = this.clone(schema);
|
|
1290
|
+
if (Array.isArray(cloned.required) && cloned.required.length === 0) {
|
|
1291
|
+
delete cloned.required;
|
|
1292
|
+
}
|
|
1293
|
+
if (cloned.properties && Object.keys(cloned.properties).length === 0) {
|
|
1294
|
+
delete cloned.properties;
|
|
1295
|
+
}
|
|
1296
|
+
if (Array.isArray(cloned.examples) && cloned.examples.length === 0) {
|
|
1297
|
+
delete cloned.examples;
|
|
1298
|
+
}
|
|
1299
|
+
if (cloned.title && cloned.description && cloned.title === cloned.description) {
|
|
1300
|
+
delete cloned.title;
|
|
1301
|
+
}
|
|
1302
|
+
return cloned;
|
|
1303
|
+
}
|
|
1304
|
+
};
|
|
1305
|
+
|
|
1306
|
+
// libs/mcp-from-openapi/src/security-resolver.ts
|
|
1307
|
+
var SecurityResolver = class {
|
|
1308
|
+
/**
|
|
1309
|
+
* Resolve security parameters from mapper entries
|
|
1310
|
+
*
|
|
1311
|
+
* @param mappers - Parameter mappers from the tool definition
|
|
1312
|
+
* @param context - Security context with auth values or custom resolver
|
|
1313
|
+
* @returns Resolved headers, query params, and cookies with auth applied
|
|
1314
|
+
*/
|
|
1315
|
+
async resolve(mappers, context) {
|
|
1316
|
+
const resolved = {
|
|
1317
|
+
headers: {},
|
|
1318
|
+
query: {},
|
|
1319
|
+
cookies: {}
|
|
1320
|
+
};
|
|
1321
|
+
if (context.clientCertificate) {
|
|
1322
|
+
resolved.clientCertificate = context.clientCertificate;
|
|
1323
|
+
}
|
|
1324
|
+
let requiresSignature = false;
|
|
1325
|
+
let signatureScheme;
|
|
1326
|
+
for (const mapper of mappers) {
|
|
1327
|
+
if (!mapper.security) {
|
|
1328
|
+
continue;
|
|
1329
|
+
}
|
|
1330
|
+
if (this.isSignatureBasedAuth(mapper.security)) {
|
|
1331
|
+
requiresSignature = true;
|
|
1332
|
+
signatureScheme = mapper.security.scheme;
|
|
1333
|
+
continue;
|
|
1334
|
+
}
|
|
1335
|
+
const authValue = await this.resolveAuthValue(mapper.security, context);
|
|
1336
|
+
if (!authValue) {
|
|
1337
|
+
continue;
|
|
1338
|
+
}
|
|
1339
|
+
const headerName = mapper.key;
|
|
1340
|
+
if (mapper.type === "header") {
|
|
1341
|
+
resolved.headers[headerName] = authValue;
|
|
1342
|
+
} else if (mapper.type === "query") {
|
|
1343
|
+
resolved.query[headerName] = authValue;
|
|
1344
|
+
} else if (mapper.type === "cookie") {
|
|
1345
|
+
resolved.cookies[headerName] = authValue;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
if (context.cookies) {
|
|
1349
|
+
resolved.cookies = { ...resolved.cookies, ...context.cookies };
|
|
1350
|
+
}
|
|
1351
|
+
if (requiresSignature) {
|
|
1352
|
+
resolved.requiresSignature = true;
|
|
1353
|
+
resolved.signatureInfo = {
|
|
1354
|
+
scheme: signatureScheme || "unknown"
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
return resolved;
|
|
1358
|
+
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Check if security scheme requires request signing
|
|
1361
|
+
*/
|
|
1362
|
+
isSignatureBasedAuth(security) {
|
|
1363
|
+
const signatureSchemes = ["aws4", "hmac", "signature", "hawk", "custom-signature"];
|
|
1364
|
+
return signatureSchemes.some(
|
|
1365
|
+
(scheme) => security.scheme.toLowerCase().includes(scheme)
|
|
1366
|
+
);
|
|
1367
|
+
}
|
|
1368
|
+
/**
|
|
1369
|
+
* Resolve the actual auth value based on security type
|
|
1370
|
+
*/
|
|
1371
|
+
async resolveAuthValue(security, context) {
|
|
1372
|
+
if (context.customResolver) {
|
|
1373
|
+
const customValue = await context.customResolver(security);
|
|
1374
|
+
if (customValue !== void 0) {
|
|
1375
|
+
return customValue;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
if (security.type === "http") {
|
|
1379
|
+
return this.resolveHttpAuth(security, context);
|
|
1380
|
+
} else if (security.type === "apiKey") {
|
|
1381
|
+
return this.resolveApiKey(security, context);
|
|
1382
|
+
} else if (security.type === "oauth2" || security.type === "openIdConnect") {
|
|
1383
|
+
return this.resolveOAuth2(security, context);
|
|
1384
|
+
}
|
|
1385
|
+
return void 0;
|
|
1386
|
+
}
|
|
1387
|
+
/**
|
|
1388
|
+
* Resolve HTTP authentication (bearer, basic, digest, etc.)
|
|
1389
|
+
*/
|
|
1390
|
+
resolveHttpAuth(security, context) {
|
|
1391
|
+
const scheme = security.httpScheme?.toLowerCase() || "bearer";
|
|
1392
|
+
switch (scheme) {
|
|
1393
|
+
case "bearer":
|
|
1394
|
+
return this.resolveBearerAuth(context);
|
|
1395
|
+
case "basic":
|
|
1396
|
+
return this.resolveBasicAuth(context);
|
|
1397
|
+
case "digest":
|
|
1398
|
+
return this.resolveDigestAuth(context);
|
|
1399
|
+
case "hoba":
|
|
1400
|
+
case "mutual":
|
|
1401
|
+
case "negotiate":
|
|
1402
|
+
case "vapid":
|
|
1403
|
+
case "scram":
|
|
1404
|
+
return this.resolveCustomHttpScheme(scheme, security, context);
|
|
1405
|
+
default:
|
|
1406
|
+
return void 0;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
/**
|
|
1410
|
+
* Resolve Bearer token authentication
|
|
1411
|
+
*/
|
|
1412
|
+
resolveBearerAuth(context) {
|
|
1413
|
+
const token = context.jwt;
|
|
1414
|
+
if (!token) return void 0;
|
|
1415
|
+
return `Bearer ${token}`;
|
|
1416
|
+
}
|
|
1417
|
+
/**
|
|
1418
|
+
* Resolve Basic authentication
|
|
1419
|
+
*/
|
|
1420
|
+
resolveBasicAuth(context) {
|
|
1421
|
+
const credentials = context.basic;
|
|
1422
|
+
if (!credentials) return void 0;
|
|
1423
|
+
return `Basic ${credentials}`;
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Resolve Digest authentication
|
|
1427
|
+
*/
|
|
1428
|
+
resolveDigestAuth(context) {
|
|
1429
|
+
const digest = context.digest;
|
|
1430
|
+
if (!digest) return void 0;
|
|
1431
|
+
const parts = [
|
|
1432
|
+
`username="${digest.username}"`,
|
|
1433
|
+
digest.realm ? `realm="${digest.realm}"` : "",
|
|
1434
|
+
digest.nonce ? `nonce="${digest.nonce}"` : "",
|
|
1435
|
+
digest.uri ? `uri="${digest.uri}"` : "",
|
|
1436
|
+
digest.response ? `response="${digest.response}"` : "",
|
|
1437
|
+
digest.opaque ? `opaque="${digest.opaque}"` : "",
|
|
1438
|
+
digest.qop ? `qop=${digest.qop}` : "",
|
|
1439
|
+
digest.nc ? `nc=${digest.nc}` : "",
|
|
1440
|
+
digest.cnonce ? `cnonce="${digest.cnonce}"` : ""
|
|
1441
|
+
].filter(Boolean);
|
|
1442
|
+
return `Digest ${parts.join(", ")}`;
|
|
1443
|
+
}
|
|
1444
|
+
/**
|
|
1445
|
+
* Resolve custom HTTP authentication schemes
|
|
1446
|
+
*/
|
|
1447
|
+
resolveCustomHttpScheme(scheme, security, context) {
|
|
1448
|
+
const headerKey = security.apiKeyName || `X-${scheme.toUpperCase()}`;
|
|
1449
|
+
if (context.customHeaders?.[headerKey]) {
|
|
1450
|
+
return context.customHeaders[headerKey];
|
|
1451
|
+
}
|
|
1452
|
+
return void 0;
|
|
1453
|
+
}
|
|
1454
|
+
/**
|
|
1455
|
+
* Resolve API key authentication
|
|
1456
|
+
*/
|
|
1457
|
+
resolveApiKey(security, context) {
|
|
1458
|
+
if (context.apiKeys && security.apiKeyName) {
|
|
1459
|
+
const key = context.apiKeys[security.apiKeyName];
|
|
1460
|
+
if (key) return key;
|
|
1461
|
+
}
|
|
1462
|
+
if (context.customHeaders && security.apiKeyName) {
|
|
1463
|
+
const header = context.customHeaders[security.apiKeyName];
|
|
1464
|
+
if (header) return header;
|
|
1465
|
+
}
|
|
1466
|
+
return context.apiKey;
|
|
1467
|
+
}
|
|
1468
|
+
/**
|
|
1469
|
+
* Resolve OAuth2/OpenID Connect authentication
|
|
1470
|
+
*/
|
|
1471
|
+
resolveOAuth2(security, context) {
|
|
1472
|
+
const token = context.oauth2Token;
|
|
1473
|
+
if (!token) return void 0;
|
|
1474
|
+
return `Bearer ${token}`;
|
|
1475
|
+
}
|
|
1476
|
+
/**
|
|
1477
|
+
* Check if any security requirements are missing from context
|
|
1478
|
+
*
|
|
1479
|
+
* @param mappers - Parameter mappers from the tool definition
|
|
1480
|
+
* @param context - Security context with auth values
|
|
1481
|
+
* @returns Array of missing security scheme names
|
|
1482
|
+
*/
|
|
1483
|
+
async checkMissingSecurity(mappers, context) {
|
|
1484
|
+
const missing = [];
|
|
1485
|
+
for (const mapper of mappers) {
|
|
1486
|
+
if (!mapper.security) continue;
|
|
1487
|
+
const authValue = await this.resolveAuthValue(mapper.security, context);
|
|
1488
|
+
if (!authValue) {
|
|
1489
|
+
missing.push(mapper.security.scheme);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
return missing;
|
|
1493
|
+
}
|
|
1494
|
+
/**
|
|
1495
|
+
* Sign a request for signature-based authentication
|
|
1496
|
+
*
|
|
1497
|
+
* Use this when resolved.requiresSignature is true.
|
|
1498
|
+
* This method will call the signatureGenerator from context to sign the request.
|
|
1499
|
+
*
|
|
1500
|
+
* @param mappers - Parameter mappers from the tool definition
|
|
1501
|
+
* @param signatureData - Request data to sign
|
|
1502
|
+
* @param context - Security context with signature generator
|
|
1503
|
+
* @returns Headers with signature added
|
|
1504
|
+
*
|
|
1505
|
+
* @example
|
|
1506
|
+
* ```typescript
|
|
1507
|
+
* const resolved = resolver.resolve(tool.mapper, context);
|
|
1508
|
+
* if (resolved.requiresSignature) {
|
|
1509
|
+
* const signedHeaders = await resolver.signRequest(
|
|
1510
|
+
* tool.mapper,
|
|
1511
|
+
* { method: 'GET', url: 'https://api.example.com/data', headers: resolved.headers },
|
|
1512
|
+
* context
|
|
1513
|
+
* );
|
|
1514
|
+
* // Use signedHeaders in request
|
|
1515
|
+
* }
|
|
1516
|
+
* ```
|
|
1517
|
+
*/
|
|
1518
|
+
async signRequest(mappers, signatureData, context) {
|
|
1519
|
+
const headers = { ...signatureData.headers };
|
|
1520
|
+
if (!context.signatureGenerator) {
|
|
1521
|
+
throw new Error("Signature-based auth required but no signatureGenerator provided");
|
|
1522
|
+
}
|
|
1523
|
+
for (const mapper of mappers) {
|
|
1524
|
+
if (!mapper.security || !this.isSignatureBasedAuth(mapper.security)) {
|
|
1525
|
+
continue;
|
|
1526
|
+
}
|
|
1527
|
+
const signature = await context.signatureGenerator(signatureData, mapper.security);
|
|
1528
|
+
if (mapper.type === "header") {
|
|
1529
|
+
headers[mapper.key] = signature;
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
return headers;
|
|
1533
|
+
}
|
|
1534
|
+
};
|
|
1535
|
+
function createSecurityContext(auth) {
|
|
1536
|
+
return {
|
|
1537
|
+
...auth,
|
|
1538
|
+
jwt: auth.jwt,
|
|
1539
|
+
basic: auth.basic,
|
|
1540
|
+
apiKey: auth.apiKey,
|
|
1541
|
+
oauth2Token: auth.oauth2Token,
|
|
1542
|
+
customResolver: auth.customResolver
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
export {
|
|
1546
|
+
GenerationError,
|
|
1547
|
+
LoadError,
|
|
1548
|
+
OpenAPIToolError,
|
|
1549
|
+
OpenAPIToolGenerator,
|
|
1550
|
+
ParameterResolver,
|
|
1551
|
+
ParseError,
|
|
1552
|
+
ResponseBuilder,
|
|
1553
|
+
SchemaBuilder,
|
|
1554
|
+
SchemaError,
|
|
1555
|
+
SecurityResolver,
|
|
1556
|
+
ValidationError,
|
|
1557
|
+
Validator,
|
|
1558
|
+
createSecurityContext,
|
|
1559
|
+
isReferenceObject,
|
|
1560
|
+
toJsonSchema
|
|
1561
|
+
};
|