@ttoss/http-server-mcp-openapi 0.3.0 → 0.5.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 +22 -5
- package/dist/index.cjs +266 -76
- package/dist/index.d.cts +59 -31
- package/dist/index.d.mts +59 -31
- package/dist/index.mjs +266 -77
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -69,6 +69,10 @@ body property declared as a single-entry `allOf` (usually `allOf: [{ $ref }]`
|
|
|
69
69
|
beside its own `description`) takes `type`, `nullable` and `items` from the
|
|
70
70
|
referenced schema; a multi-entry `allOf` is forwarded verbatim, and a property
|
|
71
71
|
with no declared type is advertised untyped so it accepts any value.
|
|
72
|
+
OpenAPI's `nullable` becomes JSON Schema at every depth — inside `items`,
|
|
73
|
+
`properties`, `additionalProperties` and `oneOf` / `anyOf` / `allOf`
|
|
74
|
+
alternatives: `nullable: true` adds `'null'` to the `type`, and the keyword is
|
|
75
|
+
dropped.
|
|
72
76
|
Parameters declared at the **path-item level** (shared by every operation on a
|
|
73
77
|
path) are merged into each operation; an operation-level parameter overrides a
|
|
74
78
|
path-item one with the same `name`+`in`.
|
|
@@ -130,7 +134,7 @@ registerOpenApiTools({
|
|
|
130
134
|
callApi,
|
|
131
135
|
options: {
|
|
132
136
|
excludeExtension: 'x-mcp-exclude', // operations flagged truthy are skipped
|
|
133
|
-
serverManagedExtension: 'x-mcp-server-managed', //
|
|
137
|
+
serverManagedExtension: 'x-mcp-server-managed', // or several: ['x-a', 'x-b']
|
|
134
138
|
argumentNames: 'camelCase', // or 'verbatim'
|
|
135
139
|
documents: { './tags.yaml': tagsDocument }, // targets of cross-file $refs
|
|
136
140
|
},
|
|
@@ -139,8 +143,8 @@ registerOpenApiTools({
|
|
|
139
143
|
|
|
140
144
|
- **`excludeExtension`** (default `x-mcp-exclude`) — an operation with this
|
|
141
145
|
extension set truthy is omitted from the tool surface.
|
|
142
|
-
- **`serverManagedExtension`** (default `x-mcp-server-managed`) —
|
|
143
|
-
[Server-managed values](#server-managed-values).
|
|
146
|
+
- **`serverManagedExtension`** (default `x-mcp-server-managed`) — one name or
|
|
147
|
+
an array of names; see [Server-managed values](#server-managed-values).
|
|
144
148
|
- **`argumentNames`** (default `camelCase`) — `verbatim` keeps the spec's
|
|
145
149
|
parameter and property names as tool argument names.
|
|
146
150
|
- **`documents`** — sibling documents for `$ref`s with a file part, keyed by
|
|
@@ -153,8 +157,9 @@ registerOpenApiTools({
|
|
|
153
157
|
|
|
154
158
|
A value flagged with `serverManagedExtension` is never offered to the model:
|
|
155
159
|
|
|
156
|
-
- A **request-body property** is hidden from `inputSchema` and
|
|
157
|
-
API sets it itself). It still appears in
|
|
160
|
+
- A **request-body property** is hidden from `inputSchema` and, unless pinned,
|
|
161
|
+
never sent (the API sets it itself). It still appears in
|
|
162
|
+
`acceptedBodyFields`.
|
|
158
163
|
- A **path or query parameter** is hidden from `inputSchema` and listed in
|
|
159
164
|
`tool.serverManagedParameters`. `registerOpenApiTools` discards anything the
|
|
160
165
|
model sent for it and fills it from `serverParameters`, keyed by spec name:
|
|
@@ -173,6 +178,18 @@ registerOpenApiTools({
|
|
|
173
178
|
With `openApiToToolDefinitions`, set each entry's `argName` in the args before
|
|
174
179
|
calling `tool.path` / `tool.query`.
|
|
175
180
|
|
|
181
|
+
A **string** extension value pins the parameter: `wait` declared with
|
|
182
|
+
`x-mcp-server-managed: 'true'` is always sent as `wait=true`. `tool.path` and
|
|
183
|
+
`tool.query` apply pinned values themselves, over anything in the args or
|
|
184
|
+
`serverParameters`, and the entry in `serverManagedParameters` carries it as
|
|
185
|
+
`value`.
|
|
186
|
+
|
|
187
|
+
A pinned **request-body property** is sent by `tool.body`, over anything in the
|
|
188
|
+
args, as the JSON type its schema declares: `'true'` on a `boolean` is `true`,
|
|
189
|
+
`'3'` on an `integer` is `3`. A pin its type cannot hold (`'yes'` on a
|
|
190
|
+
`boolean`, any pin on an `object`, `array` or untyped property) throws while
|
|
191
|
+
the tools are generated, naming the operation and property.
|
|
192
|
+
|
|
176
193
|
### Reading custom extensions
|
|
177
194
|
|
|
178
195
|
Every `x-` prefixed extension on an operation is forwarded verbatim on
|
package/dist/index.cjs
CHANGED
|
@@ -319,6 +319,136 @@ var buildBodyFn = bodyProps => {
|
|
|
319
319
|
};
|
|
320
320
|
};
|
|
321
321
|
|
|
322
|
+
//#endregion
|
|
323
|
+
//#region src/serverManaged.ts
|
|
324
|
+
/**
|
|
325
|
+
* Whether a parameter or body property carries one of the server-managed
|
|
326
|
+
* extensions, and the value it pins when the extension is a string.
|
|
327
|
+
*
|
|
328
|
+
* A string pins the value (`x-mcp-server-managed: 'true'` always sends
|
|
329
|
+
* `true`); any other truthy value only hides it. The first matching name wins.
|
|
330
|
+
*/
|
|
331
|
+
var readServerManaged = args => {
|
|
332
|
+
const names = Array.isArray(args.extension) ? args.extension : [args.extension];
|
|
333
|
+
for (const name of names) {
|
|
334
|
+
const flag = args.node[name];
|
|
335
|
+
if (typeof flag === "string") return {
|
|
336
|
+
managed: true,
|
|
337
|
+
value: flag
|
|
338
|
+
};
|
|
339
|
+
if (flag) return {
|
|
340
|
+
managed: true
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
managed: false
|
|
345
|
+
};
|
|
346
|
+
};
|
|
347
|
+
/** The args each pinned parameter is always sent with, keyed by `argName`. */
|
|
348
|
+
var pinnedArgs = parameters => {
|
|
349
|
+
const pinned = {};
|
|
350
|
+
for (const parameter of parameters) if (parameter.value !== void 0) pinned[parameter.argName] = parameter.value;
|
|
351
|
+
return pinned;
|
|
352
|
+
};
|
|
353
|
+
/**
|
|
354
|
+
* Wraps a `path` / `query` builder so pinned values replace whatever the args
|
|
355
|
+
* carry. Applied in the builder itself, so every consumer of the tool
|
|
356
|
+
* definition sends them, not only `registerOpenApiTools`.
|
|
357
|
+
*/
|
|
358
|
+
var withPinned = args => {
|
|
359
|
+
const {
|
|
360
|
+
build,
|
|
361
|
+
pinned
|
|
362
|
+
} = args;
|
|
363
|
+
if (!build || Object.keys(pinned).length === 0) return build;
|
|
364
|
+
return values => {
|
|
365
|
+
return build({
|
|
366
|
+
...values,
|
|
367
|
+
...pinned
|
|
368
|
+
});
|
|
369
|
+
};
|
|
370
|
+
};
|
|
371
|
+
var parseNumber = value => {
|
|
372
|
+
const parsed = Number(value);
|
|
373
|
+
return value.trim() !== "" && Number.isFinite(parsed) ? parsed : void 0;
|
|
374
|
+
};
|
|
375
|
+
/** Each JSON type a pin can hold, and how its text reads as that type. */
|
|
376
|
+
var PIN_PARSERS = {
|
|
377
|
+
string: value => {
|
|
378
|
+
return value;
|
|
379
|
+
},
|
|
380
|
+
boolean: value => {
|
|
381
|
+
if (value === "true") return true;
|
|
382
|
+
if (value === "false") return false;
|
|
383
|
+
},
|
|
384
|
+
number: parseNumber,
|
|
385
|
+
integer: value => {
|
|
386
|
+
const parsed = parseNumber(value);
|
|
387
|
+
return parsed !== void 0 && Number.isInteger(parsed) ? parsed : void 0;
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
/**
|
|
391
|
+
* Reads a pinned body value as the JSON type its schema declares. A body is
|
|
392
|
+
* JSON, so `'true'` on a boolean must reach the API as `true`; a query string
|
|
393
|
+
* carries the text either way. A pin the type cannot hold is a spec error.
|
|
394
|
+
*/
|
|
395
|
+
var typedPin = args => {
|
|
396
|
+
const {
|
|
397
|
+
value,
|
|
398
|
+
type,
|
|
399
|
+
where
|
|
400
|
+
} = args;
|
|
401
|
+
const typed = (typeof type === "string" ? PIN_PARSERS[type] : void 0)?.(value);
|
|
402
|
+
if (typed !== void 0) return typed;
|
|
403
|
+
throw new Error(`${where}: cannot pin '${value}' on a property of type ${JSON.stringify(type ?? "any")}; a pinned body property must be a boolean, integer, number or string that holds the value.`);
|
|
404
|
+
};
|
|
405
|
+
/**
|
|
406
|
+
* Wraps a `body` builder so pinned values replace whatever the args carry. An
|
|
407
|
+
* operation whose only body properties are pinned still gets a builder.
|
|
408
|
+
*/
|
|
409
|
+
var withPinnedBody = args => {
|
|
410
|
+
const {
|
|
411
|
+
build,
|
|
412
|
+
pinned
|
|
413
|
+
} = args;
|
|
414
|
+
if (Object.keys(pinned).length === 0) return build;
|
|
415
|
+
return values => {
|
|
416
|
+
return {
|
|
417
|
+
...(build ? build(values) : {}),
|
|
418
|
+
...pinned
|
|
419
|
+
};
|
|
420
|
+
};
|
|
421
|
+
};
|
|
422
|
+
var isPlainObject$1 = value => {
|
|
423
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
424
|
+
};
|
|
425
|
+
var mergeNullIntoType = schema => {
|
|
426
|
+
const {
|
|
427
|
+
type
|
|
428
|
+
} = schema;
|
|
429
|
+
if (typeof type === "string") schema.type = [type, "null"];else if (Array.isArray(type) && !type.includes("null")) schema.type = [...type, "null"];
|
|
430
|
+
};
|
|
431
|
+
/**
|
|
432
|
+
* Turns OpenAPI's `nullable` into JSON Schema at every depth: `nullable: true`
|
|
433
|
+
* merges `'null'` into a sibling `type` and is dropped where there is none.
|
|
434
|
+
* JSON Schema has no `nullable`, so a validator or model reading it would
|
|
435
|
+
* refuse `null` where the API accepts it.
|
|
436
|
+
*
|
|
437
|
+
* Only a boolean `nullable` is the keyword; a property *named* `nullable`
|
|
438
|
+
* inside `properties` is a schema and is kept.
|
|
439
|
+
*/
|
|
440
|
+
var normalizeNullable = value => {
|
|
441
|
+
if (Array.isArray(value)) return value.map(normalizeNullable);
|
|
442
|
+
if (!isPlainObject$1(value)) return value;
|
|
443
|
+
const result = {};
|
|
444
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
445
|
+
if (key === "nullable" && typeof nested === "boolean") continue;
|
|
446
|
+
result[key] = normalizeNullable(nested);
|
|
447
|
+
}
|
|
448
|
+
if (value.nullable === true) mergeNullIntoType(result);
|
|
449
|
+
return result;
|
|
450
|
+
};
|
|
451
|
+
|
|
322
452
|
//#endregion
|
|
323
453
|
//#region src/types.ts
|
|
324
454
|
var DEFAULT_EXCLUDE_EXTENSION = "x-mcp-exclude";
|
|
@@ -352,6 +482,14 @@ var dedupeByName = items => {
|
|
|
352
482
|
for (const item of items) byName.set(item.name, item);
|
|
353
483
|
return [...byName.values()];
|
|
354
484
|
};
|
|
485
|
+
var managedFields = flag => {
|
|
486
|
+
return flag.value === void 0 ? {
|
|
487
|
+
serverManaged: flag.managed
|
|
488
|
+
} : {
|
|
489
|
+
serverManaged: true,
|
|
490
|
+
pinnedValue: flag.value
|
|
491
|
+
};
|
|
492
|
+
};
|
|
355
493
|
var extractPathParams = args => {
|
|
356
494
|
const toArgName = args.toArgName ?? snakeToCamel;
|
|
357
495
|
const flag = args.serverManagedExtension ?? "x-mcp-server-managed";
|
|
@@ -363,7 +501,10 @@ var extractPathParams = args => {
|
|
|
363
501
|
return {
|
|
364
502
|
name: p.name || "",
|
|
365
503
|
argName: toArgName(p.name || ""),
|
|
366
|
-
|
|
504
|
+
...managedFields(readServerManaged({
|
|
505
|
+
node: p,
|
|
506
|
+
extension: flag
|
|
507
|
+
}))
|
|
367
508
|
};
|
|
368
509
|
}));
|
|
369
510
|
};
|
|
@@ -383,11 +524,13 @@ var extractQueryParams = args => {
|
|
|
383
524
|
type: p.schema?.type || "string",
|
|
384
525
|
style: p.style,
|
|
385
526
|
explode: p.explode,
|
|
386
|
-
|
|
527
|
+
...managedFields(readServerManaged({
|
|
528
|
+
node: p,
|
|
529
|
+
extension: flag
|
|
530
|
+
}))
|
|
387
531
|
};
|
|
388
532
|
}));
|
|
389
533
|
};
|
|
390
|
-
/** Lists the path and query params flagged as server-managed. */
|
|
391
534
|
var collectServerManagedParameters = args => {
|
|
392
535
|
return [...args.pathParams.map(p => {
|
|
393
536
|
return {
|
|
@@ -405,11 +548,102 @@ var collectServerManagedParameters = args => {
|
|
|
405
548
|
return {
|
|
406
549
|
name: p.name,
|
|
407
550
|
in: p.in,
|
|
408
|
-
argName: p.argName
|
|
551
|
+
argName: p.argName,
|
|
552
|
+
...(p.pinnedValue === void 0 ? {} : {
|
|
553
|
+
value: p.pinnedValue
|
|
554
|
+
})
|
|
409
555
|
};
|
|
410
556
|
});
|
|
411
557
|
};
|
|
412
558
|
|
|
559
|
+
//#endregion
|
|
560
|
+
//#region src/body.ts
|
|
561
|
+
var resolveBodySchema = args => {
|
|
562
|
+
const rawBodySchema = args.requestBody?.content?.["application/json"]?.schema;
|
|
563
|
+
return resolveSchema(dereferenceSchema(rawBodySchema, args.spec, args.documents), args.spec, args.documents);
|
|
564
|
+
};
|
|
565
|
+
/**
|
|
566
|
+
* snake_case names of every top-level property an operation's request schema
|
|
567
|
+
* declares, including server-managed ones.
|
|
568
|
+
*/
|
|
569
|
+
var extractAcceptedBodyFields = args => {
|
|
570
|
+
const bodySchema = resolveBodySchema(args);
|
|
571
|
+
return Object.keys(bodySchema?.properties ?? {});
|
|
572
|
+
};
|
|
573
|
+
var isPlainObject = value => {
|
|
574
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
575
|
+
};
|
|
576
|
+
/**
|
|
577
|
+
* Folds a single-entry `allOf` into the property that wraps it. OpenAPI
|
|
578
|
+
* declares `allOf: [{ $ref }]` so a property can carry its own `description`
|
|
579
|
+
* next to a referenced schema; without folding, the referenced `type`,
|
|
580
|
+
* `nullable` and `items` would be lost. Keys on the wrapper win over the
|
|
581
|
+
* referenced schema's. Multi-entry `allOf` is left intact.
|
|
582
|
+
*/
|
|
583
|
+
var flattenSingleAllOf = schema => {
|
|
584
|
+
const {
|
|
585
|
+
allOf,
|
|
586
|
+
...rest
|
|
587
|
+
} = schema;
|
|
588
|
+
if (!Array.isArray(allOf) || allOf.length !== 1) return schema;
|
|
589
|
+
const [entry] = allOf;
|
|
590
|
+
if (!isPlainObject(entry)) return rest;
|
|
591
|
+
return {
|
|
592
|
+
...flattenSingleAllOf(entry),
|
|
593
|
+
...rest
|
|
594
|
+
};
|
|
595
|
+
};
|
|
596
|
+
var extractBodyProps = args => {
|
|
597
|
+
const toArgName = args.toArgName ?? snakeToCamel;
|
|
598
|
+
const bodySchema = resolveBodySchema(args);
|
|
599
|
+
if (!bodySchema?.properties) return [];
|
|
600
|
+
return Object.entries(bodySchema.properties).filter(([, value]) => {
|
|
601
|
+
return !readServerManaged({
|
|
602
|
+
node: value,
|
|
603
|
+
extension: args.serverManagedExtension
|
|
604
|
+
}).managed;
|
|
605
|
+
}).map(([key, value]) => {
|
|
606
|
+
const val = flattenSingleAllOf(value);
|
|
607
|
+
return {
|
|
608
|
+
snakeName: key,
|
|
609
|
+
argName: toArgName(key),
|
|
610
|
+
description: typeof val.description === "string" ? val.description : "",
|
|
611
|
+
required: (bodySchema.required || []).includes(key),
|
|
612
|
+
type: typeof val.type === "string" ? val.type : void 0,
|
|
613
|
+
items: val.items,
|
|
614
|
+
nullable: val.nullable === true,
|
|
615
|
+
oneOf: Array.isArray(val.oneOf) ? val.oneOf : void 0,
|
|
616
|
+
anyOf: Array.isArray(val.anyOf) ? val.anyOf : void 0,
|
|
617
|
+
allOf: Array.isArray(val.allOf) ? val.allOf : void 0
|
|
618
|
+
};
|
|
619
|
+
});
|
|
620
|
+
};
|
|
621
|
+
/**
|
|
622
|
+
* The value each server-managed body property pins, typed by its schema and
|
|
623
|
+
* keyed by its spec name. A property whose extension is not a string pins
|
|
624
|
+
* nothing: it is only hidden, for the consumer to fill.
|
|
625
|
+
*/
|
|
626
|
+
var extractPinnedBody = args => {
|
|
627
|
+
const bodySchema = resolveBodySchema(args);
|
|
628
|
+
const pinned = {};
|
|
629
|
+
for (const [key, value] of Object.entries(bodySchema?.properties ?? {})) {
|
|
630
|
+
const node = value;
|
|
631
|
+
const {
|
|
632
|
+
value: pin
|
|
633
|
+
} = readServerManaged({
|
|
634
|
+
node,
|
|
635
|
+
extension: args.serverManagedExtension
|
|
636
|
+
});
|
|
637
|
+
if (pin === void 0) continue;
|
|
638
|
+
pinned[key] = typedPin({
|
|
639
|
+
value: pin,
|
|
640
|
+
type: flattenSingleAllOf(node).type,
|
|
641
|
+
where: `${args.operationId} body property '${key}'`
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
return pinned;
|
|
645
|
+
};
|
|
646
|
+
|
|
413
647
|
//#endregion
|
|
414
648
|
//#region src/toolDefinitions.ts
|
|
415
649
|
/** Converts a camelCase `operationId` to a kebab-case tool name. */
|
|
@@ -498,7 +732,7 @@ var buildInputSchema = (pathParams, queryParams, bodyProps) => {
|
|
|
498
732
|
return p.argName;
|
|
499
733
|
})];
|
|
500
734
|
const properties = {};
|
|
501
|
-
for (const param of allParams) properties[param.argName] = "description" in param ? buildTypedProperty(param) : {
|
|
735
|
+
for (const param of allParams) properties[param.argName] = "description" in param ? normalizeNullable(buildTypedProperty(param)) : {
|
|
502
736
|
type: "string",
|
|
503
737
|
description: ""
|
|
504
738
|
};
|
|
@@ -510,63 +744,6 @@ var buildInputSchema = (pathParams, queryParams, bodyProps) => {
|
|
|
510
744
|
} : {})
|
|
511
745
|
};
|
|
512
746
|
};
|
|
513
|
-
var resolveBodySchema = args => {
|
|
514
|
-
const rawBodySchema = args.requestBody?.content?.["application/json"]?.schema;
|
|
515
|
-
return resolveSchema(dereferenceSchema(rawBodySchema, args.spec, args.documents), args.spec, args.documents);
|
|
516
|
-
};
|
|
517
|
-
/**
|
|
518
|
-
* snake_case names of every top-level property an operation's request schema
|
|
519
|
-
* declares, including server-managed ones.
|
|
520
|
-
*/
|
|
521
|
-
var extractAcceptedBodyFields = args => {
|
|
522
|
-
const bodySchema = resolveBodySchema(args);
|
|
523
|
-
return Object.keys(bodySchema?.properties ?? {});
|
|
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
|
-
};
|
|
548
|
-
var extractBodyProps = args => {
|
|
549
|
-
const toArgName = args.toArgName ?? snakeToCamel;
|
|
550
|
-
const bodySchema = resolveBodySchema(args);
|
|
551
|
-
if (!bodySchema?.properties) return [];
|
|
552
|
-
return Object.entries(bodySchema.properties).filter(([, value]) => {
|
|
553
|
-
return !value[args.serverManagedExtension];
|
|
554
|
-
}).map(([key, value]) => {
|
|
555
|
-
const val = flattenSingleAllOf(value);
|
|
556
|
-
return {
|
|
557
|
-
snakeName: key,
|
|
558
|
-
argName: toArgName(key),
|
|
559
|
-
description: typeof val.description === "string" ? val.description : "",
|
|
560
|
-
required: (bodySchema.required || []).includes(key),
|
|
561
|
-
type: typeof val.type === "string" ? val.type : void 0,
|
|
562
|
-
items: val.items,
|
|
563
|
-
nullable: val.nullable === true,
|
|
564
|
-
oneOf: Array.isArray(val.oneOf) ? val.oneOf : void 0,
|
|
565
|
-
anyOf: Array.isArray(val.anyOf) ? val.anyOf : void 0,
|
|
566
|
-
allOf: Array.isArray(val.allOf) ? val.allOf : void 0
|
|
567
|
-
};
|
|
568
|
-
});
|
|
569
|
-
};
|
|
570
747
|
/** Collects every `x-` prefixed extension declared on the operation. */
|
|
571
748
|
var extractExtensions = operation => {
|
|
572
749
|
const extensions = {};
|
|
@@ -599,19 +776,22 @@ var processOperation = args => {
|
|
|
599
776
|
toArgName,
|
|
600
777
|
serverManagedExtension
|
|
601
778
|
});
|
|
602
|
-
const
|
|
779
|
+
const bodyArgs = {
|
|
603
780
|
requestBody: args.operation.requestBody,
|
|
604
781
|
spec: args.spec,
|
|
605
782
|
serverManagedExtension,
|
|
606
|
-
documents
|
|
783
|
+
documents
|
|
784
|
+
};
|
|
785
|
+
const bodyProps = extractBodyProps({
|
|
786
|
+
...bodyArgs,
|
|
607
787
|
toArgName
|
|
608
788
|
});
|
|
609
789
|
const inputSchema = buildInputSchema(pathParams, queryParams, bodyProps);
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
documents
|
|
790
|
+
const serverManagedParameters = collectServerManagedParameters({
|
|
791
|
+
pathParams,
|
|
792
|
+
queryParams
|
|
614
793
|
});
|
|
794
|
+
const pinned = pinnedArgs(serverManagedParameters);
|
|
615
795
|
return {
|
|
616
796
|
name: toolName,
|
|
617
797
|
description: sanitizeDescription(args.operation.description),
|
|
@@ -619,15 +799,24 @@ var processOperation = args => {
|
|
|
619
799
|
method: httpMethod,
|
|
620
800
|
pathTemplate: args.pathTemplate,
|
|
621
801
|
operationId: args.operation.operationId,
|
|
622
|
-
path:
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
802
|
+
path: withPinned({
|
|
803
|
+
build: buildPathFn(args.pathTemplate, pathParams),
|
|
804
|
+
pinned
|
|
805
|
+
}),
|
|
806
|
+
query: withPinned({
|
|
807
|
+
build: buildQueryFn(queryParams),
|
|
808
|
+
pinned
|
|
809
|
+
}),
|
|
810
|
+
body: withPinnedBody({
|
|
811
|
+
build: buildBodyFn(bodyProps),
|
|
812
|
+
pinned: extractPinnedBody({
|
|
813
|
+
...bodyArgs,
|
|
814
|
+
operationId: args.operation.operationId
|
|
815
|
+
})
|
|
816
|
+
}),
|
|
817
|
+
acceptedBodyFields: extractAcceptedBodyFields(bodyArgs),
|
|
626
818
|
extensions: extractExtensions(args.operation),
|
|
627
|
-
serverManagedParameters
|
|
628
|
-
pathParams,
|
|
629
|
-
queryParams
|
|
630
|
-
})
|
|
819
|
+
serverManagedParameters
|
|
631
820
|
};
|
|
632
821
|
};
|
|
633
822
|
var processPath = args => {
|
|
@@ -787,6 +976,7 @@ exports.dereferenceSchema = dereferenceSchema;
|
|
|
787
976
|
exports.extractAcceptedBodyFields = extractAcceptedBodyFields;
|
|
788
977
|
exports.extractBodyProps = extractBodyProps;
|
|
789
978
|
exports.extractPathParams = extractPathParams;
|
|
979
|
+
exports.extractPinnedBody = extractPinnedBody;
|
|
790
980
|
exports.extractQueryParams = extractQueryParams;
|
|
791
981
|
exports.getJsonSchemaType = getJsonSchemaType;
|
|
792
982
|
exports.openApiToToolDefinitions = openApiToToolDefinitions;
|
package/dist/index.d.cts
CHANGED
|
@@ -84,6 +84,11 @@ interface ServerManagedParameter {
|
|
|
84
84
|
in: 'path' | 'query';
|
|
85
85
|
/** The args key `path` / `query` read the value from. */
|
|
86
86
|
argName: string;
|
|
87
|
+
/**
|
|
88
|
+
* The value the spec pins, when the extension is a string. `path` / `query`
|
|
89
|
+
* always send it, whatever the args or `serverParameters` carry.
|
|
90
|
+
*/
|
|
91
|
+
value?: string;
|
|
87
92
|
}
|
|
88
93
|
/** Minimal shape of an OpenAPI document consumed by the generator. */
|
|
89
94
|
interface OpenApiSpec {
|
|
@@ -150,11 +155,14 @@ interface OpenApiToToolsOptions {
|
|
|
150
155
|
* - On a **request-body property**, the property stays in
|
|
151
156
|
* `acceptedBodyFields` and is never sent (the API sets it itself).
|
|
152
157
|
* - On a **path or query parameter**, the parameter is listed in
|
|
153
|
-
* `serverManagedParameters` and the consumer supplies its value.
|
|
158
|
+
* `serverManagedParameters` and the consumer supplies its value. A string
|
|
159
|
+
* extension value (`'true'`) pins it: the builders always send that value.
|
|
160
|
+
*
|
|
161
|
+
* Several names may be given; a node flagged by any of them is managed.
|
|
154
162
|
*
|
|
155
163
|
* @default 'x-mcp-server-managed'
|
|
156
164
|
*/
|
|
157
|
-
serverManagedExtension?: string;
|
|
165
|
+
serverManagedExtension?: string | string[];
|
|
158
166
|
/**
|
|
159
167
|
* How tool argument names are derived from the spec's parameter and
|
|
160
168
|
* body-property names.
|
|
@@ -181,6 +189,10 @@ type ResolvedToolOptions = Required<Omit<OpenApiToToolsOptions, 'documents'>> &
|
|
|
181
189
|
declare const DEFAULT_EXCLUDE_EXTENSION = "x-mcp-exclude";
|
|
182
190
|
declare const DEFAULT_SERVER_MANAGED_EXTENSION = "x-mcp-server-managed";
|
|
183
191
|
//#endregion
|
|
192
|
+
//#region src/serverManaged.d.ts
|
|
193
|
+
/** One extension name, or several that all mean "the server sets this". */
|
|
194
|
+
type ServerManagedExtension = string | string[];
|
|
195
|
+
//#endregion
|
|
184
196
|
//#region src/parameters.d.ts
|
|
185
197
|
/**
|
|
186
198
|
* Folds `_` and `-` separators into camelCase. OpenAPI operation and parameter
|
|
@@ -200,12 +212,13 @@ type ExtractParamsArgs = {
|
|
|
200
212
|
spec: OpenApiSpec; /** Sibling documents cross-file `$ref`s resolve against. */
|
|
201
213
|
documents?: OpenApiDocuments; /** Maps a spec name to its tool argument name. @default snakeToCamel */
|
|
202
214
|
toArgName?: ToArgName; /** @default DEFAULT_SERVER_MANAGED_EXTENSION */
|
|
203
|
-
serverManagedExtension?:
|
|
215
|
+
serverManagedExtension?: ServerManagedExtension;
|
|
204
216
|
};
|
|
205
217
|
declare const extractPathParams: (args: ExtractParamsArgs) => Array<{
|
|
206
218
|
name: string;
|
|
207
219
|
argName: string;
|
|
208
220
|
serverManaged: boolean;
|
|
221
|
+
pinnedValue?: string;
|
|
209
222
|
}>;
|
|
210
223
|
declare const extractQueryParams: (args: ExtractParamsArgs) => Array<{
|
|
211
224
|
name: string;
|
|
@@ -216,8 +229,50 @@ declare const extractQueryParams: (args: ExtractParamsArgs) => Array<{
|
|
|
216
229
|
style?: string;
|
|
217
230
|
explode?: boolean;
|
|
218
231
|
serverManaged: boolean;
|
|
232
|
+
pinnedValue?: string;
|
|
219
233
|
}>;
|
|
220
234
|
//#endregion
|
|
235
|
+
//#region src/body.d.ts
|
|
236
|
+
/**
|
|
237
|
+
* snake_case names of every top-level property an operation's request schema
|
|
238
|
+
* declares, including server-managed ones.
|
|
239
|
+
*/
|
|
240
|
+
declare const extractAcceptedBodyFields: (args: {
|
|
241
|
+
requestBody?: RequestBodySpec;
|
|
242
|
+
spec: OpenApiSpec;
|
|
243
|
+
documents?: OpenApiDocuments;
|
|
244
|
+
}) => string[];
|
|
245
|
+
declare const extractBodyProps: (args: {
|
|
246
|
+
requestBody?: RequestBodySpec;
|
|
247
|
+
spec: OpenApiSpec;
|
|
248
|
+
serverManagedExtension: ServerManagedExtension;
|
|
249
|
+
documents?: OpenApiDocuments; /** Maps a spec name to its tool argument name. @default snakeToCamel */
|
|
250
|
+
toArgName?: ToArgName;
|
|
251
|
+
}) => Array<{
|
|
252
|
+
snakeName: string;
|
|
253
|
+
argName: string;
|
|
254
|
+
description: string;
|
|
255
|
+
required: boolean;
|
|
256
|
+
type?: string;
|
|
257
|
+
items?: unknown;
|
|
258
|
+
nullable: boolean;
|
|
259
|
+
oneOf?: unknown[];
|
|
260
|
+
anyOf?: unknown[];
|
|
261
|
+
allOf?: unknown[];
|
|
262
|
+
}>;
|
|
263
|
+
/**
|
|
264
|
+
* The value each server-managed body property pins, typed by its schema and
|
|
265
|
+
* keyed by its spec name. A property whose extension is not a string pins
|
|
266
|
+
* nothing: it is only hidden, for the consumer to fill.
|
|
267
|
+
*/
|
|
268
|
+
declare const extractPinnedBody: (args: {
|
|
269
|
+
requestBody?: RequestBodySpec;
|
|
270
|
+
spec: OpenApiSpec;
|
|
271
|
+
serverManagedExtension: ServerManagedExtension;
|
|
272
|
+
documents?: OpenApiDocuments;
|
|
273
|
+
operationId: string;
|
|
274
|
+
}) => Record<string, string | number | boolean>;
|
|
275
|
+
//#endregion
|
|
221
276
|
//#region src/registerOpenApiTools.d.ts
|
|
222
277
|
/** The resolved HTTP request a tool call maps to, before transport concerns. */
|
|
223
278
|
interface ResolvedRequest {
|
|
@@ -403,33 +458,6 @@ declare const buildInputSchema: (pathParams: Array<{
|
|
|
403
458
|
anyOf?: unknown[];
|
|
404
459
|
allOf?: unknown[];
|
|
405
460
|
}>) => JsonObjectSchema;
|
|
406
|
-
/**
|
|
407
|
-
* snake_case names of every top-level property an operation's request schema
|
|
408
|
-
* declares, including server-managed ones.
|
|
409
|
-
*/
|
|
410
|
-
declare const extractAcceptedBodyFields: (args: {
|
|
411
|
-
requestBody?: RequestBodySpec;
|
|
412
|
-
spec: OpenApiSpec;
|
|
413
|
-
documents?: OpenApiDocuments;
|
|
414
|
-
}) => string[];
|
|
415
|
-
declare const extractBodyProps: (args: {
|
|
416
|
-
requestBody?: RequestBodySpec;
|
|
417
|
-
spec: OpenApiSpec;
|
|
418
|
-
serverManagedExtension: string;
|
|
419
|
-
documents?: OpenApiDocuments; /** Maps a spec name to its tool argument name. @default snakeToCamel */
|
|
420
|
-
toArgName?: ToArgName;
|
|
421
|
-
}) => Array<{
|
|
422
|
-
snakeName: string;
|
|
423
|
-
argName: string;
|
|
424
|
-
description: string;
|
|
425
|
-
required: boolean;
|
|
426
|
-
type?: string;
|
|
427
|
-
items?: unknown;
|
|
428
|
-
nullable: boolean;
|
|
429
|
-
oneOf?: unknown[];
|
|
430
|
-
anyOf?: unknown[];
|
|
431
|
-
allOf?: unknown[];
|
|
432
|
-
}>;
|
|
433
461
|
declare const processOperation: (args: {
|
|
434
462
|
pathTemplate: string;
|
|
435
463
|
method: string;
|
|
@@ -470,4 +498,4 @@ declare const openApiToToolDefinitions: (args: {
|
|
|
470
498
|
options?: OpenApiToToolsOptions;
|
|
471
499
|
}) => ToolDefinition[];
|
|
472
500
|
//#endregion
|
|
473
|
-
export { DEFAULT_EXCLUDE_EXTENSION, DEFAULT_SERVER_MANAGED_EXTENSION, type JsonSchemaProperty, NO_CONTENT_TEXT, type OpenApiDocuments, type OpenApiSpec, type OpenApiToToolsOptions, type OperationSpec, type QueryParamSerialization, type RegisterOpenApiToolsArgs, type RequestBodySpec, type ResolvedParameter, type ResolvedRequest, type ResolvedToolOptions, type ServerManagedParameter, type ToolDefinition, buildBodyFn, buildInputSchema, buildPathFn, buildQueryFn, dereferenceSchema, extractAcceptedBodyFields, extractBodyProps, extractPathParams, extractQueryParams, getJsonSchemaType, openApiToToolDefinitions, operationIdToToolName, processOperation, processPath, registerOpenApiTools, resolveParameter, resolveSchema, snakeToCamel };
|
|
501
|
+
export { DEFAULT_EXCLUDE_EXTENSION, DEFAULT_SERVER_MANAGED_EXTENSION, type JsonSchemaProperty, NO_CONTENT_TEXT, type OpenApiDocuments, type OpenApiSpec, type OpenApiToToolsOptions, type OperationSpec, type QueryParamSerialization, type RegisterOpenApiToolsArgs, type RequestBodySpec, type ResolvedParameter, type ResolvedRequest, type ResolvedToolOptions, type ServerManagedParameter, type ToolDefinition, buildBodyFn, buildInputSchema, buildPathFn, buildQueryFn, dereferenceSchema, extractAcceptedBodyFields, extractBodyProps, extractPathParams, extractPinnedBody, extractQueryParams, getJsonSchemaType, openApiToToolDefinitions, operationIdToToolName, processOperation, processPath, registerOpenApiTools, resolveParameter, resolveSchema, snakeToCamel };
|
package/dist/index.d.mts
CHANGED
|
@@ -84,6 +84,11 @@ interface ServerManagedParameter {
|
|
|
84
84
|
in: 'path' | 'query';
|
|
85
85
|
/** The args key `path` / `query` read the value from. */
|
|
86
86
|
argName: string;
|
|
87
|
+
/**
|
|
88
|
+
* The value the spec pins, when the extension is a string. `path` / `query`
|
|
89
|
+
* always send it, whatever the args or `serverParameters` carry.
|
|
90
|
+
*/
|
|
91
|
+
value?: string;
|
|
87
92
|
}
|
|
88
93
|
/** Minimal shape of an OpenAPI document consumed by the generator. */
|
|
89
94
|
interface OpenApiSpec {
|
|
@@ -150,11 +155,14 @@ interface OpenApiToToolsOptions {
|
|
|
150
155
|
* - On a **request-body property**, the property stays in
|
|
151
156
|
* `acceptedBodyFields` and is never sent (the API sets it itself).
|
|
152
157
|
* - On a **path or query parameter**, the parameter is listed in
|
|
153
|
-
* `serverManagedParameters` and the consumer supplies its value.
|
|
158
|
+
* `serverManagedParameters` and the consumer supplies its value. A string
|
|
159
|
+
* extension value (`'true'`) pins it: the builders always send that value.
|
|
160
|
+
*
|
|
161
|
+
* Several names may be given; a node flagged by any of them is managed.
|
|
154
162
|
*
|
|
155
163
|
* @default 'x-mcp-server-managed'
|
|
156
164
|
*/
|
|
157
|
-
serverManagedExtension?: string;
|
|
165
|
+
serverManagedExtension?: string | string[];
|
|
158
166
|
/**
|
|
159
167
|
* How tool argument names are derived from the spec's parameter and
|
|
160
168
|
* body-property names.
|
|
@@ -181,6 +189,10 @@ type ResolvedToolOptions = Required<Omit<OpenApiToToolsOptions, 'documents'>> &
|
|
|
181
189
|
declare const DEFAULT_EXCLUDE_EXTENSION = "x-mcp-exclude";
|
|
182
190
|
declare const DEFAULT_SERVER_MANAGED_EXTENSION = "x-mcp-server-managed";
|
|
183
191
|
//#endregion
|
|
192
|
+
//#region src/serverManaged.d.ts
|
|
193
|
+
/** One extension name, or several that all mean "the server sets this". */
|
|
194
|
+
type ServerManagedExtension = string | string[];
|
|
195
|
+
//#endregion
|
|
184
196
|
//#region src/parameters.d.ts
|
|
185
197
|
/**
|
|
186
198
|
* Folds `_` and `-` separators into camelCase. OpenAPI operation and parameter
|
|
@@ -200,12 +212,13 @@ type ExtractParamsArgs = {
|
|
|
200
212
|
spec: OpenApiSpec; /** Sibling documents cross-file `$ref`s resolve against. */
|
|
201
213
|
documents?: OpenApiDocuments; /** Maps a spec name to its tool argument name. @default snakeToCamel */
|
|
202
214
|
toArgName?: ToArgName; /** @default DEFAULT_SERVER_MANAGED_EXTENSION */
|
|
203
|
-
serverManagedExtension?:
|
|
215
|
+
serverManagedExtension?: ServerManagedExtension;
|
|
204
216
|
};
|
|
205
217
|
declare const extractPathParams: (args: ExtractParamsArgs) => Array<{
|
|
206
218
|
name: string;
|
|
207
219
|
argName: string;
|
|
208
220
|
serverManaged: boolean;
|
|
221
|
+
pinnedValue?: string;
|
|
209
222
|
}>;
|
|
210
223
|
declare const extractQueryParams: (args: ExtractParamsArgs) => Array<{
|
|
211
224
|
name: string;
|
|
@@ -216,8 +229,50 @@ declare const extractQueryParams: (args: ExtractParamsArgs) => Array<{
|
|
|
216
229
|
style?: string;
|
|
217
230
|
explode?: boolean;
|
|
218
231
|
serverManaged: boolean;
|
|
232
|
+
pinnedValue?: string;
|
|
219
233
|
}>;
|
|
220
234
|
//#endregion
|
|
235
|
+
//#region src/body.d.ts
|
|
236
|
+
/**
|
|
237
|
+
* snake_case names of every top-level property an operation's request schema
|
|
238
|
+
* declares, including server-managed ones.
|
|
239
|
+
*/
|
|
240
|
+
declare const extractAcceptedBodyFields: (args: {
|
|
241
|
+
requestBody?: RequestBodySpec;
|
|
242
|
+
spec: OpenApiSpec;
|
|
243
|
+
documents?: OpenApiDocuments;
|
|
244
|
+
}) => string[];
|
|
245
|
+
declare const extractBodyProps: (args: {
|
|
246
|
+
requestBody?: RequestBodySpec;
|
|
247
|
+
spec: OpenApiSpec;
|
|
248
|
+
serverManagedExtension: ServerManagedExtension;
|
|
249
|
+
documents?: OpenApiDocuments; /** Maps a spec name to its tool argument name. @default snakeToCamel */
|
|
250
|
+
toArgName?: ToArgName;
|
|
251
|
+
}) => Array<{
|
|
252
|
+
snakeName: string;
|
|
253
|
+
argName: string;
|
|
254
|
+
description: string;
|
|
255
|
+
required: boolean;
|
|
256
|
+
type?: string;
|
|
257
|
+
items?: unknown;
|
|
258
|
+
nullable: boolean;
|
|
259
|
+
oneOf?: unknown[];
|
|
260
|
+
anyOf?: unknown[];
|
|
261
|
+
allOf?: unknown[];
|
|
262
|
+
}>;
|
|
263
|
+
/**
|
|
264
|
+
* The value each server-managed body property pins, typed by its schema and
|
|
265
|
+
* keyed by its spec name. A property whose extension is not a string pins
|
|
266
|
+
* nothing: it is only hidden, for the consumer to fill.
|
|
267
|
+
*/
|
|
268
|
+
declare const extractPinnedBody: (args: {
|
|
269
|
+
requestBody?: RequestBodySpec;
|
|
270
|
+
spec: OpenApiSpec;
|
|
271
|
+
serverManagedExtension: ServerManagedExtension;
|
|
272
|
+
documents?: OpenApiDocuments;
|
|
273
|
+
operationId: string;
|
|
274
|
+
}) => Record<string, string | number | boolean>;
|
|
275
|
+
//#endregion
|
|
221
276
|
//#region src/registerOpenApiTools.d.ts
|
|
222
277
|
/** The resolved HTTP request a tool call maps to, before transport concerns. */
|
|
223
278
|
interface ResolvedRequest {
|
|
@@ -403,33 +458,6 @@ declare const buildInputSchema: (pathParams: Array<{
|
|
|
403
458
|
anyOf?: unknown[];
|
|
404
459
|
allOf?: unknown[];
|
|
405
460
|
}>) => JsonObjectSchema;
|
|
406
|
-
/**
|
|
407
|
-
* snake_case names of every top-level property an operation's request schema
|
|
408
|
-
* declares, including server-managed ones.
|
|
409
|
-
*/
|
|
410
|
-
declare const extractAcceptedBodyFields: (args: {
|
|
411
|
-
requestBody?: RequestBodySpec;
|
|
412
|
-
spec: OpenApiSpec;
|
|
413
|
-
documents?: OpenApiDocuments;
|
|
414
|
-
}) => string[];
|
|
415
|
-
declare const extractBodyProps: (args: {
|
|
416
|
-
requestBody?: RequestBodySpec;
|
|
417
|
-
spec: OpenApiSpec;
|
|
418
|
-
serverManagedExtension: string;
|
|
419
|
-
documents?: OpenApiDocuments; /** Maps a spec name to its tool argument name. @default snakeToCamel */
|
|
420
|
-
toArgName?: ToArgName;
|
|
421
|
-
}) => Array<{
|
|
422
|
-
snakeName: string;
|
|
423
|
-
argName: string;
|
|
424
|
-
description: string;
|
|
425
|
-
required: boolean;
|
|
426
|
-
type?: string;
|
|
427
|
-
items?: unknown;
|
|
428
|
-
nullable: boolean;
|
|
429
|
-
oneOf?: unknown[];
|
|
430
|
-
anyOf?: unknown[];
|
|
431
|
-
allOf?: unknown[];
|
|
432
|
-
}>;
|
|
433
461
|
declare const processOperation: (args: {
|
|
434
462
|
pathTemplate: string;
|
|
435
463
|
method: string;
|
|
@@ -470,4 +498,4 @@ declare const openApiToToolDefinitions: (args: {
|
|
|
470
498
|
options?: OpenApiToToolsOptions;
|
|
471
499
|
}) => ToolDefinition[];
|
|
472
500
|
//#endregion
|
|
473
|
-
export { DEFAULT_EXCLUDE_EXTENSION, DEFAULT_SERVER_MANAGED_EXTENSION, type JsonSchemaProperty, NO_CONTENT_TEXT, type OpenApiDocuments, type OpenApiSpec, type OpenApiToToolsOptions, type OperationSpec, type QueryParamSerialization, type RegisterOpenApiToolsArgs, type RequestBodySpec, type ResolvedParameter, type ResolvedRequest, type ResolvedToolOptions, type ServerManagedParameter, type ToolDefinition, buildBodyFn, buildInputSchema, buildPathFn, buildQueryFn, dereferenceSchema, extractAcceptedBodyFields, extractBodyProps, extractPathParams, extractQueryParams, getJsonSchemaType, openApiToToolDefinitions, operationIdToToolName, processOperation, processPath, registerOpenApiTools, resolveParameter, resolveSchema, snakeToCamel };
|
|
501
|
+
export { DEFAULT_EXCLUDE_EXTENSION, DEFAULT_SERVER_MANAGED_EXTENSION, type JsonSchemaProperty, NO_CONTENT_TEXT, type OpenApiDocuments, type OpenApiSpec, type OpenApiToToolsOptions, type OperationSpec, type QueryParamSerialization, type RegisterOpenApiToolsArgs, type RequestBodySpec, type ResolvedParameter, type ResolvedRequest, type ResolvedToolOptions, type ServerManagedParameter, type ToolDefinition, buildBodyFn, buildInputSchema, buildPathFn, buildQueryFn, dereferenceSchema, extractAcceptedBodyFields, extractBodyProps, extractPathParams, extractPinnedBody, extractQueryParams, getJsonSchemaType, openApiToToolDefinitions, operationIdToToolName, processOperation, processPath, registerOpenApiTools, resolveParameter, resolveSchema, snakeToCamel };
|
package/dist/index.mjs
CHANGED
|
@@ -316,6 +316,136 @@ var buildBodyFn = bodyProps => {
|
|
|
316
316
|
};
|
|
317
317
|
};
|
|
318
318
|
|
|
319
|
+
//#endregion
|
|
320
|
+
//#region src/serverManaged.ts
|
|
321
|
+
/**
|
|
322
|
+
* Whether a parameter or body property carries one of the server-managed
|
|
323
|
+
* extensions, and the value it pins when the extension is a string.
|
|
324
|
+
*
|
|
325
|
+
* A string pins the value (`x-mcp-server-managed: 'true'` always sends
|
|
326
|
+
* `true`); any other truthy value only hides it. The first matching name wins.
|
|
327
|
+
*/
|
|
328
|
+
var readServerManaged = args => {
|
|
329
|
+
const names = Array.isArray(args.extension) ? args.extension : [args.extension];
|
|
330
|
+
for (const name of names) {
|
|
331
|
+
const flag = args.node[name];
|
|
332
|
+
if (typeof flag === "string") return {
|
|
333
|
+
managed: true,
|
|
334
|
+
value: flag
|
|
335
|
+
};
|
|
336
|
+
if (flag) return {
|
|
337
|
+
managed: true
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
managed: false
|
|
342
|
+
};
|
|
343
|
+
};
|
|
344
|
+
/** The args each pinned parameter is always sent with, keyed by `argName`. */
|
|
345
|
+
var pinnedArgs = parameters => {
|
|
346
|
+
const pinned = {};
|
|
347
|
+
for (const parameter of parameters) if (parameter.value !== void 0) pinned[parameter.argName] = parameter.value;
|
|
348
|
+
return pinned;
|
|
349
|
+
};
|
|
350
|
+
/**
|
|
351
|
+
* Wraps a `path` / `query` builder so pinned values replace whatever the args
|
|
352
|
+
* carry. Applied in the builder itself, so every consumer of the tool
|
|
353
|
+
* definition sends them, not only `registerOpenApiTools`.
|
|
354
|
+
*/
|
|
355
|
+
var withPinned = args => {
|
|
356
|
+
const {
|
|
357
|
+
build,
|
|
358
|
+
pinned
|
|
359
|
+
} = args;
|
|
360
|
+
if (!build || Object.keys(pinned).length === 0) return build;
|
|
361
|
+
return values => {
|
|
362
|
+
return build({
|
|
363
|
+
...values,
|
|
364
|
+
...pinned
|
|
365
|
+
});
|
|
366
|
+
};
|
|
367
|
+
};
|
|
368
|
+
var parseNumber = value => {
|
|
369
|
+
const parsed = Number(value);
|
|
370
|
+
return value.trim() !== "" && Number.isFinite(parsed) ? parsed : void 0;
|
|
371
|
+
};
|
|
372
|
+
/** Each JSON type a pin can hold, and how its text reads as that type. */
|
|
373
|
+
var PIN_PARSERS = {
|
|
374
|
+
string: value => {
|
|
375
|
+
return value;
|
|
376
|
+
},
|
|
377
|
+
boolean: value => {
|
|
378
|
+
if (value === "true") return true;
|
|
379
|
+
if (value === "false") return false;
|
|
380
|
+
},
|
|
381
|
+
number: parseNumber,
|
|
382
|
+
integer: value => {
|
|
383
|
+
const parsed = parseNumber(value);
|
|
384
|
+
return parsed !== void 0 && Number.isInteger(parsed) ? parsed : void 0;
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
/**
|
|
388
|
+
* Reads a pinned body value as the JSON type its schema declares. A body is
|
|
389
|
+
* JSON, so `'true'` on a boolean must reach the API as `true`; a query string
|
|
390
|
+
* carries the text either way. A pin the type cannot hold is a spec error.
|
|
391
|
+
*/
|
|
392
|
+
var typedPin = args => {
|
|
393
|
+
const {
|
|
394
|
+
value,
|
|
395
|
+
type,
|
|
396
|
+
where
|
|
397
|
+
} = args;
|
|
398
|
+
const typed = (typeof type === "string" ? PIN_PARSERS[type] : void 0)?.(value);
|
|
399
|
+
if (typed !== void 0) return typed;
|
|
400
|
+
throw new Error(`${where}: cannot pin '${value}' on a property of type ${JSON.stringify(type ?? "any")}; a pinned body property must be a boolean, integer, number or string that holds the value.`);
|
|
401
|
+
};
|
|
402
|
+
/**
|
|
403
|
+
* Wraps a `body` builder so pinned values replace whatever the args carry. An
|
|
404
|
+
* operation whose only body properties are pinned still gets a builder.
|
|
405
|
+
*/
|
|
406
|
+
var withPinnedBody = args => {
|
|
407
|
+
const {
|
|
408
|
+
build,
|
|
409
|
+
pinned
|
|
410
|
+
} = args;
|
|
411
|
+
if (Object.keys(pinned).length === 0) return build;
|
|
412
|
+
return values => {
|
|
413
|
+
return {
|
|
414
|
+
...(build ? build(values) : {}),
|
|
415
|
+
...pinned
|
|
416
|
+
};
|
|
417
|
+
};
|
|
418
|
+
};
|
|
419
|
+
var isPlainObject$1 = value => {
|
|
420
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
421
|
+
};
|
|
422
|
+
var mergeNullIntoType = schema => {
|
|
423
|
+
const {
|
|
424
|
+
type
|
|
425
|
+
} = schema;
|
|
426
|
+
if (typeof type === "string") schema.type = [type, "null"];else if (Array.isArray(type) && !type.includes("null")) schema.type = [...type, "null"];
|
|
427
|
+
};
|
|
428
|
+
/**
|
|
429
|
+
* Turns OpenAPI's `nullable` into JSON Schema at every depth: `nullable: true`
|
|
430
|
+
* merges `'null'` into a sibling `type` and is dropped where there is none.
|
|
431
|
+
* JSON Schema has no `nullable`, so a validator or model reading it would
|
|
432
|
+
* refuse `null` where the API accepts it.
|
|
433
|
+
*
|
|
434
|
+
* Only a boolean `nullable` is the keyword; a property *named* `nullable`
|
|
435
|
+
* inside `properties` is a schema and is kept.
|
|
436
|
+
*/
|
|
437
|
+
var normalizeNullable = value => {
|
|
438
|
+
if (Array.isArray(value)) return value.map(normalizeNullable);
|
|
439
|
+
if (!isPlainObject$1(value)) return value;
|
|
440
|
+
const result = {};
|
|
441
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
442
|
+
if (key === "nullable" && typeof nested === "boolean") continue;
|
|
443
|
+
result[key] = normalizeNullable(nested);
|
|
444
|
+
}
|
|
445
|
+
if (value.nullable === true) mergeNullIntoType(result);
|
|
446
|
+
return result;
|
|
447
|
+
};
|
|
448
|
+
|
|
319
449
|
//#endregion
|
|
320
450
|
//#region src/types.ts
|
|
321
451
|
var DEFAULT_EXCLUDE_EXTENSION = "x-mcp-exclude";
|
|
@@ -349,6 +479,14 @@ var dedupeByName = items => {
|
|
|
349
479
|
for (const item of items) byName.set(item.name, item);
|
|
350
480
|
return [...byName.values()];
|
|
351
481
|
};
|
|
482
|
+
var managedFields = flag => {
|
|
483
|
+
return flag.value === void 0 ? {
|
|
484
|
+
serverManaged: flag.managed
|
|
485
|
+
} : {
|
|
486
|
+
serverManaged: true,
|
|
487
|
+
pinnedValue: flag.value
|
|
488
|
+
};
|
|
489
|
+
};
|
|
352
490
|
var extractPathParams = args => {
|
|
353
491
|
const toArgName = args.toArgName ?? snakeToCamel;
|
|
354
492
|
const flag = args.serverManagedExtension ?? "x-mcp-server-managed";
|
|
@@ -360,7 +498,10 @@ var extractPathParams = args => {
|
|
|
360
498
|
return {
|
|
361
499
|
name: p.name || "",
|
|
362
500
|
argName: toArgName(p.name || ""),
|
|
363
|
-
|
|
501
|
+
...managedFields(readServerManaged({
|
|
502
|
+
node: p,
|
|
503
|
+
extension: flag
|
|
504
|
+
}))
|
|
364
505
|
};
|
|
365
506
|
}));
|
|
366
507
|
};
|
|
@@ -380,11 +521,13 @@ var extractQueryParams = args => {
|
|
|
380
521
|
type: p.schema?.type || "string",
|
|
381
522
|
style: p.style,
|
|
382
523
|
explode: p.explode,
|
|
383
|
-
|
|
524
|
+
...managedFields(readServerManaged({
|
|
525
|
+
node: p,
|
|
526
|
+
extension: flag
|
|
527
|
+
}))
|
|
384
528
|
};
|
|
385
529
|
}));
|
|
386
530
|
};
|
|
387
|
-
/** Lists the path and query params flagged as server-managed. */
|
|
388
531
|
var collectServerManagedParameters = args => {
|
|
389
532
|
return [...args.pathParams.map(p => {
|
|
390
533
|
return {
|
|
@@ -402,11 +545,102 @@ var collectServerManagedParameters = args => {
|
|
|
402
545
|
return {
|
|
403
546
|
name: p.name,
|
|
404
547
|
in: p.in,
|
|
405
|
-
argName: p.argName
|
|
548
|
+
argName: p.argName,
|
|
549
|
+
...(p.pinnedValue === void 0 ? {} : {
|
|
550
|
+
value: p.pinnedValue
|
|
551
|
+
})
|
|
406
552
|
};
|
|
407
553
|
});
|
|
408
554
|
};
|
|
409
555
|
|
|
556
|
+
//#endregion
|
|
557
|
+
//#region src/body.ts
|
|
558
|
+
var resolveBodySchema = args => {
|
|
559
|
+
const rawBodySchema = args.requestBody?.content?.["application/json"]?.schema;
|
|
560
|
+
return resolveSchema(dereferenceSchema(rawBodySchema, args.spec, args.documents), args.spec, args.documents);
|
|
561
|
+
};
|
|
562
|
+
/**
|
|
563
|
+
* snake_case names of every top-level property an operation's request schema
|
|
564
|
+
* declares, including server-managed ones.
|
|
565
|
+
*/
|
|
566
|
+
var extractAcceptedBodyFields = args => {
|
|
567
|
+
const bodySchema = resolveBodySchema(args);
|
|
568
|
+
return Object.keys(bodySchema?.properties ?? {});
|
|
569
|
+
};
|
|
570
|
+
var isPlainObject = value => {
|
|
571
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
572
|
+
};
|
|
573
|
+
/**
|
|
574
|
+
* Folds a single-entry `allOf` into the property that wraps it. OpenAPI
|
|
575
|
+
* declares `allOf: [{ $ref }]` so a property can carry its own `description`
|
|
576
|
+
* next to a referenced schema; without folding, the referenced `type`,
|
|
577
|
+
* `nullable` and `items` would be lost. Keys on the wrapper win over the
|
|
578
|
+
* referenced schema's. Multi-entry `allOf` is left intact.
|
|
579
|
+
*/
|
|
580
|
+
var flattenSingleAllOf = schema => {
|
|
581
|
+
const {
|
|
582
|
+
allOf,
|
|
583
|
+
...rest
|
|
584
|
+
} = schema;
|
|
585
|
+
if (!Array.isArray(allOf) || allOf.length !== 1) return schema;
|
|
586
|
+
const [entry] = allOf;
|
|
587
|
+
if (!isPlainObject(entry)) return rest;
|
|
588
|
+
return {
|
|
589
|
+
...flattenSingleAllOf(entry),
|
|
590
|
+
...rest
|
|
591
|
+
};
|
|
592
|
+
};
|
|
593
|
+
var extractBodyProps = args => {
|
|
594
|
+
const toArgName = args.toArgName ?? snakeToCamel;
|
|
595
|
+
const bodySchema = resolveBodySchema(args);
|
|
596
|
+
if (!bodySchema?.properties) return [];
|
|
597
|
+
return Object.entries(bodySchema.properties).filter(([, value]) => {
|
|
598
|
+
return !readServerManaged({
|
|
599
|
+
node: value,
|
|
600
|
+
extension: args.serverManagedExtension
|
|
601
|
+
}).managed;
|
|
602
|
+
}).map(([key, value]) => {
|
|
603
|
+
const val = flattenSingleAllOf(value);
|
|
604
|
+
return {
|
|
605
|
+
snakeName: key,
|
|
606
|
+
argName: toArgName(key),
|
|
607
|
+
description: typeof val.description === "string" ? val.description : "",
|
|
608
|
+
required: (bodySchema.required || []).includes(key),
|
|
609
|
+
type: typeof val.type === "string" ? val.type : void 0,
|
|
610
|
+
items: val.items,
|
|
611
|
+
nullable: val.nullable === true,
|
|
612
|
+
oneOf: Array.isArray(val.oneOf) ? val.oneOf : void 0,
|
|
613
|
+
anyOf: Array.isArray(val.anyOf) ? val.anyOf : void 0,
|
|
614
|
+
allOf: Array.isArray(val.allOf) ? val.allOf : void 0
|
|
615
|
+
};
|
|
616
|
+
});
|
|
617
|
+
};
|
|
618
|
+
/**
|
|
619
|
+
* The value each server-managed body property pins, typed by its schema and
|
|
620
|
+
* keyed by its spec name. A property whose extension is not a string pins
|
|
621
|
+
* nothing: it is only hidden, for the consumer to fill.
|
|
622
|
+
*/
|
|
623
|
+
var extractPinnedBody = args => {
|
|
624
|
+
const bodySchema = resolveBodySchema(args);
|
|
625
|
+
const pinned = {};
|
|
626
|
+
for (const [key, value] of Object.entries(bodySchema?.properties ?? {})) {
|
|
627
|
+
const node = value;
|
|
628
|
+
const {
|
|
629
|
+
value: pin
|
|
630
|
+
} = readServerManaged({
|
|
631
|
+
node,
|
|
632
|
+
extension: args.serverManagedExtension
|
|
633
|
+
});
|
|
634
|
+
if (pin === void 0) continue;
|
|
635
|
+
pinned[key] = typedPin({
|
|
636
|
+
value: pin,
|
|
637
|
+
type: flattenSingleAllOf(node).type,
|
|
638
|
+
where: `${args.operationId} body property '${key}'`
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
return pinned;
|
|
642
|
+
};
|
|
643
|
+
|
|
410
644
|
//#endregion
|
|
411
645
|
//#region src/toolDefinitions.ts
|
|
412
646
|
/** Converts a camelCase `operationId` to a kebab-case tool name. */
|
|
@@ -495,7 +729,7 @@ var buildInputSchema = (pathParams, queryParams, bodyProps) => {
|
|
|
495
729
|
return p.argName;
|
|
496
730
|
})];
|
|
497
731
|
const properties = {};
|
|
498
|
-
for (const param of allParams) properties[param.argName] = "description" in param ? buildTypedProperty(param) : {
|
|
732
|
+
for (const param of allParams) properties[param.argName] = "description" in param ? normalizeNullable(buildTypedProperty(param)) : {
|
|
499
733
|
type: "string",
|
|
500
734
|
description: ""
|
|
501
735
|
};
|
|
@@ -507,63 +741,6 @@ var buildInputSchema = (pathParams, queryParams, bodyProps) => {
|
|
|
507
741
|
} : {})
|
|
508
742
|
};
|
|
509
743
|
};
|
|
510
|
-
var resolveBodySchema = args => {
|
|
511
|
-
const rawBodySchema = args.requestBody?.content?.["application/json"]?.schema;
|
|
512
|
-
return resolveSchema(dereferenceSchema(rawBodySchema, args.spec, args.documents), args.spec, args.documents);
|
|
513
|
-
};
|
|
514
|
-
/**
|
|
515
|
-
* snake_case names of every top-level property an operation's request schema
|
|
516
|
-
* declares, including server-managed ones.
|
|
517
|
-
*/
|
|
518
|
-
var extractAcceptedBodyFields = args => {
|
|
519
|
-
const bodySchema = resolveBodySchema(args);
|
|
520
|
-
return Object.keys(bodySchema?.properties ?? {});
|
|
521
|
-
};
|
|
522
|
-
var isPlainObject = value => {
|
|
523
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
524
|
-
};
|
|
525
|
-
/**
|
|
526
|
-
* Folds a single-entry `allOf` into the property that wraps it. OpenAPI
|
|
527
|
-
* declares `allOf: [{ $ref }]` so a property can carry its own `description`
|
|
528
|
-
* next to a referenced schema; without folding, the referenced `type`,
|
|
529
|
-
* `nullable` and `items` would be lost. Keys on the wrapper win over the
|
|
530
|
-
* referenced schema's. Multi-entry `allOf` is left intact.
|
|
531
|
-
*/
|
|
532
|
-
var flattenSingleAllOf = schema => {
|
|
533
|
-
const {
|
|
534
|
-
allOf,
|
|
535
|
-
...rest
|
|
536
|
-
} = schema;
|
|
537
|
-
if (!Array.isArray(allOf) || allOf.length !== 1) return schema;
|
|
538
|
-
const [entry] = allOf;
|
|
539
|
-
if (!isPlainObject(entry)) return rest;
|
|
540
|
-
return {
|
|
541
|
-
...flattenSingleAllOf(entry),
|
|
542
|
-
...rest
|
|
543
|
-
};
|
|
544
|
-
};
|
|
545
|
-
var extractBodyProps = args => {
|
|
546
|
-
const toArgName = args.toArgName ?? snakeToCamel;
|
|
547
|
-
const bodySchema = resolveBodySchema(args);
|
|
548
|
-
if (!bodySchema?.properties) return [];
|
|
549
|
-
return Object.entries(bodySchema.properties).filter(([, value]) => {
|
|
550
|
-
return !value[args.serverManagedExtension];
|
|
551
|
-
}).map(([key, value]) => {
|
|
552
|
-
const val = flattenSingleAllOf(value);
|
|
553
|
-
return {
|
|
554
|
-
snakeName: key,
|
|
555
|
-
argName: toArgName(key),
|
|
556
|
-
description: typeof val.description === "string" ? val.description : "",
|
|
557
|
-
required: (bodySchema.required || []).includes(key),
|
|
558
|
-
type: typeof val.type === "string" ? val.type : void 0,
|
|
559
|
-
items: val.items,
|
|
560
|
-
nullable: val.nullable === true,
|
|
561
|
-
oneOf: Array.isArray(val.oneOf) ? val.oneOf : void 0,
|
|
562
|
-
anyOf: Array.isArray(val.anyOf) ? val.anyOf : void 0,
|
|
563
|
-
allOf: Array.isArray(val.allOf) ? val.allOf : void 0
|
|
564
|
-
};
|
|
565
|
-
});
|
|
566
|
-
};
|
|
567
744
|
/** Collects every `x-` prefixed extension declared on the operation. */
|
|
568
745
|
var extractExtensions = operation => {
|
|
569
746
|
const extensions = {};
|
|
@@ -596,19 +773,22 @@ var processOperation = args => {
|
|
|
596
773
|
toArgName,
|
|
597
774
|
serverManagedExtension
|
|
598
775
|
});
|
|
599
|
-
const
|
|
776
|
+
const bodyArgs = {
|
|
600
777
|
requestBody: args.operation.requestBody,
|
|
601
778
|
spec: args.spec,
|
|
602
779
|
serverManagedExtension,
|
|
603
|
-
documents
|
|
780
|
+
documents
|
|
781
|
+
};
|
|
782
|
+
const bodyProps = extractBodyProps({
|
|
783
|
+
...bodyArgs,
|
|
604
784
|
toArgName
|
|
605
785
|
});
|
|
606
786
|
const inputSchema = buildInputSchema(pathParams, queryParams, bodyProps);
|
|
607
|
-
const
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
documents
|
|
787
|
+
const serverManagedParameters = collectServerManagedParameters({
|
|
788
|
+
pathParams,
|
|
789
|
+
queryParams
|
|
611
790
|
});
|
|
791
|
+
const pinned = pinnedArgs(serverManagedParameters);
|
|
612
792
|
return {
|
|
613
793
|
name: toolName,
|
|
614
794
|
description: sanitizeDescription(args.operation.description),
|
|
@@ -616,15 +796,24 @@ var processOperation = args => {
|
|
|
616
796
|
method: httpMethod,
|
|
617
797
|
pathTemplate: args.pathTemplate,
|
|
618
798
|
operationId: args.operation.operationId,
|
|
619
|
-
path:
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
799
|
+
path: withPinned({
|
|
800
|
+
build: buildPathFn(args.pathTemplate, pathParams),
|
|
801
|
+
pinned
|
|
802
|
+
}),
|
|
803
|
+
query: withPinned({
|
|
804
|
+
build: buildQueryFn(queryParams),
|
|
805
|
+
pinned
|
|
806
|
+
}),
|
|
807
|
+
body: withPinnedBody({
|
|
808
|
+
build: buildBodyFn(bodyProps),
|
|
809
|
+
pinned: extractPinnedBody({
|
|
810
|
+
...bodyArgs,
|
|
811
|
+
operationId: args.operation.operationId
|
|
812
|
+
})
|
|
813
|
+
}),
|
|
814
|
+
acceptedBodyFields: extractAcceptedBodyFields(bodyArgs),
|
|
623
815
|
extensions: extractExtensions(args.operation),
|
|
624
|
-
serverManagedParameters
|
|
625
|
-
pathParams,
|
|
626
|
-
queryParams
|
|
627
|
-
})
|
|
816
|
+
serverManagedParameters
|
|
628
817
|
};
|
|
629
818
|
};
|
|
630
819
|
var processPath = args => {
|
|
@@ -773,4 +962,4 @@ var registerOpenApiTools = args => {
|
|
|
773
962
|
};
|
|
774
963
|
|
|
775
964
|
//#endregion
|
|
776
|
-
export { DEFAULT_EXCLUDE_EXTENSION, DEFAULT_SERVER_MANAGED_EXTENSION, NO_CONTENT_TEXT, buildBodyFn, buildInputSchema, buildPathFn, buildQueryFn, dereferenceSchema, extractAcceptedBodyFields, extractBodyProps, extractPathParams, extractQueryParams, getJsonSchemaType, openApiToToolDefinitions, operationIdToToolName, processOperation, processPath, registerOpenApiTools, resolveParameter, resolveSchema, snakeToCamel };
|
|
965
|
+
export { DEFAULT_EXCLUDE_EXTENSION, DEFAULT_SERVER_MANAGED_EXTENSION, NO_CONTENT_TEXT, buildBodyFn, buildInputSchema, buildPathFn, buildQueryFn, dereferenceSchema, extractAcceptedBodyFields, extractBodyProps, extractPathParams, extractPinnedBody, extractQueryParams, getJsonSchemaType, openApiToToolDefinitions, operationIdToToolName, processOperation, processPath, registerOpenApiTools, resolveParameter, resolveSchema, snakeToCamel };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttoss/http-server-mcp-openapi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
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",
|