@ttoss/http-server-mcp-openapi 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -157,8 +157,9 @@ registerOpenApiTools({
157
157
 
158
158
  A value flagged with `serverManagedExtension` is never offered to the model:
159
159
 
160
- - A **request-body property** is hidden from `inputSchema` and never sent (the
161
- 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`.
162
163
  - A **path or query parameter** is hidden from `inputSchema` and listed in
163
164
  `tool.serverManagedParameters`. `registerOpenApiTools` discards anything the
164
165
  model sent for it and fills it from `serverParameters`, keyed by spec name:
@@ -183,6 +184,12 @@ A **string** extension value pins the parameter: `wait` declared with
183
184
  `serverParameters`, and the entry in `serverManagedParameters` carries it as
184
185
  `value`.
185
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
+
186
193
  ### Reading custom extensions
187
194
 
188
195
  Every `x-` prefixed extension on an operation is forwarded verbatim on
package/dist/index.cjs CHANGED
@@ -368,6 +368,57 @@ var withPinned = args => {
368
368
  });
369
369
  };
370
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
+ };
371
422
  var isPlainObject$1 = value => {
372
423
  return typeof value === "object" && value !== null && !Array.isArray(value);
373
424
  };
@@ -505,6 +556,94 @@ var collectServerManagedParameters = args => {
505
556
  });
506
557
  };
507
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
+
508
647
  //#endregion
509
648
  //#region src/toolDefinitions.ts
510
649
  /** Converts a camelCase `operationId` to a kebab-case tool name. */
@@ -605,66 +744,6 @@ var buildInputSchema = (pathParams, queryParams, bodyProps) => {
605
744
  } : {})
606
745
  };
607
746
  };
608
- var resolveBodySchema = args => {
609
- const rawBodySchema = args.requestBody?.content?.["application/json"]?.schema;
610
- return resolveSchema(dereferenceSchema(rawBodySchema, args.spec, args.documents), args.spec, args.documents);
611
- };
612
- /**
613
- * snake_case names of every top-level property an operation's request schema
614
- * declares, including server-managed ones.
615
- */
616
- var extractAcceptedBodyFields = args => {
617
- const bodySchema = resolveBodySchema(args);
618
- return Object.keys(bodySchema?.properties ?? {});
619
- };
620
- var isPlainObject = value => {
621
- return typeof value === "object" && value !== null && !Array.isArray(value);
622
- };
623
- /**
624
- * Folds a single-entry `allOf` into the property that wraps it. OpenAPI
625
- * declares `allOf: [{ $ref }]` so a property can carry its own `description`
626
- * next to a referenced schema; without folding, the referenced `type`,
627
- * `nullable` and `items` would be lost. Keys on the wrapper win over the
628
- * referenced schema's. Multi-entry `allOf` is left intact.
629
- */
630
- var flattenSingleAllOf = schema => {
631
- const {
632
- allOf,
633
- ...rest
634
- } = schema;
635
- if (!Array.isArray(allOf) || allOf.length !== 1) return schema;
636
- const [entry] = allOf;
637
- if (!isPlainObject(entry)) return rest;
638
- return {
639
- ...flattenSingleAllOf(entry),
640
- ...rest
641
- };
642
- };
643
- var extractBodyProps = args => {
644
- const toArgName = args.toArgName ?? snakeToCamel;
645
- const bodySchema = resolveBodySchema(args);
646
- if (!bodySchema?.properties) return [];
647
- return Object.entries(bodySchema.properties).filter(([, value]) => {
648
- return !readServerManaged({
649
- node: value,
650
- extension: args.serverManagedExtension
651
- }).managed;
652
- }).map(([key, value]) => {
653
- const val = flattenSingleAllOf(value);
654
- return {
655
- snakeName: key,
656
- argName: toArgName(key),
657
- description: typeof val.description === "string" ? val.description : "",
658
- required: (bodySchema.required || []).includes(key),
659
- type: typeof val.type === "string" ? val.type : void 0,
660
- items: val.items,
661
- nullable: val.nullable === true,
662
- oneOf: Array.isArray(val.oneOf) ? val.oneOf : void 0,
663
- anyOf: Array.isArray(val.anyOf) ? val.anyOf : void 0,
664
- allOf: Array.isArray(val.allOf) ? val.allOf : void 0
665
- };
666
- });
667
- };
668
747
  /** Collects every `x-` prefixed extension declared on the operation. */
669
748
  var extractExtensions = operation => {
670
749
  const extensions = {};
@@ -697,11 +776,14 @@ var processOperation = args => {
697
776
  toArgName,
698
777
  serverManagedExtension
699
778
  });
700
- const bodyProps = extractBodyProps({
779
+ const bodyArgs = {
701
780
  requestBody: args.operation.requestBody,
702
781
  spec: args.spec,
703
782
  serverManagedExtension,
704
- documents,
783
+ documents
784
+ };
785
+ const bodyProps = extractBodyProps({
786
+ ...bodyArgs,
705
787
  toArgName
706
788
  });
707
789
  const inputSchema = buildInputSchema(pathParams, queryParams, bodyProps);
@@ -710,11 +792,6 @@ var processOperation = args => {
710
792
  queryParams
711
793
  });
712
794
  const pinned = pinnedArgs(serverManagedParameters);
713
- const acceptedBodyFields = extractAcceptedBodyFields({
714
- requestBody: args.operation.requestBody,
715
- spec: args.spec,
716
- documents
717
- });
718
795
  return {
719
796
  name: toolName,
720
797
  description: sanitizeDescription(args.operation.description),
@@ -730,8 +807,14 @@ var processOperation = args => {
730
807
  build: buildQueryFn(queryParams),
731
808
  pinned
732
809
  }),
733
- body: buildBodyFn(bodyProps),
734
- acceptedBodyFields,
810
+ body: withPinnedBody({
811
+ build: buildBodyFn(bodyProps),
812
+ pinned: extractPinnedBody({
813
+ ...bodyArgs,
814
+ operationId: args.operation.operationId
815
+ })
816
+ }),
817
+ acceptedBodyFields: extractAcceptedBodyFields(bodyArgs),
735
818
  extensions: extractExtensions(args.operation),
736
819
  serverManagedParameters
737
820
  };
@@ -893,6 +976,7 @@ exports.dereferenceSchema = dereferenceSchema;
893
976
  exports.extractAcceptedBodyFields = extractAcceptedBodyFields;
894
977
  exports.extractBodyProps = extractBodyProps;
895
978
  exports.extractPathParams = extractPathParams;
979
+ exports.extractPinnedBody = extractPinnedBody;
896
980
  exports.extractQueryParams = extractQueryParams;
897
981
  exports.getJsonSchemaType = getJsonSchemaType;
898
982
  exports.openApiToToolDefinitions = openApiToToolDefinitions;
package/dist/index.d.cts CHANGED
@@ -232,6 +232,47 @@ declare const extractQueryParams: (args: ExtractParamsArgs) => Array<{
232
232
  pinnedValue?: string;
233
233
  }>;
234
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
235
276
  //#region src/registerOpenApiTools.d.ts
236
277
  /** The resolved HTTP request a tool call maps to, before transport concerns. */
237
278
  interface ResolvedRequest {
@@ -417,33 +458,6 @@ declare const buildInputSchema: (pathParams: Array<{
417
458
  anyOf?: unknown[];
418
459
  allOf?: unknown[];
419
460
  }>) => JsonObjectSchema;
420
- /**
421
- * snake_case names of every top-level property an operation's request schema
422
- * declares, including server-managed ones.
423
- */
424
- declare const extractAcceptedBodyFields: (args: {
425
- requestBody?: RequestBodySpec;
426
- spec: OpenApiSpec;
427
- documents?: OpenApiDocuments;
428
- }) => string[];
429
- declare const extractBodyProps: (args: {
430
- requestBody?: RequestBodySpec;
431
- spec: OpenApiSpec;
432
- serverManagedExtension: ServerManagedExtension;
433
- documents?: OpenApiDocuments; /** Maps a spec name to its tool argument name. @default snakeToCamel */
434
- toArgName?: ToArgName;
435
- }) => Array<{
436
- snakeName: string;
437
- argName: string;
438
- description: string;
439
- required: boolean;
440
- type?: string;
441
- items?: unknown;
442
- nullable: boolean;
443
- oneOf?: unknown[];
444
- anyOf?: unknown[];
445
- allOf?: unknown[];
446
- }>;
447
461
  declare const processOperation: (args: {
448
462
  pathTemplate: string;
449
463
  method: string;
@@ -484,4 +498,4 @@ declare const openApiToToolDefinitions: (args: {
484
498
  options?: OpenApiToToolsOptions;
485
499
  }) => ToolDefinition[];
486
500
  //#endregion
487
- 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
@@ -232,6 +232,47 @@ declare const extractQueryParams: (args: ExtractParamsArgs) => Array<{
232
232
  pinnedValue?: string;
233
233
  }>;
234
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
235
276
  //#region src/registerOpenApiTools.d.ts
236
277
  /** The resolved HTTP request a tool call maps to, before transport concerns. */
237
278
  interface ResolvedRequest {
@@ -417,33 +458,6 @@ declare const buildInputSchema: (pathParams: Array<{
417
458
  anyOf?: unknown[];
418
459
  allOf?: unknown[];
419
460
  }>) => JsonObjectSchema;
420
- /**
421
- * snake_case names of every top-level property an operation's request schema
422
- * declares, including server-managed ones.
423
- */
424
- declare const extractAcceptedBodyFields: (args: {
425
- requestBody?: RequestBodySpec;
426
- spec: OpenApiSpec;
427
- documents?: OpenApiDocuments;
428
- }) => string[];
429
- declare const extractBodyProps: (args: {
430
- requestBody?: RequestBodySpec;
431
- spec: OpenApiSpec;
432
- serverManagedExtension: ServerManagedExtension;
433
- documents?: OpenApiDocuments; /** Maps a spec name to its tool argument name. @default snakeToCamel */
434
- toArgName?: ToArgName;
435
- }) => Array<{
436
- snakeName: string;
437
- argName: string;
438
- description: string;
439
- required: boolean;
440
- type?: string;
441
- items?: unknown;
442
- nullable: boolean;
443
- oneOf?: unknown[];
444
- anyOf?: unknown[];
445
- allOf?: unknown[];
446
- }>;
447
461
  declare const processOperation: (args: {
448
462
  pathTemplate: string;
449
463
  method: string;
@@ -484,4 +498,4 @@ declare const openApiToToolDefinitions: (args: {
484
498
  options?: OpenApiToToolsOptions;
485
499
  }) => ToolDefinition[];
486
500
  //#endregion
487
- 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
@@ -365,6 +365,57 @@ var withPinned = args => {
365
365
  });
366
366
  };
367
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
+ };
368
419
  var isPlainObject$1 = value => {
369
420
  return typeof value === "object" && value !== null && !Array.isArray(value);
370
421
  };
@@ -502,6 +553,94 @@ var collectServerManagedParameters = args => {
502
553
  });
503
554
  };
504
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
+
505
644
  //#endregion
506
645
  //#region src/toolDefinitions.ts
507
646
  /** Converts a camelCase `operationId` to a kebab-case tool name. */
@@ -602,66 +741,6 @@ var buildInputSchema = (pathParams, queryParams, bodyProps) => {
602
741
  } : {})
603
742
  };
604
743
  };
605
- var resolveBodySchema = args => {
606
- const rawBodySchema = args.requestBody?.content?.["application/json"]?.schema;
607
- return resolveSchema(dereferenceSchema(rawBodySchema, args.spec, args.documents), args.spec, args.documents);
608
- };
609
- /**
610
- * snake_case names of every top-level property an operation's request schema
611
- * declares, including server-managed ones.
612
- */
613
- var extractAcceptedBodyFields = args => {
614
- const bodySchema = resolveBodySchema(args);
615
- return Object.keys(bodySchema?.properties ?? {});
616
- };
617
- var isPlainObject = value => {
618
- return typeof value === "object" && value !== null && !Array.isArray(value);
619
- };
620
- /**
621
- * Folds a single-entry `allOf` into the property that wraps it. OpenAPI
622
- * declares `allOf: [{ $ref }]` so a property can carry its own `description`
623
- * next to a referenced schema; without folding, the referenced `type`,
624
- * `nullable` and `items` would be lost. Keys on the wrapper win over the
625
- * referenced schema's. Multi-entry `allOf` is left intact.
626
- */
627
- var flattenSingleAllOf = schema => {
628
- const {
629
- allOf,
630
- ...rest
631
- } = schema;
632
- if (!Array.isArray(allOf) || allOf.length !== 1) return schema;
633
- const [entry] = allOf;
634
- if (!isPlainObject(entry)) return rest;
635
- return {
636
- ...flattenSingleAllOf(entry),
637
- ...rest
638
- };
639
- };
640
- var extractBodyProps = args => {
641
- const toArgName = args.toArgName ?? snakeToCamel;
642
- const bodySchema = resolveBodySchema(args);
643
- if (!bodySchema?.properties) return [];
644
- return Object.entries(bodySchema.properties).filter(([, value]) => {
645
- return !readServerManaged({
646
- node: value,
647
- extension: args.serverManagedExtension
648
- }).managed;
649
- }).map(([key, value]) => {
650
- const val = flattenSingleAllOf(value);
651
- return {
652
- snakeName: key,
653
- argName: toArgName(key),
654
- description: typeof val.description === "string" ? val.description : "",
655
- required: (bodySchema.required || []).includes(key),
656
- type: typeof val.type === "string" ? val.type : void 0,
657
- items: val.items,
658
- nullable: val.nullable === true,
659
- oneOf: Array.isArray(val.oneOf) ? val.oneOf : void 0,
660
- anyOf: Array.isArray(val.anyOf) ? val.anyOf : void 0,
661
- allOf: Array.isArray(val.allOf) ? val.allOf : void 0
662
- };
663
- });
664
- };
665
744
  /** Collects every `x-` prefixed extension declared on the operation. */
666
745
  var extractExtensions = operation => {
667
746
  const extensions = {};
@@ -694,11 +773,14 @@ var processOperation = args => {
694
773
  toArgName,
695
774
  serverManagedExtension
696
775
  });
697
- const bodyProps = extractBodyProps({
776
+ const bodyArgs = {
698
777
  requestBody: args.operation.requestBody,
699
778
  spec: args.spec,
700
779
  serverManagedExtension,
701
- documents,
780
+ documents
781
+ };
782
+ const bodyProps = extractBodyProps({
783
+ ...bodyArgs,
702
784
  toArgName
703
785
  });
704
786
  const inputSchema = buildInputSchema(pathParams, queryParams, bodyProps);
@@ -707,11 +789,6 @@ var processOperation = args => {
707
789
  queryParams
708
790
  });
709
791
  const pinned = pinnedArgs(serverManagedParameters);
710
- const acceptedBodyFields = extractAcceptedBodyFields({
711
- requestBody: args.operation.requestBody,
712
- spec: args.spec,
713
- documents
714
- });
715
792
  return {
716
793
  name: toolName,
717
794
  description: sanitizeDescription(args.operation.description),
@@ -727,8 +804,14 @@ var processOperation = args => {
727
804
  build: buildQueryFn(queryParams),
728
805
  pinned
729
806
  }),
730
- body: buildBodyFn(bodyProps),
731
- acceptedBodyFields,
807
+ body: withPinnedBody({
808
+ build: buildBodyFn(bodyProps),
809
+ pinned: extractPinnedBody({
810
+ ...bodyArgs,
811
+ operationId: args.operation.operationId
812
+ })
813
+ }),
814
+ acceptedBodyFields: extractAcceptedBodyFields(bodyArgs),
732
815
  extensions: extractExtensions(args.operation),
733
816
  serverManagedParameters
734
817
  };
@@ -879,4 +962,4 @@ var registerOpenApiTools = args => {
879
962
  };
880
963
 
881
964
  //#endregion
882
- 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.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Generate Model Context Protocol (MCP) tools from an OpenAPI specification for @ttoss/http-server-mcp",
5
5
  "keywords": [
6
6
  "ai",
@@ -35,15 +35,15 @@
35
35
  "dist"
36
36
  ],
37
37
  "dependencies": {
38
- "@ttoss/http-server-mcp": "^0.29.0"
38
+ "@ttoss/http-server-mcp": "^0.29.1"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@modelcontextprotocol/server": "^2.0.0",
42
42
  "jest": "^30.4.2",
43
43
  "supertest": "^7.2.2",
44
44
  "tsdown": "^0.22.2",
45
- "@ttoss/config": "^1.38.0",
46
- "@ttoss/http-server": "^0.8.1"
45
+ "@ttoss/http-server": "^0.9.0",
46
+ "@ttoss/config": "^1.39.0"
47
47
  },
48
48
  "publishConfig": {
49
49
  "access": "public",