@ttoss/http-server-mcp-openapi 0.2.15 → 0.4.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,29 +313,200 @@ 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
  };
242
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 isPlainObject$1 = value => {
372
+ return typeof value === "object" && value !== null && !Array.isArray(value);
373
+ };
374
+ var mergeNullIntoType = schema => {
375
+ const {
376
+ type
377
+ } = schema;
378
+ if (typeof type === "string") schema.type = [type, "null"];else if (Array.isArray(type) && !type.includes("null")) schema.type = [...type, "null"];
379
+ };
380
+ /**
381
+ * Turns OpenAPI's `nullable` into JSON Schema at every depth: `nullable: true`
382
+ * merges `'null'` into a sibling `type` and is dropped where there is none.
383
+ * JSON Schema has no `nullable`, so a validator or model reading it would
384
+ * refuse `null` where the API accepts it.
385
+ *
386
+ * Only a boolean `nullable` is the keyword; a property *named* `nullable`
387
+ * inside `properties` is a schema and is kept.
388
+ */
389
+ var normalizeNullable = value => {
390
+ if (Array.isArray(value)) return value.map(normalizeNullable);
391
+ if (!isPlainObject$1(value)) return value;
392
+ const result = {};
393
+ for (const [key, nested] of Object.entries(value)) {
394
+ if (key === "nullable" && typeof nested === "boolean") continue;
395
+ result[key] = normalizeNullable(nested);
396
+ }
397
+ if (value.nullable === true) mergeNullIntoType(result);
398
+ return result;
399
+ };
400
+
243
401
  //#endregion
244
402
  //#region src/types.ts
245
403
  var DEFAULT_EXCLUDE_EXTENSION = "x-mcp-exclude";
246
404
  var DEFAULT_SERVER_MANAGED_EXTENSION = "x-mcp-server-managed";
247
405
 
248
406
  //#endregion
249
- //#region src/toolDefinitions.ts
407
+ //#region src/parameters.ts
250
408
  /**
251
409
  * 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.
410
+ * names may be snake_case (`agent_id`) or kebab-case (`list-tools`); with the
411
+ * default `argumentNames: 'camelCase'` both are folded into tool arguments.
254
412
  */
255
413
  var snakeToCamel = str => {
256
414
  return str.replace(/[_-]([a-z])/g, (_, letter) => {
257
415
  return letter.toUpperCase();
258
416
  });
259
417
  };
418
+ var verbatim = name => {
419
+ return name;
420
+ };
421
+ var argNameMapper = argumentNames => {
422
+ return argumentNames === "verbatim" ? verbatim : snakeToCamel;
423
+ };
424
+ /**
425
+ * Deduplicates parameter entries by `name`, keeping the last occurrence. When
426
+ * path-item-level and operation-level parameters are concatenated (operation
427
+ * last), this makes the operation-level entry win — as the OpenAPI spec requires.
428
+ */
429
+ var dedupeByName = items => {
430
+ const byName = /* @__PURE__ */new Map();
431
+ for (const item of items) byName.set(item.name, item);
432
+ return [...byName.values()];
433
+ };
434
+ var managedFields = flag => {
435
+ return flag.value === void 0 ? {
436
+ serverManaged: flag.managed
437
+ } : {
438
+ serverManaged: true,
439
+ pinnedValue: flag.value
440
+ };
441
+ };
442
+ var extractPathParams = args => {
443
+ const toArgName = args.toArgName ?? snakeToCamel;
444
+ const flag = args.serverManagedExtension ?? "x-mcp-server-managed";
445
+ return dedupeByName((args.parameters || []).map(p => {
446
+ return resolveParameter(p, args.spec, args.documents);
447
+ }).filter(p => {
448
+ return p.in === "path";
449
+ }).map(p => {
450
+ return {
451
+ name: p.name || "",
452
+ argName: toArgName(p.name || ""),
453
+ ...managedFields(readServerManaged({
454
+ node: p,
455
+ extension: flag
456
+ }))
457
+ };
458
+ }));
459
+ };
460
+ var extractQueryParams = args => {
461
+ const toArgName = args.toArgName ?? snakeToCamel;
462
+ const flag = args.serverManagedExtension ?? "x-mcp-server-managed";
463
+ return dedupeByName((args.parameters || []).map(p => {
464
+ return resolveParameter(p, args.spec, args.documents);
465
+ }).filter(p => {
466
+ return p.in === "query";
467
+ }).map(p => {
468
+ return {
469
+ name: p.name || "",
470
+ argName: toArgName(p.name || ""),
471
+ description: p.description || "",
472
+ required: p.required || false,
473
+ type: p.schema?.type || "string",
474
+ style: p.style,
475
+ explode: p.explode,
476
+ ...managedFields(readServerManaged({
477
+ node: p,
478
+ extension: flag
479
+ }))
480
+ };
481
+ }));
482
+ };
483
+ var collectServerManagedParameters = args => {
484
+ return [...args.pathParams.map(p => {
485
+ return {
486
+ ...p,
487
+ in: "path"
488
+ };
489
+ }), ...args.queryParams.map(p => {
490
+ return {
491
+ ...p,
492
+ in: "query"
493
+ };
494
+ })].filter(p => {
495
+ return p.serverManaged;
496
+ }).map(p => {
497
+ return {
498
+ name: p.name,
499
+ in: p.in,
500
+ argName: p.argName,
501
+ ...(p.pinnedValue === void 0 ? {} : {
502
+ value: p.pinnedValue
503
+ })
504
+ };
505
+ });
506
+ };
507
+
508
+ //#endregion
509
+ //#region src/toolDefinitions.ts
260
510
  /** Converts a camelCase `operationId` to a kebab-case tool name. */
