@ttoss/http-server-mcp-openapi 0.2.14 → 0.3.0

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