@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 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', // values hidden from the input schema
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`) — see
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 never sent (the
157
- API sets it itself). It still appears in `acceptedBodyFields`.
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
- serverManaged: Boolean(p[flag])
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
- serverManaged: Boolean(p[flag])
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 bodyProps = extractBodyProps({
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 acceptedBodyFields = extractAcceptedBodyFields({
611
- requestBody: args.operation.requestBody,
612
- spec: args.spec,
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: buildPathFn(args.pathTemplate, pathParams),
623
- query: buildQueryFn(queryParams),
624
- body: buildBodyFn(bodyProps),
625
- acceptedBodyFields,
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: collectServerManagedParameters({
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?: string;
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?: string;
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
- serverManaged: Boolean(p[flag])
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
- serverManaged: Boolean(p[flag])
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 bodyProps = extractBodyProps({
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 acceptedBodyFields = extractAcceptedBodyFields({
608
- requestBody: args.operation.requestBody,
609
- spec: args.spec,
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: buildPathFn(args.pathTemplate, pathParams),
620
- query: buildQueryFn(queryParams),
621
- body: buildBodyFn(bodyProps),
622
- acceptedBodyFields,
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: collectServerManagedParameters({
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.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/http-server": "^0.8.1",
46
- "@ttoss/config": "^1.38.0"
45
+ "@ttoss/config": "^1.38.0",
46
+ "@ttoss/http-server": "^0.8.1"
47
47
  },
48
48
  "publishConfig": {
49
49
  "access": "public",