261
511
  var operationIdToToolName = operationId => {
262
512
  return operationId.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "");
@@ -321,23 +571,29 @@ var buildTypedProperty = param => {
321
571
  };
322
572
  };
323
573
  var buildInputSchema = (pathParams, queryParams, bodyProps) => {
324
- const allParams = [...pathParams, ...queryParams, ...bodyProps];
574
+ const modelPathParams = pathParams.filter(p => {
575
+ return !p.serverManaged;
576
+ });
577
+ const modelQueryParams = queryParams.filter(p => {
578
+ return !p.serverManaged;
579
+ });
580
+ const allParams = [...modelPathParams, ...modelQueryParams, ...bodyProps];
325
581
  if (allParams.length === 0) return {
326
582
  type: "object"
327
583
  };
328
- const requiredFields = [...pathParams.map(p => {
329
- return p.camelName;
330
- }), ...queryParams.filter(p => {
584
+ const requiredFields = [...modelPathParams.map(p => {
585
+ return p.argName;
586
+ }), ...modelQueryParams.filter(p => {
331
587
  return p.required;
332
588
  }).map(p => {
333
- return p.camelName;
589
+ return p.argName;
334
590
  }), ...bodyProps.filter(p => {
335
591
  return p.required;
336
592
  }).map(p => {
337
- return p.camelName;
593
+ return p.argName;
338
594
  })];
339
595
  const properties = {};
340
- for (const param of allParams) properties[param.camelName] = "description" in param ? buildTypedProperty(param) : {
596
+ for (const param of allParams) properties[param.argName] = "description" in param ? normalizeNullable(buildTypedProperty(param)) : {
341
597
  type: "string",
342
598
  description: ""
343
599
  };
@@ -349,48 +605,9 @@ var buildInputSchema = (pathParams, queryParams, bodyProps) => {
349
605
  } : {})
350
606
  };
351
607
  };
352
- /**
353
- * Deduplicates parameter entries by `name`, keeping the last occurrence. When
354
- * path-item-level and operation-level parameters are concatenated (operation
355
- * last), this makes the operation-level entry win — as the OpenAPI spec requires.
356
- */
357
- var dedupeByName = items => {
358
- const byName = /* @__PURE__ */new Map();
359
- for (const item of items) byName.set(item.name, item);
360
- return [...byName.values()];
361
- };
362
- var extractPathParams = args => {
363
- return dedupeByName((args.parameters || []).map(p => {
364
- return resolveParameter(p, args.spec);
365
- }).filter(p => {
366
- return p.in === "path";
367
- }).map(p => {
368
- return {
369
- name: p.name || "",
370
- camelName: snakeToCamel(p.name || "")
371
- };
372
- }));
373
- };
374
- var extractQueryParams = args => {
375
- return dedupeByName((args.parameters || []).map(p => {
376
- return resolveParameter(p, args.spec);
377
- }).filter(p => {
378
- return p.in === "query";
379
- }).map(p => {
380
- return {
381
- name: p.name || "",
382
- camelName: snakeToCamel(p.name || ""),
383
- description: p.description || "",
384
- required: p.required || false,
385
- type: p.schema?.type || "string",
386
- style: p.style,
387
- explode: p.explode
388
- };
389
- }));
390
- };
391
608
  var resolveBodySchema = args => {
392
609
  const rawBodySchema = args.requestBody?.content?.["application/json"]?.schema;
393
- return resolveSchema(dereferenceSchema(rawBodySchema, args.spec), args.spec);
610
+ return resolveSchema(dereferenceSchema(rawBodySchema, args.spec, args.documents), args.spec, args.documents);
394
611
  };
395
612
  /**
396
613
  * snake_case names of every top-level property an operation's request schema
@@ -424,15 +641,19 @@ var flattenSingleAllOf = schema => {
424
641
  };
425
642
  };
426
643
  var extractBodyProps = args => {
644
+ const toArgName = args.toArgName ?? snakeToCamel;
427
645
  const bodySchema = resolveBodySchema(args);
428
646
  if (!bodySchema?.properties) return [];
429
647
  return Object.entries(bodySchema.properties).filter(([, value]) => {
430
- return !value[args.serverManagedExtension];
648
+ return !readServerManaged({
649
+ node: value,
650
+ extension: args.serverManagedExtension
651
+ }).managed;
431
652
  }).map(([key, value]) => {
432
653
  const val = flattenSingleAllOf(value);
433
654
  return {
434
655
  snakeName: key,
435
- camelName: snakeToCamel(key),
656
+ argName: toArgName(key),
436
657
  description: typeof val.description === "string" ? val.description : "",
437
658
  required: (bodySchema.required || []).includes(key),
438
659
  type: typeof val.type === "string" ? val.type : void 0,
@@ -457,23 +678,42 @@ var processOperation = args => {
457
678
  if (args.operation[args.options.excludeExtension]) return null;
458
679
  const toolName = operationIdToToolName(args.operation.operationId);
459
680
  const parameters = [...(args.pathItemParameters ?? []), ...(args.operation.parameters ?? [])];
681
+ const {
682
+ documents,
683
+ serverManagedExtension
684
+ } = args.options;
685
+ const toArgName = argNameMapper(args.options.argumentNames);
460
686
  const pathParams = extractPathParams({
461
687
  parameters,
462
- spec: args.spec
688
+ spec: args.spec,
689
+ documents,
690
+ toArgName,
691
+ serverManagedExtension
463
692
  });
464
693
  const queryParams = extractQueryParams({
465
694
  parameters,
466
- spec: args.spec
695
+ spec: args.spec,
696
+ documents,
697
+ toArgName,
698
+ serverManagedExtension
467
699
  });
468
700
  const bodyProps = extractBodyProps({
469
701
  requestBody: args.operation.requestBody,
470
702
  spec: args.spec,
471
- serverManagedExtension: args.options.serverManagedExtension
703
+ serverManagedExtension,
704
+ documents,
705
+ toArgName
472
706
  });
473
707
  const inputSchema = buildInputSchema(pathParams, queryParams, bodyProps);
708
+ const serverManagedParameters = collectServerManagedParameters({
709
+ pathParams,
710
+ queryParams
711
+ });
712
+ const pinned = pinnedArgs(serverManagedParameters);
474
713
  const acceptedBodyFields = extractAcceptedBodyFields({
475
714
  requestBody: args.operation.requestBody,
476
- spec: args.spec
715
+ spec: args.spec,
716
+ documents
477
717
  });
478
718
  return {
479
719
  name: toolName,
@@ -482,11 +722,18 @@ var processOperation = args => {
482
722
  method: httpMethod,
483
723
  pathTemplate: args.pathTemplate,
484
724
  operationId: args.operation.operationId,
485
- path: buildPathFn(args.pathTemplate, pathParams),
486
- query: buildQueryFn(queryParams),
725
+ path: withPinned({
726
+ build: buildPathFn(args.pathTemplate, pathParams),
727
+ pinned
728
+ }),
729
+ query: withPinned({
730
+ build: buildQueryFn(queryParams),
731
+ pinned
732
+ }),
487
733
  body: buildBodyFn(bodyProps),
488
734
  acceptedBodyFields,
489
- extensions: extractExtensions(args.operation)
735
+ extensions: extractExtensions(args.operation),
736
+ serverManagedParameters
490
737
  };
491
738
  };
492
739
  var processPath = args => {
@@ -507,6 +754,15 @@ var processPath = args => {
507
754
  }
508
755
  return tools;
509
756
  };
757
+ /** Applies the defaults to {@link OpenApiToToolsOptions}. */
758
+ var resolveOptions = (options = {}) => {
759
+ return {
760
+ excludeExtension: options.excludeExtension ?? "x-mcp-exclude",
761
+ serverManagedExtension: options.serverManagedExtension ?? "x-mcp-server-managed",
762
+ argumentNames: options.argumentNames ?? "camelCase",
763
+ documents: options.documents
764
+ };
765
+ };
510
766
  /**
511
767
  * Translates one or more OpenAPI documents into REST-backed MCP tool
512
768
  * definitions. Each translatable operation (has an `operationId`, a supported
@@ -520,10 +776,7 @@ var processPath = args => {
520
776
  * ```
521
777
  */
522
778
  var openApiToToolDefinitions = args => {
523
- const options = {
524
- excludeExtension: args.options?.excludeExtension ?? "x-mcp-exclude",
525
- serverManagedExtension: args.options?.serverManagedExtension ?? "x-mcp-server-managed"
526
- };
779
+ const options = resolveOptions(args.options);
527
780
  const specs = Array.isArray(args.spec) ? args.spec : [args.spec];
528
781
  const tools = [];
529
782
  for (const spec of specs) {
@@ -540,13 +793,34 @@ var openApiToToolDefinitions = args => {
540
793
 
541
794
  //#endregion
542
795
  //#region src/registerOpenApiTools.ts
796
+ /** The text the default `toText` answers when the API returned no body. */
797
+ var NO_CONTENT_TEXT = "Succeeded. The operation returned no content.";
543
798
  var defaultToText = data => {
799
+ if (data === void 0 || data === "") return NO_CONTENT_TEXT;
544
800
  return typeof data === "string" ? data : JSON.stringify(data, null, 2);
545
801
  };
546
802
  /**
803
+ * Replaces whatever the model sent for server-managed parameters with the
804
+ * values `serverParameters` supplies, so the model can never set them.
805
+ */
806
+ var applyServerParameters = async args => {
807
+ const managed = args.tool.serverManagedParameters;
808
+ if (managed.length === 0) return args.handlerArgs;
809
+ const result = {
810
+ ...args.handlerArgs
811
+ };
812
+ for (const param of managed) delete result[param.argName];
813
+ const values = args.serverParameters ? await args.serverParameters({
814
+ tool: args.tool,
815
+ headers: args.headers
816
+ }) : {};
817
+ for (const param of managed) if (values[param.name] !== void 0) result[param.argName] = values[param.name];
818
+ return result;
819
+ };
820
+ /**
547
821
  * Derives MCP tools from OpenAPI document(s) and registers each on the given
548
- * MCP server. Every tool's handler resolves the incoming camelCase args into a
549
- * concrete HTTP request and delegates execution to `callApi`.
822
+ * MCP server. Every tool's handler resolves the incoming args into a concrete
823
+ * HTTP request and delegates execution to `callApi`.
550
824
  *
551
825
  * @returns The list of {@link ToolDefinition} that were registered.
552
826
  *
@@ -560,10 +834,10 @@ var defaultToText = data => {
560
834
  * registerOpenApiTools({
561
835
  * server,
562
836
  * spec: myOpenApiDocument,
563
- * callApi: async ({ method, url, body }) => {
837
+ * callApi: async ({ method, url, body, headers }) => {
564
838
  * const res = await fetch(`https://api.example.com${url}`, {
565
839
  * method,
566
- * headers: { 'Content-Type': 'application/json' },
840
+ * headers: { ...headers, 'Content-Type': 'application/json' },
567
841
  * body: body ? JSON.stringify(body) : undefined,
568
842
  * });
569
843
  * return res.json();
@@ -581,7 +855,14 @@ var registerOpenApiTools = args => {
581
855
  name: tool.name,
582
856
  description: tool.description,
583
857
  inputSchema: tool.inputSchema,
584
- handler: async handlerArgs => {
858
+ handler: async rawArgs => {
859
+ const headers = (0, _ttoss_http_server_mcp.getApiHeaders)();
860
+ const handlerArgs = await applyServerParameters({
861
+ tool,
862
+ handlerArgs: rawArgs,
863
+ headers,
864
+ serverParameters: args.serverParameters
865
+ });
585
866
  const url = tool.path(handlerArgs) + (tool.query ? tool.query(handlerArgs) : "");
586
867
  return {
587
868
  content: [{
@@ -590,7 +871,8 @@ var registerOpenApiTools = args => {
590
871
  method: tool.method,
591
872
  url,
592
873
  body: tool.body ? tool.body(handlerArgs) : void 0,
593
- tool
874
+ tool,
875
+ headers
594
876
  }))
595
877
  }]
596
878
  };
@@ -602,6 +884,7 @@ var registerOpenApiTools = args => {
602
884
  //#endregion
603
885
  exports.DEFAULT_EXCLUDE_EXTENSION = DEFAULT_EXCLUDE_EXTENSION;
604
886
  exports.DEFAULT_SERVER_MANAGED_EXTENSION = DEFAULT_SERVER_MANAGED_EXTENSION;
887
+ exports.NO_CONTENT_TEXT = NO_CONTENT_TEXT;
605
888
  exports.buildBodyFn = buildBodyFn;
606
889
  exports.buildInputSchema = buildInputSchema;
607
890
  exports.buildPathFn = buildPathFn;