@ttoss/http-server-mcp-openapi 0.2.13 → 0.2.15
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 +10 -1
- package/dist/index.cjs +150 -17
- package/dist/index.d.cts +26 -11
- package/dist/index.d.mts +26 -11
- package/dist/index.mjs +150 -17
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -61,7 +61,11 @@ Each OpenAPI operation with an `operationId` and a supported HTTP method
|
|
|
61
61
|
| operation `description` | tool description (quotes/newlines sanitised) |
|
|
62
62
|
|
|
63
63
|
Path params are always required strings. Query and body params carry their
|
|
64
|
-
declared type and `required` flag. Array params keep their `items` schema.
|
|
64
|
+
declared type and `required` flag. Array params keep their `items` schema. A
|
|
65
|
+
body property declared as a single-entry `allOf` (usually `allOf: [{ $ref }]`
|
|
66
|
+
beside its own `description`) takes `type`, `nullable` and `items` from the
|
|
67
|
+
referenced schema; a multi-entry `allOf` is forwarded verbatim, and a property
|
|
68
|
+
with no declared type is advertised untyped so it accepts any value.
|
|
65
69
|
Parameters declared at the **path-item level** (shared by every operation on a
|
|
66
70
|
path) are merged into each operation; an operation-level parameter overrides a
|
|
67
71
|
path-item one with the same `name`+`in`.
|
|
@@ -69,6 +73,11 @@ path-item one with the same `name`+`in`.
|
|
|
69
73
|
Tool arguments are **camelCase** (`agentId`, `projectId`); the generated
|
|
70
74
|
request path, query string, and body use the original **snake_case** names.
|
|
71
75
|
|
|
76
|
+
Query params honour their declared `style` and `explode`. `form` (the default)
|
|
77
|
+
repeats array values, `spaceDelimited`/`pipeDelimited` join them, and
|
|
78
|
+
`deepObject` emits bracketed keys — including nested objects and arrays, so
|
|
79
|
+
`{ documentId: { $eq: 'doc_1' } }` becomes `filters[documentId][$eq]=doc_1`.
|
|
80
|
+
|
|
72
81
|
## `registerOpenApiTools`
|
|
73
82
|
|
|
74
83
|
| Field | Description |
|
package/dist/index.cjs
CHANGED
|
@@ -117,22 +117,108 @@ var buildPathFn = (pathTemplate, pathParams) => {
|
|
|
117
117
|
return result;
|
|
118
118
|
};
|
|
119
119
|
};
|
|
120
|
+
var NON_EXPLODED_DELIMITERS = {
|
|
121
|
+
form: ",",
|
|
122
|
+
spaceDelimited: " ",
|
|
123
|
+
pipeDelimited: "|"
|
|
124
|
+
};
|
|
125
|
+
var isRecord = value => {
|
|
126
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* Appends a `deepObject` value as bracketed keys — `filters[documentId][$eq]`.
|
|
130
|
+
* OpenAPI only defines one level, but the APIs that ask for `deepObject`
|
|
131
|
+
* (Strapi, Directus) nest, so nested objects and arrays recurse instead of
|
|
132
|
+
* being stringified into `[object Object]`.
|
|
133
|
+
*/
|
|
134
|
+
var appendDeepObject = args => {
|
|
135
|
+
if (args.value === void 0 || args.value === null) return;
|
|
136
|
+
if (Array.isArray(args.value)) {
|
|
137
|
+
for (const [index, item] of args.value.entries()) appendDeepObject({
|
|
138
|
+
search: args.search,
|
|
139
|
+
key: `${args.key}[${index}]`,
|
|
140
|
+
value: item
|
|
141
|
+
});
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (isRecord(args.value)) {
|
|
145
|
+
for (const [property, propertyValue] of Object.entries(args.value)) appendDeepObject({
|
|
146
|
+
search: args.search,
|
|
147
|
+
key: `${args.key}[${property}]`,
|
|
148
|
+
value: propertyValue
|
|
149
|
+
});
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
args.search.append(args.key, String(args.value));
|
|
153
|
+
};
|
|
154
|
+
var appendArrayValue = args => {
|
|
155
|
+
if (args.explode) {
|
|
156
|
+
for (const item of args.value) args.search.append(args.name, String(item));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
args.search.append(args.name, args.value.map(String).join(args.delimiter));
|
|
160
|
+
};
|
|
161
|
+
var appendObjectValue = args => {
|
|
162
|
+
const entries = Object.entries(args.value).filter(([, entryValue]) => {
|
|
163
|
+
return entryValue !== void 0 && entryValue !== null;
|
|
164
|
+
});
|
|
165
|
+
if (args.explode) {
|
|
166
|
+
for (const [property, entryValue] of entries) args.search.append(property, String(entryValue));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
args.search.append(args.name, entries.flatMap(([property, entryValue]) => {
|
|
170
|
+
return [property, String(entryValue)];
|
|
171
|
+
}).join(args.delimiter));
|
|
172
|
+
};
|
|
173
|
+
var appendQueryValue = args => {
|
|
174
|
+
const style = args.param.style ?? "form";
|
|
175
|
+
if (style === "deepObject") {
|
|
176
|
+
appendDeepObject({
|
|
177
|
+
search: args.search,
|
|
178
|
+
key: args.param.name,
|
|
179
|
+
value: args.value
|
|
180
|
+
});
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const serialization = {
|
|
184
|
+
search: args.search,
|
|
185
|
+
name: args.param.name,
|
|
186
|
+
explode: args.param.explode ?? style === "form",
|
|
187
|
+
delimiter: NON_EXPLODED_DELIMITERS[style] ?? ","
|
|
188
|
+
};
|
|
189
|
+
if (Array.isArray(args.value)) {
|
|
190
|
+
appendArrayValue({
|
|
191
|
+
...serialization,
|
|
192
|
+
value: args.value
|
|
193
|
+
});
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (isRecord(args.value)) {
|
|
197
|
+
appendObjectValue({
|
|
198
|
+
...serialization,
|
|
199
|
+
value: args.value
|
|
200
|
+
});
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
args.search.append(args.param.name, String(args.value));
|
|
204
|
+
};
|
|
120
205
|
/**
|
|
121
206
|
* Builds a function that serialises query params into a query string
|
|
122
|
-
* (including the leading `?`)
|
|
123
|
-
*
|
|
207
|
+
* (including the leading `?`), honouring each param's OpenAPI `style` and
|
|
208
|
+
* `explode`. Returns `undefined` when the op has no query params.
|
|
124
209
|
*/
|
|
125
210
|
var buildQueryFn = queryParams => {
|
|
126
211
|
if (queryParams.length === 0) return void 0;
|
|
127
212
|
return args => {
|
|
128
213
|
const search = new URLSearchParams();
|
|
129
|
-
for (const {
|
|
130
|
-
|
|
131
|
-
camelName
|
|
132
|
-
} of queryParams) {
|
|
133
|
-
const value = args[camelName];
|
|
214
|
+
for (const param of queryParams) {
|
|
215
|
+
const value = args[param.camelName];
|
|
134
216
|
if (value === void 0 || value === null) continue;
|
|
135
|
-
|
|
217
|
+
appendQueryValue({
|
|
218
|
+
search,
|
|
219
|
+
param,
|
|
220
|
+
value
|
|
221
|
+
});
|
|
136
222
|
}
|
|
137
223
|
const qs = search.toString();
|
|
138
224
|
return qs ? `?${qs}` : "";
|
|
@@ -186,11 +272,13 @@ var sanitizeDescription = description => {
|
|
|
186
272
|
return (description || "").replace(/'/g, "\\'").replace(/\n/g, " ").trim();
|
|
187
273
|
};
|
|
188
274
|
/**
|
|
189
|
-
*
|
|
190
|
-
*
|
|
275
|
+
* Forwards a property's `oneOf` / `anyOf` / multi-entry `allOf` verbatim, or
|
|
276
|
+
* returns `undefined` when it declares none.
|
|
191
277
|
*/
|
|
192
|
-
var
|
|
193
|
-
const
|
|
278
|
+
var buildComposedProperty = param => {
|
|
279
|
+
const {
|
|
280
|
+
description
|
|
281
|
+
} = param;
|
|
194
282
|
if (param.oneOf && param.oneOf.length > 0) return {
|
|
195
283
|
oneOf: param.oneOf,
|
|
196
284
|
description
|
|
@@ -199,6 +287,25 @@ var buildTypedProperty = param => {
|
|
|
199
287
|
anyOf: param.anyOf,
|
|
200
288
|
description
|
|
201
289
|
};
|
|
290
|
+
if (param.allOf && param.allOf.length > 0) return {
|
|
291
|
+
allOf: param.allOf,
|
|
292
|
+
description
|
|
293
|
+
};
|
|
294
|
+
};
|
|
295
|
+
/**
|
|
296
|
+
* Builds a single body/query property's `JsonSchemaProperty`. Split out of
|
|
297
|
+
* {@link buildInputSchema} to keep that function's size and branching down.
|
|
298
|
+
*/
|
|
299
|
+
var buildTypedProperty = param => {
|
|
300
|
+
const description = sanitizeDescription(param.description);
|
|
301
|
+
const composed = buildComposedProperty({
|
|
302
|
+
...param,
|
|
303
|
+
description
|
|
304
|
+
});
|
|
305
|
+
if (composed) return composed;
|
|
306
|
+
if (param.type === void 0) return {
|
|
307
|
+
description
|
|
308
|
+
};
|
|
202
309
|
const jsonType = getJsonSchemaType(param.type);
|
|
203
310
|
const finalType = param.nullable === true ? [jsonType, "null"] : jsonType;
|
|
204
311
|
if (param.type === "array") return {
|
|
@@ -230,7 +337,7 @@ var buildInputSchema = (pathParams, queryParams, bodyProps) => {
|
|
|
230
337
|
return p.camelName;
|
|
231
338
|
})];
|
|
232
339
|
const properties = {};
|
|
233
|
-
for (const param of allParams) properties[param.camelName] = "
|
|
340
|
+
for (const param of allParams) properties[param.camelName] = "description" in param ? buildTypedProperty(param) : {
|
|
234
341
|
type: "string",
|
|
235
342
|
description: ""
|
|
236
343
|
};
|
|
@@ -275,7 +382,9 @@ var extractQueryParams = args => {
|
|
|
275
382
|
camelName: snakeToCamel(p.name || ""),
|
|
276
383
|
description: p.description || "",
|
|
277
384
|
required: p.required || false,
|
|
278
|
-
type: p.schema?.type || "string"
|
|
385
|
+
type: p.schema?.type || "string",
|
|
386
|
+
style: p.style,
|
|
387
|
+
explode: p.explode
|
|
279
388
|
};
|
|
280
389
|
}));
|
|
281
390
|
};
|
|
@@ -291,23 +400,47 @@ var extractAcceptedBodyFields = args => {
|
|
|
291
400
|
const bodySchema = resolveBodySchema(args);
|
|
292
401
|
return Object.keys(bodySchema?.properties ?? {});
|
|
293
402
|
};
|
|
403
|
+
var isPlainObject = value => {
|
|
404
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
405
|
+
};
|
|
406
|
+
/**
|
|
407
|
+
* Folds a single-entry `allOf` into the property that wraps it. OpenAPI
|
|
408
|
+
* declares `allOf: [{ $ref }]` so a property can carry its own `description`
|
|
409
|
+
* next to a referenced schema; without folding, the referenced `type`,
|
|
410
|
+
* `nullable` and `items` would be lost. Keys on the wrapper win over the
|
|
411
|
+
* referenced schema's. Multi-entry `allOf` is left intact.
|
|
412
|
+
*/
|
|
413
|
+
var flattenSingleAllOf = schema => {
|
|
414
|
+
const {
|
|
415
|
+
allOf,
|
|
416
|
+
...rest
|
|
417
|
+
} = schema;
|
|
418
|
+
if (!Array.isArray(allOf) || allOf.length !== 1) return schema;
|
|
419
|
+
const [entry] = allOf;
|
|
420
|
+
if (!isPlainObject(entry)) return rest;
|
|
421
|
+
return {
|
|
422
|
+
...flattenSingleAllOf(entry),
|
|
423
|
+
...rest
|
|
424
|
+
};
|
|
425
|
+
};
|
|
294
426
|
var extractBodyProps = args => {
|
|
295
427
|
const bodySchema = resolveBodySchema(args);
|
|
296
428
|
if (!bodySchema?.properties) return [];
|
|
297
429
|
return Object.entries(bodySchema.properties).filter(([, value]) => {
|
|
298
430
|
return !value[args.serverManagedExtension];
|
|
299
431
|
}).map(([key, value]) => {
|
|
300
|
-
const val = value;
|
|
432
|
+
const val = flattenSingleAllOf(value);
|
|
301
433
|
return {
|
|
302
434
|
snakeName: key,
|
|
303
435
|
camelName: snakeToCamel(key),
|
|
304
436
|
description: typeof val.description === "string" ? val.description : "",
|
|
305
437
|
required: (bodySchema.required || []).includes(key),
|
|
306
|
-
type: typeof val.type === "string" ? val.type :
|
|
438
|
+
type: typeof val.type === "string" ? val.type : void 0,
|
|
307
439
|
items: val.items,
|
|
308
440
|
nullable: val.nullable === true,
|
|
309
441
|
oneOf: Array.isArray(val.oneOf) ? val.oneOf : void 0,
|
|
310
|
-
anyOf: Array.isArray(val.anyOf) ? val.anyOf : void 0
|
|
442
|
+
anyOf: Array.isArray(val.anyOf) ? val.anyOf : void 0,
|
|
443
|
+
allOf: Array.isArray(val.allOf) ? val.allOf : void 0
|
|
311
444
|
};
|
|
312
445
|
});
|
|
313
446
|
};
|
package/dist/index.d.cts
CHANGED
|
@@ -13,7 +13,9 @@ type JsonSchemaPrimitiveType = 'string' | 'number' | 'boolean' | 'array' | 'inte
|
|
|
13
13
|
* client-side validator enforces the same alternatives the REST API does,
|
|
14
14
|
* rather than collapsing to a single guessed primitive. `type` may be an
|
|
15
15
|
* array of two entries (e.g. `['string', 'null']`) to represent an OpenAPI
|
|
16
|
-
* `nullable: true` property without losing its declared type.
|
|
16
|
+
* `nullable: true` property without losing its declared type. A property with
|
|
17
|
+
* no declared type is emitted untyped (only `description`), so it accepts any
|
|
18
|
+
* value instead of a guessed `string`.
|
|
17
19
|
*/
|
|
18
20
|
type JsonSchemaProperty = {
|
|
19
21
|
type: JsonSchemaPrimitiveType | JsonSchemaPrimitiveType[];
|
|
@@ -22,6 +24,7 @@ type JsonSchemaProperty = {
|
|
|
22
24
|
} | {
|
|
23
25
|
oneOf?: unknown[];
|
|
24
26
|
anyOf?: unknown[];
|
|
27
|
+
allOf?: unknown[];
|
|
25
28
|
description?: string;
|
|
26
29
|
};
|
|
27
30
|
/**
|
|
@@ -93,7 +96,9 @@ interface OperationSpec {
|
|
|
93
96
|
name?: string;
|
|
94
97
|
in?: string;
|
|
95
98
|
required?: boolean;
|
|
96
|
-
description?: string;
|
|
99
|
+
description?: string; /** Query serialisation style, e.g. `form` (default) or `deepObject`. */
|
|
100
|
+
style?: string; /** Whether each value gets its own key. Defaults to `true` for `form`. */
|
|
101
|
+
explode?: boolean;
|
|
97
102
|
schema?: {
|
|
98
103
|
type?: string;
|
|
99
104
|
items?: {
|
|
@@ -212,6 +217,8 @@ declare const resolveParameter: (param: Record<string, unknown> | undefined, spe
|
|
|
212
217
|
in?: string;
|
|
213
218
|
required?: boolean;
|
|
214
219
|
description?: string;
|
|
220
|
+
style?: string;
|
|
221
|
+
explode?: boolean;
|
|
215
222
|
schema?: {
|
|
216
223
|
type?: string;
|
|
217
224
|
items?: {
|
|
@@ -224,15 +231,19 @@ declare const buildPathFn: (pathTemplate: string, pathParams: Array<{
|
|
|
224
231
|
name: string;
|
|
225
232
|
camelName: string;
|
|
226
233
|
}>) => ((args: Record<string, unknown>) => string);
|
|
234
|
+
/** Query parameter with the OpenAPI serialisation rules declared for it. */
|
|
235
|
+
type QueryParamSerialization = {
|
|
236
|
+
name: string;
|
|
237
|
+
camelName: string; /** OpenAPI `style` (`form`, `deepObject`, `spaceDelimited`, `pipeDelimited`). */
|
|
238
|
+
style?: string; /** OpenAPI `explode`. Defaults to `true` for `form`, `false` otherwise. */
|
|
239
|
+
explode?: boolean;
|
|
240
|
+
};
|
|
227
241
|
/**
|
|
228
242
|
* Builds a function that serialises query params into a query string
|
|
229
|
-
* (including the leading `?`)
|
|
230
|
-
*
|
|
243
|
+
* (including the leading `?`), honouring each param's OpenAPI `style` and
|
|
244
|
+
* `explode`. Returns `undefined` when the op has no query params.
|
|
231
245
|
*/
|
|
232
|
-
declare const buildQueryFn: (queryParams:
|
|
233
|
-
name: string;
|
|
234
|
-
camelName: string;
|
|
235
|
-
}>) => ((args: Record<string, unknown>) => string) | undefined;
|
|
246
|
+
declare const buildQueryFn: (queryParams: QueryParamSerialization[]) => ((args: Record<string, unknown>) => string) | undefined;
|
|
236
247
|
/**
|
|
237
248
|
* Builds a function that maps camelCase args back to a snake_case request
|
|
238
249
|
* body, skipping `undefined` args. Returns `undefined` when the op has no body.
|
|
@@ -266,11 +277,12 @@ declare const buildInputSchema: (pathParams: Array<{
|
|
|
266
277
|
camelName: string;
|
|
267
278
|
description: string;
|
|
268
279
|
required: boolean;
|
|
269
|
-
type
|
|
280
|
+
type?: string;
|
|
270
281
|
items?: unknown;
|
|
271
282
|
nullable?: boolean;
|
|
272
283
|
oneOf?: unknown[];
|
|
273
284
|
anyOf?: unknown[];
|
|
285
|
+
allOf?: unknown[];
|
|
274
286
|
}>) => JsonObjectSchema;
|
|
275
287
|
declare const extractPathParams: (args: {
|
|
276
288
|
parameters?: Array<{
|
|
@@ -296,6 +308,8 @@ declare const extractQueryParams: (args: {
|
|
|
296
308
|
description: string;
|
|
297
309
|
required: boolean;
|
|
298
310
|
type: string;
|
|
311
|
+
style?: string;
|
|
312
|
+
explode?: boolean;
|
|
299
313
|
}>;
|
|
300
314
|
/**
|
|
301
315
|
* snake_case names of every top-level property an operation's request schema
|
|
@@ -314,11 +328,12 @@ declare const extractBodyProps: (args: {
|
|
|
314
328
|
camelName: string;
|
|
315
329
|
description: string;
|
|
316
330
|
required: boolean;
|
|
317
|
-
type
|
|
331
|
+
type?: string;
|
|
318
332
|
items?: unknown;
|
|
319
333
|
nullable: boolean;
|
|
320
334
|
oneOf?: unknown[];
|
|
321
335
|
anyOf?: unknown[];
|
|
336
|
+
allOf?: unknown[];
|
|
322
337
|
}>;
|
|
323
338
|
declare const processOperation: (args: {
|
|
324
339
|
pathTemplate: string;
|
|
@@ -360,4 +375,4 @@ declare const openApiToToolDefinitions: (args: {
|
|
|
360
375
|
options?: OpenApiToToolsOptions;
|
|
361
376
|
}) => ToolDefinition[];
|
|
362
377
|
//#endregion
|
|
363
|
-
export { DEFAULT_EXCLUDE_EXTENSION, DEFAULT_SERVER_MANAGED_EXTENSION, type JsonSchemaProperty, type OpenApiSpec, type OpenApiToToolsOptions, type OperationSpec, type RegisterOpenApiToolsArgs, type RequestBodySpec, type ResolvedRequest, type ToolDefinition, buildBodyFn, buildInputSchema, buildPathFn, buildQueryFn, dereferenceSchema, extractAcceptedBodyFields, extractBodyProps, extractPathParams, extractQueryParams, getJsonSchemaType, openApiToToolDefinitions, operationIdToToolName, processOperation, processPath, registerOpenApiTools, resolveParameter, resolveSchema, snakeToCamel };
|
|
378
|
+
export { DEFAULT_EXCLUDE_EXTENSION, DEFAULT_SERVER_MANAGED_EXTENSION, type JsonSchemaProperty, type OpenApiSpec, type OpenApiToToolsOptions, type OperationSpec, type QueryParamSerialization, type RegisterOpenApiToolsArgs, type RequestBodySpec, type ResolvedRequest, type ToolDefinition, buildBodyFn, buildInputSchema, buildPathFn, buildQueryFn, dereferenceSchema, extractAcceptedBodyFields, extractBodyProps, extractPathParams, extractQueryParams, getJsonSchemaType, openApiToToolDefinitions, operationIdToToolName, processOperation, processPath, registerOpenApiTools, resolveParameter, resolveSchema, snakeToCamel };
|
package/dist/index.d.mts
CHANGED
|
@@ -13,7 +13,9 @@ type JsonSchemaPrimitiveType = 'string' | 'number' | 'boolean' | 'array' | 'inte
|
|
|
13
13
|
* client-side validator enforces the same alternatives the REST API does,
|
|
14
14
|
* rather than collapsing to a single guessed primitive. `type` may be an
|
|
15
15
|
* array of two entries (e.g. `['string', 'null']`) to represent an OpenAPI
|
|
16
|
-
* `nullable: true` property without losing its declared type.
|
|
16
|
+
* `nullable: true` property without losing its declared type. A property with
|
|
17
|
+
* no declared type is emitted untyped (only `description`), so it accepts any
|
|
18
|
+
* value instead of a guessed `string`.
|
|
17
19
|
*/
|
|
18
20
|
type JsonSchemaProperty = {
|
|
19
21
|
type: JsonSchemaPrimitiveType | JsonSchemaPrimitiveType[];
|
|
@@ -22,6 +24,7 @@ type JsonSchemaProperty = {
|
|
|
22
24
|
} | {
|
|
23
25
|
oneOf?: unknown[];
|
|
24
26
|
anyOf?: unknown[];
|
|
27
|
+
allOf?: unknown[];
|
|
25
28
|
description?: string;
|
|
26
29
|
};
|
|
27
30
|
/**
|
|
@@ -93,7 +96,9 @@ interface OperationSpec {
|
|
|
93
96
|
name?: string;
|
|
94
97
|
in?: string;
|
|
95
98
|
required?: boolean;
|
|
96
|
-
description?: string;
|
|
99
|
+
description?: string; /** Query serialisation style, e.g. `form` (default) or `deepObject`. */
|
|
100
|
+
style?: string; /** Whether each value gets its own key. Defaults to `true` for `form`. */
|
|
101
|
+
explode?: boolean;
|
|
97
102
|
schema?: {
|
|
98
103
|
type?: string;
|
|
99
104
|
items?: {
|
|
@@ -212,6 +217,8 @@ declare const resolveParameter: (param: Record<string, unknown> | undefined, spe
|
|
|
212
217
|
in?: string;
|
|
213
218
|
required?: boolean;
|
|
214
219
|
description?: string;
|
|
220
|
+
style?: string;
|
|
221
|
+
explode?: boolean;
|
|
215
222
|
schema?: {
|
|
216
223
|
type?: string;
|
|
217
224
|
items?: {
|
|
@@ -224,15 +231,19 @@ declare const buildPathFn: (pathTemplate: string, pathParams: Array<{
|
|
|
224
231
|
name: string;
|
|
225
232
|
camelName: string;
|
|
226
233
|
}>) => ((args: Record<string, unknown>) => string);
|
|
234
|
+
/** Query parameter with the OpenAPI serialisation rules declared for it. */
|
|
235
|
+
type QueryParamSerialization = {
|
|
236
|
+
name: string;
|
|
237
|
+
camelName: string; /** OpenAPI `style` (`form`, `deepObject`, `spaceDelimited`, `pipeDelimited`). */
|
|
238
|
+
style?: string; /** OpenAPI `explode`. Defaults to `true` for `form`, `false` otherwise. */
|
|
239
|
+
explode?: boolean;
|
|
240
|
+
};
|
|
227
241
|
/**
|
|
228
242
|
* Builds a function that serialises query params into a query string
|
|
229
|
-
* (including the leading `?`)
|
|
230
|
-
*
|
|
243
|
+
* (including the leading `?`), honouring each param's OpenAPI `style` and
|
|
244
|
+
* `explode`. Returns `undefined` when the op has no query params.
|
|
231
245
|
*/
|
|
232
|
-
declare const buildQueryFn: (queryParams:
|
|
233
|
-
name: string;
|
|
234
|
-
camelName: string;
|
|
235
|
-
}>) => ((args: Record<string, unknown>) => string) | undefined;
|
|
246
|
+
declare const buildQueryFn: (queryParams: QueryParamSerialization[]) => ((args: Record<string, unknown>) => string) | undefined;
|
|
236
247
|
/**
|
|
237
248
|
* Builds a function that maps camelCase args back to a snake_case request
|
|
238
249
|
* body, skipping `undefined` args. Returns `undefined` when the op has no body.
|
|
@@ -266,11 +277,12 @@ declare const buildInputSchema: (pathParams: Array<{
|
|
|
266
277
|
camelName: string;
|
|
267
278
|
description: string;
|
|
268
279
|
required: boolean;
|
|
269
|
-
type
|
|
280
|
+
type?: string;
|
|
270
281
|
items?: unknown;
|
|
271
282
|
nullable?: boolean;
|
|
272
283
|
oneOf?: unknown[];
|
|
273
284
|
anyOf?: unknown[];
|
|
285
|
+
allOf?: unknown[];
|
|
274
286
|
}>) => JsonObjectSchema;
|
|
275
287
|
declare const extractPathParams: (args: {
|
|
276
288
|
parameters?: Array<{
|
|
@@ -296,6 +308,8 @@ declare const extractQueryParams: (args: {
|
|
|
296
308
|
description: string;
|
|
297
309
|
required: boolean;
|
|
298
310
|
type: string;
|
|
311
|
+
style?: string;
|
|
312
|
+
explode?: boolean;
|
|
299
313
|
}>;
|
|
300
314
|
/**
|
|
301
315
|
* snake_case names of every top-level property an operation's request schema
|
|
@@ -314,11 +328,12 @@ declare const extractBodyProps: (args: {
|
|
|
314
328
|
camelName: string;
|
|
315
329
|
description: string;
|
|
316
330
|
required: boolean;
|
|
317
|
-
type
|
|
331
|
+
type?: string;
|
|
318
332
|
items?: unknown;
|
|
319
333
|
nullable: boolean;
|
|
320
334
|
oneOf?: unknown[];
|
|
321
335
|
anyOf?: unknown[];
|
|
336
|
+
allOf?: unknown[];
|
|
322
337
|
}>;
|
|
323
338
|
declare const processOperation: (args: {
|
|
324
339
|
pathTemplate: string;
|
|
@@ -360,4 +375,4 @@ declare const openApiToToolDefinitions: (args: {
|
|
|
360
375
|
options?: OpenApiToToolsOptions;
|
|
361
376
|
}) => ToolDefinition[];
|
|
362
377
|
//#endregion
|
|
363
|
-
export { DEFAULT_EXCLUDE_EXTENSION, DEFAULT_SERVER_MANAGED_EXTENSION, type JsonSchemaProperty, type OpenApiSpec, type OpenApiToToolsOptions, type OperationSpec, type RegisterOpenApiToolsArgs, type RequestBodySpec, type ResolvedRequest, type ToolDefinition, buildBodyFn, buildInputSchema, buildPathFn, buildQueryFn, dereferenceSchema, extractAcceptedBodyFields, extractBodyProps, extractPathParams, extractQueryParams, getJsonSchemaType, openApiToToolDefinitions, operationIdToToolName, processOperation, processPath, registerOpenApiTools, resolveParameter, resolveSchema, snakeToCamel };
|
|
378
|
+
export { DEFAULT_EXCLUDE_EXTENSION, DEFAULT_SERVER_MANAGED_EXTENSION, type JsonSchemaProperty, type OpenApiSpec, type OpenApiToToolsOptions, type OperationSpec, type QueryParamSerialization, type RegisterOpenApiToolsArgs, type RequestBodySpec, type ResolvedRequest, type ToolDefinition, buildBodyFn, buildInputSchema, buildPathFn, buildQueryFn, dereferenceSchema, extractAcceptedBodyFields, extractBodyProps, extractPathParams, extractQueryParams, getJsonSchemaType, openApiToToolDefinitions, operationIdToToolName, processOperation, processPath, registerOpenApiTools, resolveParameter, resolveSchema, snakeToCamel };
|
package/dist/index.mjs
CHANGED
|
@@ -114,22 +114,108 @@ var buildPathFn = (pathTemplate, pathParams) => {
|
|
|
114
114
|
return result;
|
|
115
115
|
};
|
|
116
116
|
};
|
|
117
|
+
var NON_EXPLODED_DELIMITERS = {
|
|
118
|
+
form: ",",
|
|
119
|
+
spaceDelimited: " ",
|
|
120
|
+
pipeDelimited: "|"
|
|
121
|
+
};
|
|
122
|
+
var isRecord = value => {
|
|
123
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Appends a `deepObject` value as bracketed keys — `filters[documentId][$eq]`.
|
|
127
|
+
* OpenAPI only defines one level, but the APIs that ask for `deepObject`
|
|
128
|
+
* (Strapi, Directus) nest, so nested objects and arrays recurse instead of
|
|
129
|
+
* being stringified into `[object Object]`.
|
|
130
|
+
*/
|
|
131
|
+
var appendDeepObject = args => {
|
|
132
|
+
if (args.value === void 0 || args.value === null) return;
|
|
133
|
+
if (Array.isArray(args.value)) {
|
|
134
|
+
for (const [index, item] of args.value.entries()) appendDeepObject({
|
|
135
|
+
search: args.search,
|
|
136
|
+
key: `${args.key}[${index}]`,
|
|
137
|
+
value: item
|
|
138
|
+
});
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (isRecord(args.value)) {
|
|
142
|
+
for (const [property, propertyValue] of Object.entries(args.value)) appendDeepObject({
|
|
143
|
+
search: args.search,
|
|
144
|
+
key: `${args.key}[${property}]`,
|
|
145
|
+
value: propertyValue
|
|
146
|
+
});
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
args.search.append(args.key, String(args.value));
|
|
150
|
+
};
|
|
151
|
+
var appendArrayValue = args => {
|
|
152
|
+
if (args.explode) {
|
|
153
|
+
for (const item of args.value) args.search.append(args.name, String(item));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
args.search.append(args.name, args.value.map(String).join(args.delimiter));
|
|
157
|
+
};
|
|
158
|
+
var appendObjectValue = args => {
|
|
159
|
+
const entries = Object.entries(args.value).filter(([, entryValue]) => {
|
|
160
|
+
return entryValue !== void 0 && entryValue !== null;
|
|
161
|
+
});
|
|
162
|
+
if (args.explode) {
|
|
163
|
+
for (const [property, entryValue] of entries) args.search.append(property, String(entryValue));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
args.search.append(args.name, entries.flatMap(([property, entryValue]) => {
|
|
167
|
+
return [property, String(entryValue)];
|
|
168
|
+
}).join(args.delimiter));
|
|
169
|
+
};
|
|
170
|
+
var appendQueryValue = args => {
|
|
171
|
+
const style = args.param.style ?? "form";
|
|
172
|
+
if (style === "deepObject") {
|
|
173
|
+
appendDeepObject({
|
|
174
|
+
search: args.search,
|
|
175
|
+
key: args.param.name,
|
|
176
|
+
value: args.value
|
|
177
|
+
});
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const serialization = {
|
|
181
|
+
search: args.search,
|
|
182
|
+
name: args.param.name,
|
|
183
|
+
explode: args.param.explode ?? style === "form",
|
|
184
|
+
delimiter: NON_EXPLODED_DELIMITERS[style] ?? ","
|
|
185
|
+
};
|
|
186
|
+
if (Array.isArray(args.value)) {
|
|
187
|
+
appendArrayValue({
|
|
188
|
+
...serialization,
|
|
189
|
+
value: args.value
|
|
190
|
+
});
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (isRecord(args.value)) {
|
|
194
|
+
appendObjectValue({
|
|
195
|
+
...serialization,
|
|
196
|
+
value: args.value
|
|
197
|
+
});
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
args.search.append(args.param.name, String(args.value));
|
|
201
|
+
};
|
|
117
202
|
/**
|
|
118
203
|
* Builds a function that serialises query params into a query string
|
|
119
|
-
* (including the leading `?`)
|
|
120
|
-
*
|
|
204
|
+
* (including the leading `?`), honouring each param's OpenAPI `style` and
|
|
205
|
+
* `explode`. Returns `undefined` when the op has no query params.
|
|
121
206
|
*/
|
|
122
207
|
var buildQueryFn = queryParams => {
|
|
123
208
|
if (queryParams.length === 0) return void 0;
|
|
124
209
|
return args => {
|
|
125
210
|
const search = new URLSearchParams();
|
|
126
|
-
for (const {
|
|
127
|
-
|
|
128
|
-
camelName
|
|
129
|
-
} of queryParams) {
|
|
130
|
-
const value = args[camelName];
|
|
211
|
+
for (const param of queryParams) {
|
|
212
|
+
const value = args[param.camelName];
|
|
131
213
|
if (value === void 0 || value === null) continue;
|
|
132
|
-
|
|
214
|
+
appendQueryValue({
|
|
215
|
+
search,
|
|
216
|
+
param,
|
|
217
|
+
value
|
|
218
|
+
});
|
|
133
219
|
}
|
|
134
220
|
const qs = search.toString();
|
|
135
221
|
return qs ? `?${qs}` : "";
|
|
@@ -183,11 +269,13 @@ var sanitizeDescription = description => {
|
|
|
183
269
|
return (description || "").replace(/'/g, "\\'").replace(/\n/g, " ").trim();
|
|
184
270
|
};
|
|
185
271
|
/**
|
|
186
|
-
*
|
|
187
|
-
*
|
|
272
|
+
* Forwards a property's `oneOf` / `anyOf` / multi-entry `allOf` verbatim, or
|
|
273
|
+
* returns `undefined` when it declares none.
|
|
188
274
|
*/
|
|
189
|
-
var
|
|
190
|
-
const
|
|
275
|
+
var buildComposedProperty = param => {
|
|
276
|
+
const {
|
|
277
|
+
description
|
|
278
|
+
} = param;
|
|
191
279
|
if (param.oneOf && param.oneOf.length > 0) return {
|
|
192
280
|
oneOf: param.oneOf,
|
|
193
281
|
description
|
|
@@ -196,6 +284,25 @@ var buildTypedProperty = param => {
|
|
|
196
284
|
anyOf: param.anyOf,
|
|
197
285
|
description
|
|
198
286
|
};
|
|
287
|
+
if (param.allOf && param.allOf.length > 0) return {
|
|
288
|
+
allOf: param.allOf,
|
|
289
|
+
description
|
|
290
|
+
};
|
|
291
|
+
};
|
|
292
|
+
/**
|
|
293
|
+
* Builds a single body/query property's `JsonSchemaProperty`. Split out of
|
|
294
|
+
* {@link buildInputSchema} to keep that function's size and branching down.
|
|
295
|
+
*/
|
|
296
|
+
var buildTypedProperty = param => {
|
|
297
|
+
const description = sanitizeDescription(param.description);
|
|
298
|
+
const composed = buildComposedProperty({
|
|
299
|
+
...param,
|
|
300
|
+
description
|
|
301
|
+
});
|
|
302
|
+
if (composed) return composed;
|
|
303
|
+
if (param.type === void 0) return {
|
|
304
|
+
description
|
|
305
|
+
};
|
|
199
306
|
const jsonType = getJsonSchemaType(param.type);
|
|
200
307
|
const finalType = param.nullable === true ? [jsonType, "null"] : jsonType;
|
|
201
308
|
if (param.type === "array") return {
|
|
@@ -227,7 +334,7 @@ var buildInputSchema = (pathParams, queryParams, bodyProps) => {
|
|
|
227
334
|
return p.camelName;
|
|
228
335
|
})];
|
|
229
336
|
const properties = {};
|
|
230
|
-
for (const param of allParams) properties[param.camelName] = "
|
|
337
|
+
for (const param of allParams) properties[param.camelName] = "description" in param ? buildTypedProperty(param) : {
|
|
231
338
|
type: "string",
|
|
232
339
|
description: ""
|
|
233
340
|
};
|
|
@@ -272,7 +379,9 @@ var extractQueryParams = args => {
|
|
|
272
379
|
camelName: snakeToCamel(p.name || ""),
|
|
273
380
|
description: p.description || "",
|
|
274
381
|
required: p.required || false,
|
|
275
|
-
type: p.schema?.type || "string"
|
|
382
|
+
type: p.schema?.type || "string",
|
|
383
|
+
style: p.style,
|
|
384
|
+
explode: p.explode
|
|
276
385
|
};
|
|
277
386
|
}));
|
|
278
387
|
};
|
|
@@ -288,23 +397,47 @@ var extractAcceptedBodyFields = args => {
|
|
|
288
397
|
const bodySchema = resolveBodySchema(args);
|
|
289
398
|
return Object.keys(bodySchema?.properties ?? {});
|
|
290
399
|
};
|
|
400
|
+
var isPlainObject = value => {
|
|
401
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
402
|
+
};
|
|
403
|
+
/**
|
|
404
|
+
* Folds a single-entry `allOf` into the property that wraps it. OpenAPI
|
|
405
|
+
* declares `allOf: [{ $ref }]` so a property can carry its own `description`
|
|
406
|
+
* next to a referenced schema; without folding, the referenced `type`,
|
|
407
|
+
* `nullable` and `items` would be lost. Keys on the wrapper win over the
|
|
408
|
+
* referenced schema's. Multi-entry `allOf` is left intact.
|
|
409
|
+
*/
|
|
410
|
+
var flattenSingleAllOf = schema => {
|
|
411
|
+
const {
|
|
412
|
+
allOf,
|
|
413
|
+
...rest
|
|
414
|
+
} = schema;
|
|
415
|
+
if (!Array.isArray(allOf) || allOf.length !== 1) return schema;
|
|
416
|
+
const [entry] = allOf;
|
|
417
|
+
if (!isPlainObject(entry)) return rest;
|
|
418
|
+
return {
|
|
419
|
+
...flattenSingleAllOf(entry),
|
|
420
|
+
...rest
|
|
421
|
+
};
|
|
422
|
+
};
|
|
291
423
|
var extractBodyProps = args => {
|
|
292
424
|
const bodySchema = resolveBodySchema(args);
|
|
293
425
|
if (!bodySchema?.properties) return [];
|
|
294
426
|
return Object.entries(bodySchema.properties).filter(([, value]) => {
|
|
295
427
|
return !value[args.serverManagedExtension];
|
|
296
428
|
}).map(([key, value]) => {
|
|
297
|
-
const val = value;
|
|
429
|
+
const val = flattenSingleAllOf(value);
|
|
298
430
|
return {
|
|
299
431
|
snakeName: key,
|
|
300
432
|
camelName: snakeToCamel(key),
|
|
301
433
|
description: typeof val.description === "string" ? val.description : "",
|
|
302
434
|
required: (bodySchema.required || []).includes(key),
|
|
303
|
-
type: typeof val.type === "string" ? val.type :
|
|
435
|
+
type: typeof val.type === "string" ? val.type : void 0,
|
|
304
436
|
items: val.items,
|
|
305
437
|
nullable: val.nullable === true,
|
|
306
438
|
oneOf: Array.isArray(val.oneOf) ? val.oneOf : void 0,
|
|
307
|
-
anyOf: Array.isArray(val.anyOf) ? val.anyOf : void 0
|
|
439
|
+
anyOf: Array.isArray(val.anyOf) ? val.anyOf : void 0,
|
|
440
|
+
allOf: Array.isArray(val.allOf) ? val.allOf : void 0
|
|
308
441
|
};
|
|
309
442
|
});
|
|
310
443
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttoss/http-server-mcp-openapi",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.15",
|
|
4
4
|
"description": "Generate Model Context Protocol (MCP) tools from an OpenAPI specification for @ttoss/http-server-mcp",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"jest": "^30.4.2",
|
|
43
43
|
"supertest": "^7.2.2",
|
|
44
44
|
"tsdown": "^0.22.2",
|
|
45
|
-
"@ttoss/
|
|
46
|
-
"@ttoss/
|
|
45
|
+
"@ttoss/config": "^1.38.0",
|
|
46
|
+
"@ttoss/http-server": "^0.8.1"
|
|
47
47
|
},
|
|
48
48
|
"publishConfig": {
|
|
49
49
|
"access": "public",
|