@ttoss/http-server-mcp-openapi 0.2.14 → 0.3.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/README.md +65 -20
- package/dist/index.cjs +330 -108
- package/dist/index.d.cts +167 -67
- package/dist/index.d.mts +167 -67
- package/dist/index.mjs +331 -110
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -5,6 +5,9 @@ Object.defineProperty(exports, Symbol.toStringTag, {
|
|
|
5
5
|
let _ttoss_http_server_mcp = require("@ttoss/http-server-mcp");
|
|
6
6
|
|
|
7
7
|
//#region src/schema.ts
|
|
8
|
+
var isRecord = value => {
|
|
9
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10
|
+
};
|
|
8
11
|
var getAlternativeSchemas = schema => {
|
|
9
12
|
if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) return schema.oneOf;
|
|
10
13
|
if (Array.isArray(schema.anyOf) && schema.anyOf.length > 0) return schema.anyOf;
|
|
@@ -26,36 +29,96 @@ var mergeResolvedSchemas = resolvedAlternatives => {
|
|
|
26
29
|
required: requiredIntersection && requiredIntersection.length > 0 ? requiredIntersection : void 0
|
|
27
30
|
};
|
|
28
31
|
};
|
|
32
|
+
var stripDotSlash = file => {
|
|
33
|
+
return file.replace(/^\.\//, "");
|
|
34
|
+
};
|
|
35
|
+
var findDocument = args => {
|
|
36
|
+
if (!args.documents) return void 0;
|
|
37
|
+
const wanted = stripDotSlash(args.file);
|
|
38
|
+
for (const [key, document] of Object.entries(args.documents)) if (stripDotSlash(key) === wanted) return document;
|
|
39
|
+
};
|
|
40
|
+
/** Follows an RFC 6901 JSON pointer (`/components/schemas/Tag`) into a value. */
|
|
41
|
+
var followPointer = args => {
|
|
42
|
+
const tokens = args.pointer.split("/").slice(1);
|
|
43
|
+
let current = args.root;
|
|
44
|
+
for (const rawToken of tokens) {
|
|
45
|
+
if (!isRecord(current) && !Array.isArray(current)) return void 0;
|
|
46
|
+
const token = decodeURIComponent(rawToken).replace(/~1/g, "/").replace(/~0/g, "~");
|
|
47
|
+
current = current[token];
|
|
48
|
+
}
|
|
49
|
+
return current;
|
|
50
|
+
};
|
|
51
|
+
/** Stable identity for each document, so cycle keys never collide across files. */
|
|
52
|
+
var documentIds = /* @__PURE__ */new WeakMap();
|
|
53
|
+
var nextDocumentId = 0;
|
|
54
|
+
var documentId = spec => {
|
|
55
|
+
const known = documentIds.get(spec);
|
|
56
|
+
if (known !== void 0) return known;
|
|
57
|
+
nextDocumentId += 1;
|
|
58
|
+
documentIds.set(spec, nextDocumentId);
|
|
59
|
+
return nextDocumentId;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Resolves a `$ref` to its target and the document the target lives in (the
|
|
63
|
+
* scope its own nested refs resolve against). A ref without a file part
|
|
64
|
+
* (`#/components/schemas/X`) points into the current document; one with a
|
|
65
|
+
* file part (`./tags.yaml#/components/schemas/Tag`) points into the matching
|
|
66
|
+
* entry of `documents`. Returns `undefined` when the target does not exist.
|
|
67
|
+
*/
|
|
68
|
+
var resolveRef = args => {
|
|
69
|
+
const hashIndex = args.ref.indexOf("#");
|
|
70
|
+
const file = hashIndex === -1 ? args.ref : args.ref.slice(0, hashIndex);
|
|
71
|
+
const pointer = hashIndex === -1 ? "" : args.ref.slice(hashIndex + 1);
|
|
72
|
+
const spec = file === "" ? args.scope.spec : findDocument({
|
|
73
|
+
file,
|
|
74
|
+
documents: args.scope.documents
|
|
75
|
+
});
|
|
76
|
+
if (!spec) return void 0;
|
|
77
|
+
const value = followPointer({
|
|
78
|
+
root: spec,
|
|
79
|
+
pointer
|
|
80
|
+
});
|
|
81
|
+
if (value === void 0) return void 0;
|
|
82
|
+
return {
|
|
83
|
+
value,
|
|
84
|
+
scope: {
|
|
85
|
+
spec,
|
|
86
|
+
documents: args.scope.documents
|
|
87
|
+
},
|
|
88
|
+
key: `${documentId(spec)}#${pointer}`
|
|
89
|
+
};
|
|
90
|
+
};
|
|
29
91
|
var dereferenceValue = args => {
|
|
30
92
|
const {
|
|
31
93
|
value,
|
|
32
|
-
|
|
94
|
+
scope,
|
|
33
95
|
seenRefs
|
|
34
96
|
} = args;
|
|
35
97
|
if (Array.isArray(value)) return value.map(item => {
|
|
36
98
|
return dereferenceValue({
|
|
37
99
|
value: item,
|
|
38
|
-
|
|
100
|
+
scope,
|
|
39
101
|
seenRefs
|
|
40
102
|
});
|
|
41
103
|
});
|
|
42
|
-
if (value
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (
|
|
104
|
+
if (isRecord(value)) {
|
|
105
|
+
if (typeof value.$ref === "string") {
|
|
106
|
+
const target = resolveRef({
|
|
107
|
+
ref: value.$ref,
|
|
108
|
+
scope
|
|
109
|
+
});
|
|
110
|
+
if (!target) return {};
|
|
111
|
+
if (seenRefs.has(target.key)) return {};
|
|
49
112
|
return dereferenceValue({
|
|
50
|
-
value:
|
|
51
|
-
|
|
52
|
-
seenRefs: new Set(seenRefs).add(
|
|
113
|
+
value: target.value,
|
|
114
|
+
scope: target.scope,
|
|
115
|
+
seenRefs: new Set(seenRefs).add(target.key)
|
|
53
116
|
});
|
|
54
117
|
}
|
|
55
118
|
const result = {};
|
|
56
|
-
for (const [key, entryValue] of Object.entries(
|
|
119
|
+
for (const [key, entryValue] of Object.entries(value)) result[key] = dereferenceValue({
|
|
57
120
|
value: entryValue,
|
|
58
|
-
|
|
121
|
+
scope,
|
|
59
122
|
seenRefs
|
|
60
123
|
});
|
|
61
124
|
return result;
|
|
@@ -67,12 +130,17 @@ var dereferenceValue = args => {
|
|
|
67
130
|
* `properties`, `items`, `oneOf`, `anyOf`, etc.), producing a self-contained
|
|
68
131
|
* schema safe to hand to an MCP client or LLM provider as a tool definition —
|
|
69
132
|
* provider tool schemas have no `components` section to resolve refs against.
|
|
133
|
+
*
|
|
134
|
+
* Refs into other files resolve against `documents`; see {@link OpenApiDocuments}.
|
|
70
135
|
*/
|
|
71
|
-
var dereferenceSchema = (schema, spec) => {
|
|
136
|
+
var dereferenceSchema = (schema, spec, documents) => {
|
|
72
137
|
if (!schema) return schema;
|
|
73
138
|
return dereferenceValue({
|
|
74
139
|
value: schema,
|
|
75
|
-
|
|
140
|
+
scope: {
|
|
141
|
+
spec,
|
|
142
|
+
documents
|
|
143
|
+
},
|
|
76
144
|
seenRefs: /* @__PURE__ */new Set()
|
|
77
145
|
});
|
|
78
146
|
};
|
|
@@ -81,25 +149,39 @@ var dereferenceSchema = (schema, spec) => {
|
|
|
81
149
|
* and merges `oneOf` / `anyOf` alternatives (union of properties, intersection
|
|
82
150
|
* of `required`) so the caller sees one flat property set.
|
|
83
151
|
*/
|
|
84
|
-
var resolveSchema = (schema, spec) => {
|
|
152
|
+
var resolveSchema = (schema, spec, documents) => {
|
|
85
153
|
if (!schema) return {};
|
|
86
154
|
if (typeof schema.$ref === "string") {
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
155
|
+
const target = resolveRef({
|
|
156
|
+
ref: schema.$ref,
|
|
157
|
+
scope: {
|
|
158
|
+
spec,
|
|
159
|
+
documents
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
return resolveSchema(target?.value, target?.scope.spec ?? spec, documents);
|
|
90
163
|
}
|
|
91
164
|
const alternatives = getAlternativeSchemas(schema);
|
|
92
165
|
if (alternatives) return mergeResolvedSchemas(alternatives.map(candidate => {
|
|
93
|
-
return resolveSchema(candidate, spec);
|
|
166
|
+
return resolveSchema(candidate, spec, documents);
|
|
94
167
|
}));
|
|
95
168
|
return schema;
|
|
96
169
|
};
|
|
97
|
-
/**
|
|
98
|
-
|
|
170
|
+
/**
|
|
171
|
+
* Follows a parameter `$ref` — into `components.parameters`, or into another
|
|
172
|
+
* file through `documents` — if present.
|
|
173
|
+
*/
|
|
174
|
+
var resolveParameter = (param, spec, documents) => {
|
|
99
175
|
if (!param) return {};
|
|
100
176
|
if (typeof param.$ref === "string") {
|
|
101
|
-
const
|
|
102
|
-
|
|
177
|
+
const target = resolveRef({
|
|
178
|
+
ref: param.$ref,
|
|
179
|
+
scope: {
|
|
180
|
+
spec,
|
|
181
|
+
documents
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
return isRecord(target?.value) ? target.value : {};
|
|
103
185
|
}
|
|
104
186
|
return param;
|
|
105
187
|
};
|
|
@@ -109,9 +191,9 @@ var buildPathFn = (pathTemplate, pathParams) => {
|
|
|
109
191
|
let result = pathTemplate;
|
|
110
192
|
for (const {
|
|
111
193
|
name,
|
|
112
|
-
|
|
194
|
+
argName
|
|
113
195
|
} of pathParams) {
|
|
114
|
-
const value = args[
|
|
196
|
+
const value = args[argName];
|
|
115
197
|
if (value !== void 0) result = result.replace(`{${name}}`, encodeURIComponent(String(value)));
|
|
116
198
|
}
|
|
117
199
|
return result;
|
|
@@ -122,9 +204,6 @@ var NON_EXPLODED_DELIMITERS = {
|
|
|
122
204
|
spaceDelimited: " ",
|
|
123
205
|
pipeDelimited: "|"
|
|
124
206
|
};
|
|
125
|
-
var isRecord = value => {
|
|
126
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
127
|
-
};
|
|
128
207
|
/**
|
|
129
208
|
* Appends a `deepObject` value as bracketed keys — `filters[documentId][$eq]`.
|
|
130
209
|
* OpenAPI only defines one level, but the APIs that ask for `deepObject`
|
|
@@ -212,7 +291,7 @@ var buildQueryFn = queryParams => {
|
|
|
212
291
|
return args => {
|
|
213
292
|
const search = new URLSearchParams();
|
|
214
293
|
for (const param of queryParams) {
|
|
215
|
-
const value = args[param.
|
|
294
|
+
const value = args[param.argName];
|
|
216
295
|
if (value === void 0 || value === null) continue;
|
|
217
296
|
appendQueryValue({
|
|
218
297
|
search,
|
|
@@ -225,8 +304,8 @@ var buildQueryFn = queryParams => {
|
|
|
225
304
|
};
|
|
226
305
|
};
|
|
227
306
|
/**
|
|
228
|
-
* Builds a function that maps
|
|
229
|
-
*
|
|
307
|
+
* Builds a function that maps tool args back to a request body keyed by the
|
|
308
|
+
* spec's property names, skipping `undefined` args. Returns `undefined` when the op has no body.
|
|
230
309
|
*/
|
|
231
310
|
var buildBodyFn = bodyProps => {
|
|
232
311
|
if (bodyProps.length === 0) return void 0;
|
|
@@ -234,8 +313,8 @@ var buildBodyFn = bodyProps => {
|
|
|
234
313
|
const body = {};
|
|
235
314
|
for (const {
|
|
236
315
|
snakeName,
|
|
237
|
-
|
|
238
|
-
} of bodyProps) if (args[
|
|
316
|
+
argName
|
|
317
|
+
} of bodyProps) if (args[argName] !== void 0) body[snakeName] = args[argName];
|
|
239
318
|
return body;
|
|
240
319
|
};
|
|
241
320
|
};
|
|
@@ -246,17 +325,93 @@ var DEFAULT_EXCLUDE_EXTENSION = "x-mcp-exclude";
|
|
|
246
325
|
var DEFAULT_SERVER_MANAGED_EXTENSION = "x-mcp-server-managed";
|
|
247
326
|
|
|
248
327
|
//#endregion
|
|
249
|
-
//#region src/
|
|
328
|
+
//#region src/parameters.ts
|
|
250
329
|
/**
|
|
251
330
|
* Folds `_` and `-` separators into camelCase. OpenAPI operation and parameter
|
|
252
|
-
* names may be snake_case (`agent_id`) or kebab-case (`list-tools`)
|
|
253
|
-
*
|
|
331
|
+
* names may be snake_case (`agent_id`) or kebab-case (`list-tools`); with the
|
|
332
|
+
* default `argumentNames: 'camelCase'` both are folded into tool arguments.
|
|
254
333
|
*/
|
|
255
334
|
var snakeToCamel = str => {
|
|
256
335
|
return str.replace(/[_-]([a-z])/g, (_, letter) => {
|
|
257
336
|
return letter.toUpperCase();
|
|
258
337
|
});
|
|
259
338
|
};
|
|
339
|
+
var verbatim = name => {
|
|
340
|
+
return name;
|
|
341
|
+
};
|
|
342
|
+
var argNameMapper = argumentNames => {
|
|
343
|
+
return argumentNames === "verbatim" ? verbatim : snakeToCamel;
|
|
344
|
+
};
|
|
345
|
+
/**
|
|
346
|
+
* Deduplicates parameter entries by `name`, keeping the last occurrence. When
|
|
347
|
+
* path-item-level and operation-level parameters are concatenated (operation
|
|
348
|
+
* last), this makes the operation-level entry win — as the OpenAPI spec requires.
|
|
349
|
+
*/
|
|
350
|
+
var dedupeByName = items => {
|
|
351
|
+
const byName = /* @__PURE__ */new Map();
|
|
352
|
+
for (const item of items) byName.set(item.name, item);
|
|
353
|
+
return [...byName.values()];
|
|
354
|
+
};
|
|
355
|
+
var extractPathParams = args => {
|
|
356
|
+
const toArgName = args.toArgName ?? snakeToCamel;
|
|
357
|
+
const flag = args.serverManagedExtension ?? "x-mcp-server-managed";
|
|
358
|
+
return dedupeByName((args.parameters || []).map(p => {
|
|
359
|
+
return resolveParameter(p, args.spec, args.documents);
|
|
360
|
+
}).filter(p => {
|
|
361
|
+
return p.in === "path";
|
|
362
|
+
}).map(p => {
|
|
363
|
+
return {
|
|
364
|
+
name: p.name || "",
|
|
365
|
+
argName: toArgName(p.name || ""),
|
|
366
|
+
serverManaged: Boolean(p[flag])
|
|
367
|
+
};
|
|
368
|
+
}));
|
|
369
|
+
};
|
|
370
|
+
var extractQueryParams = args => {
|
|
371
|
+
const toArgName = args.toArgName ?? snakeToCamel;
|
|
372
|
+
const flag = args.serverManagedExtension ?? "x-mcp-server-managed";
|
|
373
|
+
return dedupeByName((args.parameters || []).map(p => {
|
|
374
|
+
return resolveParameter(p, args.spec, args.documents);
|
|
375
|
+
}).filter(p => {
|
|
376
|
+
return p.in === "query";
|
|
377
|
+
}).map(p => {
|
|
378
|
+
return {
|
|
379
|
+
name: p.name || "",
|
|
380
|
+
argName: toArgName(p.name || ""),
|
|
381
|
+
description: p.description || "",
|
|
382
|
+
required: p.required || false,
|
|
383
|
+
type: p.schema?.type || "string",
|
|
384
|
+
style: p.style,
|
|
385
|
+
explode: p.explode,
|
|
386
|
+
serverManaged: Boolean(p[flag])
|
|
387
|
+
};
|
|
388
|
+
}));
|
|
389
|
+
};
|
|
390
|
+
/** Lists the path and query params flagged as server-managed. */
|
|
391
|
+
var collectServerManagedParameters = args => {
|
|
392
|
+
return [...args.pathParams.map(p => {
|
|
393
|
+
return {
|
|
394
|
+
...p,
|
|
395
|
+
in: "path"
|
|
396
|
+
};
|
|
397
|
+
}), ...args.queryParams.map(p => {
|
|
398
|
+
return {
|
|
399
|
+
...p,
|
|
400
|
+
in: "query"
|
|
401
|
+
};
|
|
402
|
+
})].filter(p => {
|
|
403
|
+
return p.serverManaged;
|
|
404
|
+
}).map(p => {
|
|
405
|
+
return {
|
|
406
|
+
name: p.name,
|
|
407
|
+
in: p.in,
|
|
408
|
+
argName: p.argName
|
|
409
|
+
};
|
|
410
|
+
});
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
//#endregion
|
|
414
|
+
//#region src/toolDefinitions.ts
|
|
260
415
|
/** Converts a camelCase `operationId` to a kebab-case tool name. */
|
|
261
416
|
var operationIdToToolName = operationId => {
|
|
262
417
|
return operationId.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "");
|
|
@@ -272,11 +427,13 @@ var sanitizeDescription = description => {
|
|
|
272
427
|
return (description || "").replace(/'/g, "\\'").replace(/\n/g, " ").trim();
|
|
273
428
|
};
|
|
274
429
|
/**
|
|
275
|
-
*
|
|
276
|
-
*
|
|
430
|
+
* Forwards a property's `oneOf` / `anyOf` / multi-entry `allOf` verbatim, or
|
|
431
|
+
* returns `undefined` when it declares none.
|
|
277
432
|
*/
|
|
278
|
-
var
|
|
279
|
-
const
|
|
433
|
+
var buildComposedProperty = param => {
|
|
434
|
+
const {
|
|
435
|
+
description
|
|
436
|
+
} = param;
|
|
280
437
|
if (param.oneOf && param.oneOf.length > 0) return {
|
|
281
438
|
oneOf: param.oneOf,
|
|
282
439
|
description
|
|
@@ -285,6 +442,25 @@ var buildTypedProperty = param => {
|
|
|
285
442
|
anyOf: param.anyOf,
|
|
286
443
|
description
|
|
287
444
|
};
|
|
445
|
+
if (param.allOf && param.allOf.length > 0) return {
|
|
446
|
+
allOf: param.allOf,
|
|
447
|
+
description
|
|
448
|
+
};
|
|
449
|
+
};
|
|
450
|
+
/**
|
|
451
|
+
* Builds a single body/query property's `JsonSchemaProperty`. Split out of
|
|
452
|
+
* {@link buildInputSchema} to keep that function's size and branching down.
|
|
453
|
+
*/
|
|
454
|
+
var buildTypedProperty = param => {
|
|
455
|
+
const description = sanitizeDescription(param.description);
|
|
456
|
+
const composed = buildComposedProperty({
|
|
457
|
+
...param,
|
|
458
|
+
description
|
|
459
|
+
});
|
|
460
|
+
if (composed) return composed;
|
|
461
|
+
if (param.type === void 0) return {
|
|
462
|
+
description
|
|
463
|
+
};
|
|
288
464
|
const jsonType = getJsonSchemaType(param.type);
|
|
289
465
|
const finalType = param.nullable === true ? [jsonType, "null"] : jsonType;
|
|
290
466
|
if (param.type === "array") return {
|
|
@@ -300,23 +476,29 @@ var buildTypedProperty = param => {
|
|
|
300
476
|
};
|
|
301
477
|
};
|
|
302
478
|
var buildInputSchema = (pathParams, queryParams, bodyProps) => {
|
|
303
|
-
const
|
|
479
|
+
const modelPathParams = pathParams.filter(p => {
|
|
480
|
+
return !p.serverManaged;
|
|
481
|
+
});
|
|
482
|
+
const modelQueryParams = queryParams.filter(p => {
|
|
483
|
+
return !p.serverManaged;
|
|
484
|
+
});
|
|
485
|
+
const allParams = [...modelPathParams, ...modelQueryParams, ...bodyProps];
|
|
304
486
|
if (allParams.length === 0) return {
|
|
305
487
|
type: "object"
|
|
306
488
|
};
|
|
307
|
-
const requiredFields = [...
|
|
308
|
-
return p.
|
|
309
|
-
}), ...
|
|
489
|
+
const requiredFields = [...modelPathParams.map(p => {
|
|
490
|
+
return p.argName;
|
|
491
|
+
}), ...modelQueryParams.filter(p => {
|
|
310
492
|
return p.required;
|
|
311
493
|
}).map(p => {
|
|
312
|
-
return p.
|
|
494
|
+
return p.argName;
|
|
313
495
|
}), ...bodyProps.filter(p => {
|
|
314
496
|
return p.required;
|
|
315
497
|
}).map(p => {
|
|
316
|
-
return p.
|
|
498
|
+
return p.argName;
|
|
317
499
|
})];
|
|
318
500
|
const properties = {};
|
|
319
|
-
for (const param of allParams) properties[param.
|
|
501
|
+
for (const param of allParams) properties[param.argName] = "description" in param ? buildTypedProperty(param) : {
|
|
320
502
|
type: "string",
|
|
321
503
|
description: ""
|
|
322
504
|
};
|
|
@@ -328,48 +510,9 @@ var buildInputSchema = (pathParams, queryParams, bodyProps) => {
|
|
|
328
510
|
} : {})
|
|
329
511
|
};
|
|
330
512
|
};
|
|
331
|
-
/**
|
|
332
|
-
* Deduplicates parameter entries by `name`, keeping the last occurrence. When
|
|
333
|
-
* path-item-level and operation-level parameters are concatenated (operation
|
|
334
|
-
* last), this makes the operation-level entry win — as the OpenAPI spec requires.
|
|
335
|
-
*/
|
|
336
|
-
var dedupeByName = items => {
|
|
337
|
-
const byName = /* @__PURE__ */new Map();
|
|
338
|
-
for (const item of items) byName.set(item.name, item);
|
|
339
|
-
return [...byName.values()];
|
|
340
|
-
};
|
|
341
|
-
var extractPathParams = args => {
|
|
342
|
-
return dedupeByName((args.parameters || []).map(p => {
|
|
343
|
-
return resolveParameter(p, args.spec);
|
|
344
|
-
}).filter(p => {
|
|
345
|
-
return p.in === "path";
|
|
346
|
-
}).map(p => {
|
|
347
|
-
return {
|
|
348
|
-
name: p.name || "",
|
|
349
|
-
camelName: snakeToCamel(p.name || "")
|
|
350
|
-
};
|
|
351
|
-
}));
|
|
352
|
-
};
|
|
353
|
-
var extractQueryParams = args => {
|
|
354
|
-
return dedupeByName((args.parameters || []).map(p => {
|
|
355
|
-
return resolveParameter(p, args.spec);
|
|
356
|
-
}).filter(p => {
|
|
357
|
-
return p.in === "query";
|
|
358
|
-
}).map(p => {
|
|
359
|
-
return {
|
|
360
|
-
name: p.name || "",
|
|
361
|
-
camelName: snakeToCamel(p.name || ""),
|
|
362
|
-
description: p.description || "",
|
|
363
|
-
required: p.required || false,
|
|
364
|
-
type: p.schema?.type || "string",
|
|
365
|
-
style: p.style,
|
|
366
|
-
explode: p.explode
|
|
367
|
-
};
|
|
368
|
-
}));
|
|
369
|
-
};
|
|
370
513
|
var resolveBodySchema = args => {
|
|
371
514
|
const rawBodySchema = args.requestBody?.content?.["application/json"]?.schema;
|
|
372
|
-
return resolveSchema(dereferenceSchema(rawBodySchema, args.spec), args.spec);
|
|
515
|
+
return resolveSchema(dereferenceSchema(rawBodySchema, args.spec, args.documents), args.spec, args.documents);
|
|
373
516
|
};
|
|
374
517
|
/**
|
|
375
518
|
* snake_case names of every top-level property an operation's request schema
|
|
@@ -379,23 +522,48 @@ var extractAcceptedBodyFields = args => {
|
|
|
379
522
|
const bodySchema = resolveBodySchema(args);
|
|
380
523
|
return Object.keys(bodySchema?.properties ?? {});
|
|
381
524
|
};
|
|
525
|
+
var isPlainObject = value => {
|
|
526
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
527
|
+
};
|
|
528
|
+
/**
|
|
529
|
+
* Folds a single-entry `allOf` into the property that wraps it. OpenAPI
|
|
530
|
+
* declares `allOf: [{ $ref }]` so a property can carry its own `description`
|
|
531
|
+
* next to a referenced schema; without folding, the referenced `type`,
|
|
532
|
+
* `nullable` and `items` would be lost. Keys on the wrapper win over the
|
|
533
|
+
* referenced schema's. Multi-entry `allOf` is left intact.
|
|
534
|
+
*/
|
|
535
|
+
var flattenSingleAllOf = schema => {
|
|
536
|
+
const {
|
|
537
|
+
allOf,
|
|
538
|
+
...rest
|
|
539
|
+
} = schema;
|
|
540
|
+
if (!Array.isArray(allOf) || allOf.length !== 1) return schema;
|
|
541
|
+
const [entry] = allOf;
|
|
542
|
+
if (!isPlainObject(entry)) return rest;
|
|
543
|
+
return {
|
|
544
|
+
...flattenSingleAllOf(entry),
|
|
545
|
+
...rest
|
|
546
|
+
};
|
|
547
|
+
};
|
|
382
548
|
var extractBodyProps = args => {
|
|
549
|
+
const toArgName = args.toArgName ?? snakeToCamel;
|
|
383
550
|
const bodySchema = resolveBodySchema(args);
|
|
384
551
|
if (!bodySchema?.properties) return [];
|
|
385
552
|
return Object.entries(bodySchema.properties).filter(([, value]) => {
|
|
386
553
|
return !value[args.serverManagedExtension];
|
|
387
554
|
}).map(([key, value]) => {
|
|
388
|
-
const val = value;
|
|
555
|
+
const val = flattenSingleAllOf(value);
|
|
389
556
|
return {
|
|
390
557
|
snakeName: key,
|
|
391
|
-
|
|
558
|
+
argName: toArgName(key),
|
|
392
559
|
description: typeof val.description === "string" ? val.description : "",
|
|
393
560
|
required: (bodySchema.required || []).includes(key),
|
|
394
|
-
type: typeof val.type === "string" ? val.type :
|
|
561
|
+
type: typeof val.type === "string" ? val.type : void 0,
|
|
395
562
|
items: val.items,
|
|
396
563
|
nullable: val.nullable === true,
|
|
397
564
|
oneOf: Array.isArray(val.oneOf) ? val.oneOf : void 0,
|
|
398
|
-
anyOf: Array.isArray(val.anyOf) ? val.anyOf : void 0
|
|
565
|
+
anyOf: Array.isArray(val.anyOf) ? val.anyOf : void 0,
|
|
566
|
+
allOf: Array.isArray(val.allOf) ? val.allOf : void 0
|
|
399
567
|
};
|
|
400
568
|
});
|
|
401
569
|
};
|
|
@@ -412,23 +580,37 @@ var processOperation = args => {
|
|
|
412
580
|
if (args.operation[args.options.excludeExtension]) return null;
|
|
413
581
|
const toolName = operationIdToToolName(args.operation.operationId);
|
|
414
582
|
const parameters = [...(args.pathItemParameters ?? []), ...(args.operation.parameters ?? [])];
|
|
583
|
+
const {
|
|
584
|
+
documents,
|
|
585
|
+
serverManagedExtension
|
|
586
|
+
} = args.options;
|
|
587
|
+
const toArgName = argNameMapper(args.options.argumentNames);
|
|
415
588
|
const pathParams = extractPathParams({
|
|
416
589
|
parameters,
|
|
417
|
-
spec: args.spec
|
|
590
|
+
spec: args.spec,
|
|
591
|
+
documents,
|
|
592
|
+
toArgName,
|
|
593
|
+
serverManagedExtension
|
|
418
594
|
});
|
|
419
595
|
const queryParams = extractQueryParams({
|
|
420
596
|
parameters,
|
|
421
|
-
spec: args.spec
|
|
597
|
+
spec: args.spec,
|
|
598
|
+
documents,
|
|
599
|
+
toArgName,
|
|
600
|
+
serverManagedExtension
|
|
422
601
|
});
|
|
423
602
|
const bodyProps = extractBodyProps({
|
|
424
603
|
requestBody: args.operation.requestBody,
|
|
425
604
|
spec: args.spec,
|
|
426
|
-
serverManagedExtension
|
|
605
|
+
serverManagedExtension,
|
|
606
|
+
documents,
|
|
607
|
+
toArgName
|
|
427
608
|
});
|
|
428
609
|
const inputSchema = buildInputSchema(pathParams, queryParams, bodyProps);
|
|
429
610
|
const acceptedBodyFields = extractAcceptedBodyFields({
|
|
430
611
|
requestBody: args.operation.requestBody,
|
|
431
|
-
spec: args.spec
|
|
612
|
+
spec: args.spec,
|
|
613
|
+
documents
|
|
432
614
|
});
|
|
433
615
|
return {
|
|
434
616
|
name: toolName,
|
|
@@ -441,7 +623,11 @@ var processOperation = args => {
|
|
|
441
623
|
query: buildQueryFn(queryParams),
|
|
442
624
|
body: buildBodyFn(bodyProps),
|
|
443
625
|
acceptedBodyFields,
|
|
444
|
-
extensions: extractExtensions(args.operation)
|
|
626
|
+
extensions: extractExtensions(args.operation),
|
|
627
|
+
serverManagedParameters: collectServerManagedParameters({
|
|
628
|
+
pathParams,
|
|
629
|
+
queryParams
|
|
630
|
+
})
|
|
445
631
|
};
|
|
446
632
|
};
|
|
447
633
|
var processPath = args => {
|
|
@@ -462,6 +648,15 @@ var processPath = args => {
|
|
|
462
648
|
}
|
|
463
649
|
return tools;
|
|
464
650
|
};
|
|
651
|
+
/** Applies the defaults to {@link OpenApiToToolsOptions}. */
|
|
652
|
+
var resolveOptions = (options = {}) => {
|
|
653
|
+
return {
|
|
654
|
+
excludeExtension: options.excludeExtension ?? "x-mcp-exclude",
|
|
655
|
+
serverManagedExtension: options.serverManagedExtension ?? "x-mcp-server-managed",
|
|
656
|
+
argumentNames: options.argumentNames ?? "camelCase",
|
|
657
|
+
documents: options.documents
|
|
658
|
+
};
|
|
659
|
+
};
|
|
465
660
|
/**
|
|
466
661
|
* Translates one or more OpenAPI documents into REST-backed MCP tool
|
|
467
662
|
* definitions. Each translatable operation (has an `operationId`, a supported
|
|
@@ -475,10 +670,7 @@ var processPath = args => {
|
|
|
475
670
|
* ```
|
|
476
671
|
*/
|
|
477
672
|
var openApiToToolDefinitions = args => {
|
|
478
|
-
const options =
|
|
479
|
-
excludeExtension: args.options?.excludeExtension ?? "x-mcp-exclude",
|
|
480
|
-
serverManagedExtension: args.options?.serverManagedExtension ?? "x-mcp-server-managed"
|
|
481
|
-
};
|
|
673
|
+
const options = resolveOptions(args.options);
|
|
482
674
|
const specs = Array.isArray(args.spec) ? args.spec : [args.spec];
|
|
483
675
|
const tools = [];
|
|
484
676
|
for (const spec of specs) {
|
|
@@ -495,13 +687,34 @@ var openApiToToolDefinitions = args => {
|
|
|
495
687
|
|
|
496
688
|
//#endregion
|
|
497
689
|
//#region src/registerOpenApiTools.ts
|
|
690
|
+
/** The text the default `toText` answers when the API returned no body. */
|
|
691
|
+
var NO_CONTENT_TEXT = "Succeeded. The operation returned no content.";
|
|
498
692
|
var defaultToText = data => {
|
|
693
|
+
if (data === void 0 || data === "") return NO_CONTENT_TEXT;
|
|
499
694
|
return typeof data === "string" ? data : JSON.stringify(data, null, 2);
|
|
500
695
|
};
|
|
501
696
|
/**
|
|
697
|
+
* Replaces whatever the model sent for server-managed parameters with the
|
|
698
|
+
* values `serverParameters` supplies, so the model can never set them.
|
|
699
|
+
*/
|
|
700
|
+
var applyServerParameters = async args => {
|
|
701
|
+
const managed = args.tool.serverManagedParameters;
|
|
702
|
+
if (managed.length === 0) return args.handlerArgs;
|
|
703
|
+
const result = {
|
|
704
|
+
...args.handlerArgs
|
|
705
|
+
};
|
|
706
|
+
for (const param of managed) delete result[param.argName];
|
|
707
|
+
const values = args.serverParameters ? await args.serverParameters({
|
|
708
|
+
tool: args.tool,
|
|
709
|
+
headers: args.headers
|
|
710
|
+
}) : {};
|
|
711
|
+
for (const param of managed) if (values[param.name] !== void 0) result[param.argName] = values[param.name];
|
|
712
|
+
return result;
|
|
713
|
+
};
|
|
714
|
+
/**
|
|
502
715
|
* Derives MCP tools from OpenAPI document(s) and registers each on the given
|
|
503
|
-
* MCP server. Every tool's handler resolves the incoming
|
|
504
|
-
*
|
|
716
|
+
* MCP server. Every tool's handler resolves the incoming args into a concrete
|
|
717
|
+
* HTTP request and delegates execution to `callApi`.
|
|
505
718
|
*
|
|
506
719
|
* @returns The list of {@link ToolDefinition} that were registered.
|
|
507
720
|
*
|
|
@@ -515,10 +728,10 @@ var defaultToText = data => {
|
|
|
515
728
|
* registerOpenApiTools({
|
|
516
729
|
* server,
|
|
517
730
|
* spec: myOpenApiDocument,
|
|
518
|
-
* callApi: async ({ method, url, body }) => {
|
|
731
|
+
* callApi: async ({ method, url, body, headers }) => {
|
|
519
732
|
* const res = await fetch(`https://api.example.com${url}`, {
|
|
520
733
|
* method,
|
|
521
|
-
* headers: { 'Content-Type': 'application/json' },
|
|
734
|
+
* headers: { ...headers, 'Content-Type': 'application/json' },
|
|
522
735
|
* body: body ? JSON.stringify(body) : undefined,
|
|
523
736
|
* });
|
|
524
737
|
* return res.json();
|
|
@@ -536,7 +749,14 @@ var registerOpenApiTools = args => {
|
|
|
536
749
|
name: tool.name,
|
|
537
750
|
description: tool.description,
|
|
538
751
|
inputSchema: tool.inputSchema,
|
|
539
|
-
handler: async
|
|
752
|
+
handler: async rawArgs => {
|
|
753
|
+
const headers = (0, _ttoss_http_server_mcp.getApiHeaders)();
|
|
754
|
+
const handlerArgs = await applyServerParameters({
|
|
755
|
+
tool,
|
|
756
|
+
handlerArgs: rawArgs,
|
|
757
|
+
headers,
|
|
758
|
+
serverParameters: args.serverParameters
|
|
759
|
+
});
|
|
540
760
|
const url = tool.path(handlerArgs) + (tool.query ? tool.query(handlerArgs) : "");
|
|
541
761
|
return {
|
|
542
762
|
content: [{
|
|
@@ -545,7 +765,8 @@ var registerOpenApiTools = args => {
|
|
|
545
765
|
method: tool.method,
|
|
546
766
|
url,
|
|
547
767
|
body: tool.body ? tool.body(handlerArgs) : void 0,
|
|
548
|
-
tool
|
|
768
|
+
tool,
|
|
769
|
+
headers
|
|
549
770
|
}))
|
|
550
771
|
}]
|
|
551
772
|
};
|
|
@@ -557,6 +778,7 @@ var registerOpenApiTools = args => {
|
|
|
557
778
|
//#endregion
|
|
558
779
|
exports.DEFAULT_EXCLUDE_EXTENSION = DEFAULT_EXCLUDE_EXTENSION;
|
|
559
780
|
exports.DEFAULT_SERVER_MANAGED_EXTENSION = DEFAULT_SERVER_MANAGED_EXTENSION;
|
|
781
|
+
exports.NO_CONTENT_TEXT = NO_CONTENT_TEXT;
|
|
560
782
|
exports.buildBodyFn = buildBodyFn;
|
|
561
783
|
exports.buildInputSchema = buildInputSchema;
|
|
562
784
|
exports.buildPathFn = buildPathFn;
|