@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.mjs CHANGED
@@ -1,7 +1,10 @@
1
1
  /** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
2
- import { registerToolFromSchema } from "@ttoss/http-server-mcp";
2
+ import { getApiHeaders, registerToolFromSchema } from "@ttoss/http-server-mcp";
3
3
 
4
4
  //#region src/schema.ts
5
+ var isRecord = value => {
6
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7
+ };
5
8
  var getAlternativeSchemas = schema => {
6
9
  if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) return schema.oneOf;
7
10
  if (Array.isArray(schema.anyOf) && schema.anyOf.length > 0) return schema.anyOf;
@@ -23,36 +26,96 @@ var mergeResolvedSchemas = resolvedAlternatives => {
23
26
  required: requiredIntersection && requiredIntersection.length > 0 ? requiredIntersection : void 0
24
27
  };
25
28
  };
29
+ var stripDotSlash = file => {
30
+ return file.replace(/^\.\//, "");
31
+ };
32
+ var findDocument = args => {
33
+ if (!args.documents) return void 0;
34
+ const wanted = stripDotSlash(args.file);
35
+ for (const [key, document] of Object.entries(args.documents)) if (stripDotSlash(key) === wanted) return document;
36
+ };
37
+ /** Follows an RFC 6901 JSON pointer (`/components/schemas/Tag`) into a value. */
38
+ var followPointer = args => {
39
+ const tokens = args.pointer.split("/").slice(1);
40
+ let current = args.root;
41
+ for (const rawToken of tokens) {
42
+ if (!isRecord(current) && !Array.isArray(current)) return void 0;
43
+ const token = decodeURIComponent(rawToken).replace(/~1/g, "/").replace(/~0/g, "~");
44
+ current = current[token];
45
+ }
46
+ return current;
47
+ };
48
+ /** Stable identity for each document, so cycle keys never collide across files. */
49
+ var documentIds = /* @__PURE__ */new WeakMap();
50
+ var nextDocumentId = 0;
51
+ var documentId = spec => {
52
+ const known = documentIds.get(spec);
53
+ if (known !== void 0) return known;
54
+ nextDocumentId += 1;
55
+ documentIds.set(spec, nextDocumentId);
56
+ return nextDocumentId;
57
+ };
58
+ /**
59
+ * Resolves a `$ref` to its target and the document the target lives in (the
60
+ * scope its own nested refs resolve against). A ref without a file part
61
+ * (`#/components/schemas/X`) points into the current document; one with a
62
+ * file part (`./tags.yaml#/components/schemas/Tag`) points into the matching
63
+ * entry of `documents`. Returns `undefined` when the target does not exist.
64
+ */
65
+ var resolveRef = args => {
66
+ const hashIndex = args.ref.indexOf("#");
67
+ const file = hashIndex === -1 ? args.ref : args.ref.slice(0, hashIndex);
68
+ const pointer = hashIndex === -1 ? "" : args.ref.slice(hashIndex + 1);
69
+ const spec = file === "" ? args.scope.spec : findDocument({
70
+ file,
71
+ documents: args.scope.documents
72
+ });
73
+ if (!spec) return void 0;
74
+ const value = followPointer({
75
+ root: spec,
76
+ pointer
77
+ });
78
+ if (value === void 0) return void 0;
79
+ return {
80
+ value,
81
+ scope: {
82
+ spec,
83
+ documents: args.scope.documents
84
+ },
85
+ key: `${documentId(spec)}#${pointer}`
86
+ };
87
+ };
26
88
  var dereferenceValue = args => {
27
89
  const {
28
90
  value,
29
- spec,
91
+ scope,
30
92
  seenRefs
31
93
  } = args;
32
94
  if (Array.isArray(value)) return value.map(item => {
33
95
  return dereferenceValue({
34
96
  value: item,
35
- spec,
97
+ scope,
36
98
  seenRefs
37
99
  });
38
100
  });
39
- if (value && typeof value === "object") {
40
- const obj = value;
41
- if (typeof obj.$ref === "string") {
42
- const refName = obj.$ref.replace("#/components/schemas/", "");
43
- if (seenRefs.has(refName)) return {};
44
- const resolved = spec.components?.schemas?.[refName];
45
- if (resolved === void 0) return {};
101
+ if (isRecord(value)) {
102
+ if (typeof value.$ref === "string") {
103
+ const target = resolveRef({
104
+ ref: value.$ref,
105
+ scope
106
+ });
107
+ if (!target) return {};
108
+ if (seenRefs.has(target.key)) return {};
46
109
  return dereferenceValue({
47
- value: resolved,
48
- spec,
49
- seenRefs: new Set(seenRefs).add(refName)
110
+ value: target.value,
111
+ scope: target.scope,
112
+ seenRefs: new Set(seenRefs).add(target.key)
50
113
  });
51
114
  }
52
115
  const result = {};
53
- for (const [key, entryValue] of Object.entries(obj)) result[key] = dereferenceValue({
116
+ for (const [key, entryValue] of Object.entries(value)) result[key] = dereferenceValue({
54
117
  value: entryValue,
55
- spec,
118
+ scope,
56
119
  seenRefs
57
120
  });
58
121
  return result;
@@ -64,12 +127,17 @@ var dereferenceValue = args => {
64
127
  * `properties`, `items`, `oneOf`, `anyOf`, etc.), producing a self-contained
65
128
  * schema safe to hand to an MCP client or LLM provider as a tool definition —
66
129
  * provider tool schemas have no `components` section to resolve refs against.
130
+ *
131
+ * Refs into other files resolve against `documents`; see {@link OpenApiDocuments}.
67
132
  */
68
- var dereferenceSchema = (schema, spec) => {
133
+ var dereferenceSchema = (schema, spec, documents) => {
69
134
  if (!schema) return schema;
70
135
  return dereferenceValue({
71
136
  value: schema,
72
- spec,
137
+ scope: {
138
+ spec,
139
+ documents
140
+ },
73
141
  seenRefs: /* @__PURE__ */new Set()
74
142
  });
75
143
  };
@@ -78,25 +146,39 @@ var dereferenceSchema = (schema, spec) => {
78
146
  * and merges `oneOf` / `anyOf` alternatives (union of properties, intersection
79
147
  * of `required`) so the caller sees one flat property set.
80
148
  */
81
- var resolveSchema = (schema, spec) => {
149
+ var resolveSchema = (schema, spec, documents) => {
82
150
  if (!schema) return {};
83
151
  if (typeof schema.$ref === "string") {
84
- const refName = schema.$ref.replace("#/components/schemas/", "");
85
- const resolved = spec.components?.schemas?.[refName];
86
- return resolveSchema(resolved, spec);
152
+ const target = resolveRef({
153
+ ref: schema.$ref,
154
+ scope: {
155
+ spec,
156
+ documents
157
+ }
158
+ });
159
+ return resolveSchema(target?.value, target?.scope.spec ?? spec, documents);
87
160
  }
88
161
  const alternatives = getAlternativeSchemas(schema);
89
162
  if (alternatives) return mergeResolvedSchemas(alternatives.map(candidate => {
90
- return resolveSchema(candidate, spec);
163
+ return resolveSchema(candidate, spec, documents);
91
164
  }));
92
165
  return schema;
93
166
  };
94
- /** Follows a parameter `$ref` into `components.parameters`, if present. */
95
- var resolveParameter = (param, spec) => {
167
+ /**
168
+ * Follows a parameter `$ref` — into `components.parameters`, or into another
169
+ * file through `documents` — if present.
170
+ */
171
+ var resolveParameter = (param, spec, documents) => {
96
172
  if (!param) return {};
97
173
  if (typeof param.$ref === "string") {
98
- const refName = param.$ref.replace("#/components/parameters/", "");
99
- return spec.components?.parameters?.[refName] || {};
174
+ const target = resolveRef({
175
+ ref: param.$ref,
176
+ scope: {
177
+ spec,
178
+ documents
179
+ }
180
+ });
181
+ return isRecord(target?.value) ? target.value : {};
100
182
  }
101
183
  return param;
102
184
  };
@@ -106,9 +188,9 @@ var buildPathFn = (pathTemplate, pathParams) => {
106
188
  let result = pathTemplate;
107
189
  for (const {
108
190
  name,
109
- camelName
191
+ argName
110
192
  } of pathParams) {
111
- const value = args[camelName];
193
+ const value = args[argName];
112
194
  if (value !== void 0) result = result.replace(`{${name}}`, encodeURIComponent(String(value)));
113
195
  }
114
196
  return result;
@@ -119,9 +201,6 @@ var NON_EXPLODED_DELIMITERS = {
119
201
  spaceDelimited: " ",
120
202
  pipeDelimited: "|"
121
203
  };
122
- var isRecord = value => {
123
- return typeof value === "object" && value !== null && !Array.isArray(value);
124
- };
125
204
  /**
126
205
  * Appends a `deepObject` value as bracketed keys — `filters[documentId][$eq]`.
127
206
  * OpenAPI only defines one level, but the APIs that ask for `deepObject`
@@ -209,7 +288,7 @@ var buildQueryFn = queryParams => {
209
288
  return args => {
210
289
  const search = new URLSearchParams();
211
290
  for (const param of queryParams) {
212
- const value = args[param.camelName];
291
+ const value = args[param.argName];
213
292
  if (value === void 0 || value === null) continue;
214
293
  appendQueryValue({
215
294
  search,
@@ -222,8 +301,8 @@ var buildQueryFn = queryParams => {
222
301
  };
223
302
  };
224
303
  /**
225
- * Builds a function that maps camelCase args back to a snake_case request
226
- * body, skipping `undefined` args. Returns `undefined` when the op has no body.
304
+ * Builds a function that maps tool args back to a request body keyed by the
305
+ * spec's property names, skipping `undefined` args. Returns `undefined` when the op has no body.
227
306
  */
228
307
  var buildBodyFn = bodyProps => {
229
308
  if (bodyProps.length === 0) return void 0;
@@ -231,29 +310,200 @@ var buildBodyFn = bodyProps => {
231
310
  const body = {};
232
311
  for (const {
233
312
  snakeName,
234
- camelName
235
- } of bodyProps) if (args[camelName] !== void 0) body[snakeName] = args[camelName];
313
+ argName
314
+ } of bodyProps) if (args[argName] !== void 0) body[snakeName] = args[argName];
236
315
  return body;
237
316
  };
238
317
  };
239
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 isPlainObject$1 = value => {
369
+ return typeof value === "object" && value !== null && !Array.isArray(value);
370
+ };
371
+ var mergeNullIntoType = schema => {
372
+ const {
373
+ type
374
+ } = schema;
375
+ if (typeof type === "string") schema.type = [type, "null"];else if (Array.isArray(type) && !type.includes("null")) schema.type = [...type, "null"];
376
+ };
377
+ /**
378
+ * Turns OpenAPI's `nullable` into JSON Schema at every depth: `nullable: true`
379
+ * merges `'null'` into a sibling `type` and is dropped where there is none.
380
+ * JSON Schema has no `nullable`, so a validator or model reading it would
381
+ * refuse `null` where the API accepts it.
382
+ *
383
+ * Only a boolean `nullable` is the keyword; a property *named* `nullable`
384
+ * inside `properties` is a schema and is kept.
385
+ */
386
+ var normalizeNullable = value => {
387
+ if (Array.isArray(value)) return value.map(normalizeNullable);
388
+ if (!isPlainObject$1(value)) return value;
389
+ const result = {};
390
+ for (const [key, nested] of Object.entries(value)) {
391
+ if (key === "nullable" && typeof nested === "boolean") continue;
392
+ result[key] = normalizeNullable(nested);
393
+ }
394
+ if (value.nullable === true) mergeNullIntoType(result);
395
+ return result;
396
+ };
397
+
240
398
  //#endregion
241
399
  //#region src/types.ts
242
400
  var DEFAULT_EXCLUDE_EXTENSION = "x-mcp-exclude";
243
401
  var DEFAULT_SERVER_MANAGED_EXTENSION = "x-mcp-server-managed";
244
402
 
245
403
  //#endregion
246
- //#region src/toolDefinitions.ts
404
+ //#region src/parameters.ts
247
405
  /**
248
406
  * Folds `_` and `-` separators into camelCase. OpenAPI operation and parameter
249
- * names may be snake_case (`agent_id`) or kebab-case (`list-tools`), and MCP
250
- * tool inputs are camelCase by convention, so both are folded here.
407
+ * names may be snake_case (`agent_id`) or kebab-case (`list-tools`); with the
408
+ * default `argumentNames: 'camelCase'` both are folded into tool arguments.
251
409
  */
252
410
  var snakeToCamel = str => {
253
411
  return str.replace(/[_-]([a-z])/g, (_, letter) => {
254
412
  return letter.toUpperCase();
255
413
  });
256
414
  };
415
+ var verbatim = name => {
416
+ return name;
417
+ };
418
+ var argNameMapper = argumentNames => {
419
+ return argumentNames === "verbatim" ? verbatim : snakeToCamel;
420
+ };
421
+ /**
422
+ * Deduplicates parameter entries by `name`, keeping the last occurrence. When
423
+ * path-item-level and operation-level parameters are concatenated (operation
424
+ * last), this makes the operation-level entry win — as the OpenAPI spec requires.
425
+ */
426
+ var dedupeByName = items => {
427
+ const byName = /* @__PURE__ */new Map();
428
+ for (const item of items) byName.set(item.name, item);
429
+ return [...byName.values()];
430
+ };
431
+ var managedFields = flag => {
432
+ return flag.value === void 0 ? {
433
+ serverManaged: flag.managed
434
+ } : {
435
+ serverManaged: true,
436
+ pinnedValue: flag.value
437
+ };
438
+ };
439
+ var extractPathParams = args => {
440
+ const toArgName = args.toArgName ?? snakeToCamel;
441
+ const flag = args.serverManagedExtension ?? "x-mcp-server-managed";
442
+ return dedupeByName((args.parameters || []).map(p => {
443
+ return resolveParameter(p, args.spec, args.documents);
444
+ }).filter(p => {
445
+ return p.in === "path";
446
+ }).map(p => {
447
+ return {
448
+ name: p.name || "",
449
+ argName: toArgName(p.name || ""),
450
+ ...managedFields(readServerManaged({
451
+ node: p,
452
+ extension: flag
453
+ }))
454
+ };
455
+ }));
456
+ };
457
+ var extractQueryParams = args => {
458
+ const toArgName = args.toArgName ?? snakeToCamel;
459
+ const flag = args.serverManagedExtension ?? "x-mcp-server-managed";
460
+ return dedupeByName((args.parameters || []).map(p => {
461
+ return resolveParameter(p, args.spec, args.documents);
462
+ }).filter(p => {
463
+ return p.in === "query";
464
+ }).map(p => {
465
+ return {
466
+ name: p.name || "",
467
+ argName: toArgName(p.name || ""),
468
+ description: p.description || "",
469
+ required: p.required || false,
470
+ type: p.schema?.type || "string",
471
+ style: p.style,
472
+ explode: p.explode,
473
+ ...managedFields(readServerManaged({
474
+ node: p,
475
+ extension: flag
476
+ }))
477
+ };
478
+ }));
479
+ };
480
+ var collectServerManagedParameters = args => {
481
+ return [...args.pathParams.map(p => {
482
+ return {
483
+ ...p,
484
+ in: "path"
485
+ };
486
+ }), ...args.queryParams.map(p => {
487
+ return {
488
+ ...p,
489
+ in: "query"
490
+ };
491
+ })].filter(p => {
492
+ return p.serverManaged;
493
+ }).map(p => {
494
+ return {
495
+ name: p.name,
496
+ in: p.in,
497
+ argName: p.argName,
498
+ ...(p.pinnedValue === void 0 ? {} : {
499
+ value: p.pinnedValue
500
+ })
501
+ };
502
+ });
503
+ };
504
+
505
+ //#endregion
506
+ //#region src/toolDefinitions.ts
257
507
  /** Converts a camelCase `operationId` to a kebab-case tool name. */
258
508
  var operationIdToToolName = operationId => {
259
509
  return operationId.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "");
@@ -318,23 +568,29 @@ var buildTypedProperty = param => {
318
568
  };
319
569
  };
320
570
  var buildInputSchema = (pathParams, queryParams, bodyProps) => {
321
- const allParams = [...pathParams, ...queryParams, ...bodyProps];
571
+ const modelPathParams = pathParams.filter(p => {
572
+ return !p.serverManaged;
573
+ });
574
+ const modelQueryParams = queryParams.filter(p => {
575
+ return !p.serverManaged;
576
+ });
577
+ const allParams = [...modelPathParams, ...modelQueryParams, ...bodyProps];
322
578
  if (allParams.length === 0) return {
323
579
  type: "object"
324
580
  };
325
- const requiredFields = [...pathParams.map(p => {
326
- return p.camelName;
327
- }), ...queryParams.filter(p => {
581
+ const requiredFields = [...modelPathParams.map(p => {
582
+ return p.argName;
583
+ }), ...modelQueryParams.filter(p => {
328
584
  return p.required;
329
585
  }).map(p => {
330
- return p.camelName;
586
+ return p.argName;
331
587
  }), ...bodyProps.filter(p => {
332
588
  return p.required;
333
589
  }).map(p => {
334
- return p.camelName;
590
+ return p.argName;
335
591
  })];
336
592
  const properties = {};
337
- for (const param of allParams) properties[param.camelName] = "description" in param ? buildTypedProperty(param) : {
593
+ for (const param of allParams) properties[param.argName] = "description" in param ? normalizeNullable(buildTypedProperty(param)) : {
338
594
  type: "string",
339
595
  description: ""
340
596
  };
@@ -346,48 +602,9 @@ var buildInputSchema = (pathParams, queryParams, bodyProps) => {
346
602
  } : {})
347
603
  };
348
604
  };
349
- /**
350
- * Deduplicates parameter entries by `name`, keeping the last occurrence. When
351
- * path-item-level and operation-level parameters are concatenated (operation
352
- * last), this makes the operation-level entry win — as the OpenAPI spec requires.
353
- */
354
- var dedupeByName = items => {
355
- const byName = /* @__PURE__ */new Map();
356
- for (const item of items) byName.set(item.name, item);
357
- return [...byName.values()];
358
- };
359
- var extractPathParams = args => {
360
- return dedupeByName((args.parameters || []).map(p => {
361
- return resolveParameter(p, args.spec);
362
- }).filter(p => {
363
- return p.in === "path";
364
- }).map(p => {
365
- return {
366
- name: p.name || "",
367
- camelName: snakeToCamel(p.name || "")
368
- };
369
- }));
370
- };
371
- var extractQueryParams = args => {
372
- return dedupeByName((args.parameters || []).map(p => {
373
- return resolveParameter(p, args.spec);
374
- }).filter(p => {
375
- return p.in === "query";
376
- }).map(p => {
377
- return {
378
- name: p.name || "",
379
- camelName: snakeToCamel(p.name || ""),
380
- description: p.description || "",
381
- required: p.required || false,
382
- type: p.schema?.type || "string",
383
- style: p.style,
384
- explode: p.explode
385
- };
386
- }));
387
- };
388
605
  var resolveBodySchema = args => {
389
606
  const rawBodySchema = args.requestBody?.content?.["application/json"]?.schema;
390
- return resolveSchema(dereferenceSchema(rawBodySchema, args.spec), args.spec);
607
+ return resolveSchema(dereferenceSchema(rawBodySchema, args.spec, args.documents), args.spec, args.documents);
391
608
  };
392
609
  /**
393
610
  * snake_case names of every top-level property an operation's request schema
@@ -421,15 +638,19 @@ var flattenSingleAllOf = schema => {
421
638
  };
422
639
  };
423
640
  var extractBodyProps = args => {
641
+ const toArgName = args.toArgName ?? snakeToCamel;
424
642
  const bodySchema = resolveBodySchema(args);
425
643
  if (!bodySchema?.properties) return [];
426
644
  return Object.entries(bodySchema.properties).filter(([, value]) => {
427
- return !value[args.serverManagedExtension];
645
+ return !readServerManaged({
646
+ node: value,
647
+ extension: args.serverManagedExtension
648
+ }).managed;
428
649
  }).map(([key, value]) => {
429
650
  const val = flattenSingleAllOf(value);
430
651
  return {
431
652
  snakeName: key,
432
- camelName: snakeToCamel(key),
653
+ argName: toArgName(key),
433
654
  description: typeof val.description === "string" ? val.description : "",
434
655
  required: (bodySchema.required || []).includes(key),
435
656
  type: typeof val.type === "string" ? val.type : void 0,
@@ -454,23 +675,42 @@ var processOperation = args => {
454
675
  if (args.operation[args.options.excludeExtension]) return null;
455
676
  const toolName = operationIdToToolName(args.operation.operationId);
456
677
  const parameters = [...(args.pathItemParameters ?? []), ...(args.operation.parameters ?? [])];
678
+ const {
679
+ documents,
680
+ serverManagedExtension
681
+ } = args.options;
682
+ const toArgName = argNameMapper(args.options.argumentNames);
457
683
  const pathParams = extractPathParams({
458
684
  parameters,
459
- spec: args.spec
685
+ spec: args.spec,
686
+ documents,
687
+ toArgName,
688
+ serverManagedExtension
460
689
  });
461
690
  const queryParams = extractQueryParams({
462
691
  parameters,
463
- spec: args.spec
692
+ spec: args.spec,
693
+ documents,
694
+ toArgName,
695
+ serverManagedExtension
464
696
  });
465
697
  const bodyProps = extractBodyProps({
466
698
  requestBody: args.operation.requestBody,
467
699
  spec: args.spec,
468
- serverManagedExtension: args.options.serverManagedExtension
700
+ serverManagedExtension,
701
+ documents,
702
+ toArgName
469
703
  });
470
704
  const inputSchema = buildInputSchema(pathParams, queryParams, bodyProps);
705
+ const serverManagedParameters = collectServerManagedParameters({
706
+ pathParams,
707
+ queryParams
708
+ });
709
+ const pinned = pinnedArgs(serverManagedParameters);
471
710
  const acceptedBodyFields = extractAcceptedBodyFields({
472
711
  requestBody: args.operation.requestBody,
473
- spec: args.spec
712
+ spec: args.spec,
713
+ documents
474
714
  });
475
715
  return {
476
716
  name: toolName,
@@ -479,11 +719,18 @@ var processOperation = args => {
479
719
  method: httpMethod,
480
720
  pathTemplate: args.pathTemplate,
481
721
  operationId: args.operation.operationId,
482
- path: buildPathFn(args.pathTemplate, pathParams),
483
- query: buildQueryFn(queryParams),
722
+ path: withPinned({
723
+ build: buildPathFn(args.pathTemplate, pathParams),
724
+ pinned
725
+ }),
726
+ query: withPinned({
727
+ build: buildQueryFn(queryParams),
728
+ pinned
729
+ }),
484
730
  body: buildBodyFn(bodyProps),
485
731
  acceptedBodyFields,
486
- extensions: extractExtensions(args.operation)
732
+ extensions: extractExtensions(args.operation),
733
+ serverManagedParameters
487
734
  };
488
735
  };
489
736
  var processPath = args => {
@@ -504,6 +751,15 @@ var processPath = args => {
504
751
  }
505
752
  return tools;
506
753
  };
754
+ /** Applies the defaults to {@link OpenApiToToolsOptions}. */
755
+ var resolveOptions = (options = {}) => {
756
+ return {
757
+ excludeExtension: options.excludeExtension ?? "x-mcp-exclude",
758
+ serverManagedExtension: options.serverManagedExtension ?? "x-mcp-server-managed",
759
+ argumentNames: options.argumentNames ?? "camelCase",
760
+ documents: options.documents
761
+ };
762
+ };
507
763
  /**
508
764
  * Translates one or more OpenAPI documents into REST-backed MCP tool
509
765
  * definitions. Each translatable operation (has an `operationId`, a supported
@@ -517,10 +773,7 @@ var processPath = args => {
517
773
  * ```
518
774
  */
519
775
  var openApiToToolDefinitions = args => {
520
- const options = {
521
- excludeExtension: args.options?.excludeExtension ?? "x-mcp-exclude",
522
- serverManagedExtension: args.options?.serverManagedExtension ?? "x-mcp-server-managed"
523
- };
776
+ const options = resolveOptions(args.options);
524
777
  const specs = Array.isArray(args.spec) ? args.spec : [args.spec];
525
778
  const tools = [];
526
779
  for (const spec of specs) {
@@ -537,13 +790,34 @@ var openApiToToolDefinitions = args => {
537
790
 
538
791
  //#endregion
539
792
  //#region src/registerOpenApiTools.ts
793
+ /** The text the default `toText` answers when the API returned no body. */
794
+ var NO_CONTENT_TEXT = "Succeeded. The operation returned no content.";
540
795
  var defaultToText = data => {
796
+ if (data === void 0 || data === "") return NO_CONTENT_TEXT;
541
797
  return typeof data === "string" ? data : JSON.stringify(data, null, 2);
542
798
  };
543
799
  /**
800
+ * Replaces whatever the model sent for server-managed parameters with the
801
+ * values `serverParameters` supplies, so the model can never set them.
802
+ */
803
+ var applyServerParameters = async args => {
804
+ const managed = args.tool.serverManagedParameters;
805
+ if (managed.length === 0) return args.handlerArgs;
806
+ const result = {
807
+ ...args.handlerArgs
808
+ };
809
+ for (const param of managed) delete result[param.argName];
810
+ const values = args.serverParameters ? await args.serverParameters({
811
+ tool: args.tool,
812
+ headers: args.headers
813
+ }) : {};
814
+ for (const param of managed) if (values[param.name] !== void 0) result[param.argName] = values[param.name];
815
+ return result;
816
+ };
817
+ /**
544
818
  * Derives MCP tools from OpenAPI document(s) and registers each on the given
545
- * MCP server. Every tool's handler resolves the incoming camelCase args into a
546
- * concrete HTTP request and delegates execution to `callApi`.
819
+ * MCP server. Every tool's handler resolves the incoming args into a concrete
820
+ * HTTP request and delegates execution to `callApi`.
547
821
  *
548
822
  * @returns The list of {@link ToolDefinition} that were registered.
549
823
  *
@@ -557,10 +831,10 @@ var defaultToText = data => {
557
831
  * registerOpenApiTools({
558
832
  * server,
559
833
  * spec: myOpenApiDocument,
560
- * callApi: async ({ method, url, body }) => {
834
+ * callApi: async ({ method, url, body, headers }) => {
561
835
  * const res = await fetch(`https://api.example.com${url}`, {
562
836
  * method,
563
- * headers: { 'Content-Type': 'application/json' },
837
+ * headers: { ...headers, 'Content-Type': 'application/json' },
564
838
  * body: body ? JSON.stringify(body) : undefined,
565
839
  * });
566
840
  * return res.json();
@@ -578,7 +852,14 @@ var registerOpenApiTools = args => {
578
852
  name: tool.name,
579
853
  description: tool.description,
580
854
  inputSchema: tool.inputSchema,
581
- handler: async handlerArgs => {
855
+ handler: async rawArgs => {
856
+ const headers = getApiHeaders();
857
+ const handlerArgs = await applyServerParameters({
858
+ tool,
859
+ handlerArgs: rawArgs,
860
+ headers,
861
+ serverParameters: args.serverParameters
862
+ });
582
863
  const url = tool.path(handlerArgs) + (tool.query ? tool.query(handlerArgs) : "");
583
864
  return {
584
865
  content: [{
@@ -587,7 +868,8 @@ var registerOpenApiTools = args => {
587
868
  method: tool.method,
588
869
  url,
589
870
  body: tool.body ? tool.body(handlerArgs) : void 0,
590
- tool
871
+ tool,
872
+ headers
591
873
  }))
592
874
  }]
593
875
  };
@@ -597,4 +879,4 @@ var registerOpenApiTools = args => {
597
879
  };
598
880
 
599
881
  //#endregion
600
- export { DEFAULT_EXCLUDE_EXTENSION, DEFAULT_SERVER_MANAGED_EXTENSION, buildBodyFn, buildInputSchema, buildPathFn, buildQueryFn, dereferenceSchema, extractAcceptedBodyFields, extractBodyProps, extractPathParams, extractQueryParams, getJsonSchemaType, openApiToToolDefinitions, operationIdToToolName, processOperation, processPath, registerOpenApiTools, resolveParameter, resolveSchema, snakeToCamel };
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 };