fastmcp 4.24.3 → 4.24.5

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.
@@ -2,628 +2,727 @@ import {
2
2
  FastMCP,
3
3
  UserError,
4
4
  jsonSchemaAdapter
5
- } from "../chunk-D2CVYAA5.js";
5
+ } from "../chunk-UYV7MFKA.js";
6
6
  import "../chunk-CAQLTSJ5.js";
7
7
  import "../chunk-OKKFL2M4.js";
8
8
 
9
9
  // src/openapi/loadSpec.ts
10
10
  import SwaggerParser from "@apidevtools/swagger-parser";
11
- async function loadSpec(spec) {
12
- const document = await SwaggerParser.bundle(
13
- spec
11
+
12
+ // src/openapi/schemas.ts
13
+ var SCHEMA_MAP_KEYS = /* @__PURE__ */ new Set([
14
+ "$defs",
15
+ "definitions",
16
+ "dependentSchemas",
17
+ "patternProperties",
18
+ "properties"
19
+ ]);
20
+ var DATA_KEYS = /* @__PURE__ */ new Set([
21
+ "const",
22
+ "default",
23
+ "enum",
24
+ "example",
25
+ "examples"
26
+ ]);
27
+ var STRIP_KEYS = /* @__PURE__ */ new Set(["example", "examples"]);
28
+ function buildFlatSchema(route, sharedDefs) {
29
+ const byName = /* @__PURE__ */ new Map();
30
+ for (const param of route.parameters) {
31
+ const list = byName.get(param.name) ?? [];
32
+ list.push(param);
33
+ byName.set(param.name, list);
34
+ }
35
+ const {
36
+ bodyEncoding,
37
+ properties: bodyProperties,
38
+ unsupportedBodyContentType,
39
+ wholeBodyKey
40
+ } = extractBodyProperties(
41
+ route.method === "get" ? void 0 : route.requestBody,
42
+ sharedDefs
14
43
  );
15
- if (!document.openapi?.startsWith("3.")) {
16
- throw new Error(
17
- `fromOpenAPI only supports OpenAPI 3.x documents (found ${document.openapi ?? document.swagger ?? "an unrecognized version"}). Swagger 2.0 is not supported.`
18
- );
44
+ const properties = /* @__PURE__ */ Object.create(null);
45
+ const required = [];
46
+ const parameterMap = /* @__PURE__ */ Object.create(null);
47
+ const used = new Set(bodyProperties.keys());
48
+ for (const [name, occurrences] of byName) {
49
+ if (occurrences.length === 1 && !bodyProperties.has(name)) {
50
+ used.add(name);
51
+ }
52
+ }
53
+ const allocateAlias = (base) => {
54
+ let key = base;
55
+ let suffix = 1;
56
+ while (used.has(key)) {
57
+ suffix += 1;
58
+ key = `${base}_${suffix}`;
59
+ }
60
+ used.add(key);
61
+ return key;
62
+ };
63
+ for (const [name, occurrences] of byName) {
64
+ const collides = name === "__proto__" || occurrences.length > 1 || bodyProperties.has(name);
65
+ for (const param of occurrences) {
66
+ const base = collides ? `${name}__${param.in}` : name;
67
+ const key = collides ? allocateAlias(base) : name;
68
+ properties[key] = rewriteComponentRefs(
69
+ param.schema ?? { type: "string" }
70
+ );
71
+ parameterMap[key] = {
72
+ allowReserved: param.allowReserved,
73
+ explode: param.explode,
74
+ in: param.in,
75
+ name,
76
+ style: param.style
77
+ };
78
+ if (param.in === "path" || param.required) {
79
+ required.push(key);
80
+ }
81
+ }
82
+ }
83
+ for (const [name, { required: isRequired, schema }] of bodyProperties) {
84
+ const key = name === "__proto__" ? allocateAlias(`${name}__body`) : name;
85
+ properties[key] = rewriteComponentRefs(schema);
86
+ parameterMap[key] = { in: "body", name };
87
+ if (isRequired) {
88
+ required.push(key);
89
+ }
90
+ }
91
+ const flatSchema = {
92
+ additionalProperties: false,
93
+ properties,
94
+ type: "object",
95
+ ...required.length > 0 ? { required } : {}
96
+ };
97
+ const usedDefs = sharedDefs && filterReferencedDefs(properties, sharedDefs);
98
+ if (usedDefs) {
99
+ flatSchema.$defs = usedDefs;
19
100
  }
20
101
  return {
21
- document,
22
- origin: typeof spec === "string" && isHttpUrl(spec) ? spec : void 0
102
+ bodyEncoding,
103
+ flatSchema,
104
+ parameterMap,
105
+ unsupportedBodyContentType,
106
+ wholeBodyKey
23
107
  };
24
108
  }
25
- function isHttpUrl(value) {
26
- return /^https?:\/\//i.test(value);
109
+ function buildSharedDefs(document) {
110
+ const schemas = document.components?.schemas;
111
+ if (!schemas || Object.keys(schemas).length === 0) {
112
+ return void 0;
113
+ }
114
+ return rewriteNode(schemas, "schemaMap");
27
115
  }
28
-
29
- // src/openapi/naming.ts
30
- var MAX_NAME_LENGTH = 56;
31
- var MAX_BASE_LENGTH = MAX_NAME_LENGTH - 5;
32
- function generateNames(routes, mcpNames) {
33
- const names = /* @__PURE__ */ new Map();
34
- const used = /* @__PURE__ */ new Set();
35
- for (const route of routes) {
36
- const base = slugify(baseNameFor(route, mcpNames));
37
- let candidate = base;
38
- let suffix = 1;
39
- while (used.has(candidate)) {
40
- suffix += 1;
41
- candidate = `${base}_${suffix}`;
42
- }
43
- used.add(candidate);
44
- names.set(route, candidate);
116
+ var SUCCESS_STATUS_PATTERN = /^2\d\d$/;
117
+ var MAX_OUTPUT_SCHEMA_DEFS = 50;
118
+ function buildOutputSchema(route, sharedDefs) {
119
+ const successEntry = Object.entries(route.responses ?? {}).find(
120
+ ([code]) => SUCCESS_STATUS_PATTERN.test(code)
121
+ );
122
+ const declaredSchema = findMediaType(
123
+ successEntry?.[1].content,
124
+ "application/json"
125
+ )?.schema;
126
+ if (!declaredSchema) {
127
+ return void 0;
45
128
  }
46
- return names;
129
+ const schema = resolveComponentRef(declaredSchema, sharedDefs);
130
+ const rewritten = rewriteComponentRefs(schema, true);
131
+ const { type } = rewritten;
132
+ const isObjectShaped = type === "object" || Array.isArray(type) && type.includes("object");
133
+ if (!isObjectShaped) {
134
+ return void 0;
135
+ }
136
+ const normalized = { ...rewritten, type: "object" };
137
+ const usedDefs = sharedDefs && filterReferencedDefs(normalized, sharedDefs);
138
+ if (usedDefs && Object.keys(usedDefs).length > MAX_OUTPUT_SCHEMA_DEFS) {
139
+ return void 0;
140
+ }
141
+ return {
142
+ ...normalized,
143
+ ...usedDefs ? {
144
+ $defs: rewriteNode(usedDefs, "schemaMap", true)
145
+ } : {}
146
+ };
47
147
  }
48
- function baseNameFor(route, mcpNames) {
49
- if (route.operationId) {
50
- const override = mcpNames && Object.hasOwn(mcpNames, route.operationId) ? mcpNames[route.operationId] : void 0;
51
- return override ?? route.operationId.split("__")[0];
148
+ function resolveComponentRef(schema, sharedDefs) {
149
+ const seen = /* @__PURE__ */ new Set();
150
+ let current = schema;
151
+ let ref = current.$ref;
152
+ while (typeof ref === "string") {
153
+ const name = componentSchemaName(ref);
154
+ const target = name === void 0 ? void 0 : sharedDefs?.[name];
155
+ if (name === void 0 || target === void 0 || seen.has(name)) {
156
+ break;
157
+ }
158
+ seen.add(name);
159
+ current = target;
160
+ ref = current.$ref;
52
161
  }
53
- return route.summary || `${route.method}_${route.path}`;
162
+ return current;
54
163
  }
55
- function slugify(value) {
56
- const slug = value.replace(/[^a-zA-Z0-9_]+/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "").slice(0, MAX_BASE_LENGTH);
57
- return slug || "operation";
164
+ function rewriteComponentRefs(value, stripExamples = false) {
165
+ return rewriteNode(value, "schema", stripExamples);
58
166
  }
59
-
60
- // src/openapi/requestBuilder.ts
61
- async function executeRequest(options) {
62
- const baseUrl = resolveBaseUrl(
63
- options.servers,
64
- options.origin,
65
- options.baseUrlOverride
66
- );
67
- const pathParams = /* @__PURE__ */ Object.create(null);
68
- const queryParts = [];
69
- const headers = new Headers(await resolveHeaders(options.headers));
70
- const bodyProps = /* @__PURE__ */ Object.create(null);
71
- for (const [key, value] of Object.entries(options.args)) {
72
- const mapping = options.parameterMap[key];
73
- if (!mapping || value === void 0) {
74
- continue;
167
+ function childMode(key) {
168
+ if (DATA_KEYS.has(key)) {
169
+ return "data";
170
+ }
171
+ return SCHEMA_MAP_KEYS.has(key) ? "schemaMap" : "schema";
172
+ }
173
+ function componentSchemaName(ref) {
174
+ for (const prefix of ["#/components/schemas/", "#/$defs/"]) {
175
+ if (ref.startsWith(prefix)) {
176
+ return ref.slice(prefix.length).replaceAll("~1", "/").replaceAll("~0", "~");
75
177
  }
76
- switch (mapping.in) {
77
- case "body":
78
- bodyProps[mapping.name] = value;
79
- break;
80
- case "cookie": {
81
- const pair = `${mapping.name}=${encodeCookieValue(mapping, value)}`;
82
- const existing = headers.get("cookie");
83
- headers.set("cookie", existing ? `${existing}; ${pair}` : pair);
84
- break;
85
- }
86
- case "header":
87
- headers.set(mapping.name, String(value));
88
- break;
89
- case "path":
90
- pathParams[mapping.name] = encodePathValue(
91
- value,
92
- mapping.style,
93
- mapping.explode
94
- );
95
- break;
96
- case "query": {
97
- const query = new URLSearchParams();
98
- appendQueryValue(
99
- query,
100
- mapping.name,
101
- mapping.style,
102
- value,
103
- mapping.explode
104
- );
105
- if (query.size > 0) {
106
- queryParts.push(serializeQuery(query, mapping.allowReserved));
107
- }
108
- break;
109
- }
178
+ }
179
+ return void 0;
180
+ }
181
+ function extractBodyProperties(requestBody, sharedDefs) {
182
+ const properties = /* @__PURE__ */ new Map();
183
+ const content = requestBody?.content;
184
+ if (!content) {
185
+ return { properties };
186
+ }
187
+ const json = findMediaType(content, "application/json");
188
+ const form = findMediaType(content, "application/x-www-form-urlencoded");
189
+ if (json === void 0 && form === void 0) {
190
+ const contentTypes = Object.keys(content);
191
+ return contentTypes.length > 0 ? { properties, unsupportedBodyContentType: contentTypes[0] } : { properties };
192
+ }
193
+ const bodyEncoding = json === void 0 ? "form" : "json";
194
+ const declaredSchema = bodyEncoding === "json" ? json?.schema : form?.schema;
195
+ if (!declaredSchema) {
196
+ return { bodyEncoding, properties };
197
+ }
198
+ const schema = bodyEncoding === "form" ? resolveComponentRef(declaredSchema, sharedDefs) : declaredSchema;
199
+ const schemaProperties = schema.properties;
200
+ if (schema.type === "object" && schemaProperties && (bodyEncoding === "form" || schema.additionalProperties === false || Object.keys(schemaProperties).length > 0)) {
201
+ const requiredNames = new Set(
202
+ schema.required ?? []
203
+ );
204
+ for (const [name, propertySchema] of Object.entries(schemaProperties)) {
205
+ properties.set(name, {
206
+ required: requiredNames.has(name),
207
+ schema: propertySchema
208
+ });
110
209
  }
210
+ return { bodyEncoding, properties };
111
211
  }
112
- let path = options.route.path;
113
- for (const [name, value] of Object.entries(pathParams)) {
114
- path = path.replaceAll(`{${name}}`, value);
212
+ if (bodyEncoding === "form") {
213
+ return {
214
+ properties,
215
+ unsupportedBodyContentType: "application/x-www-form-urlencoded"
216
+ };
115
217
  }
116
- assertNoSubstitutedDotSegments(options.route, path);
117
- const url = new URL(baseUrl.replace(/\/$/, "") + path);
118
- url.search = queryParts.join("&");
119
- let body;
120
- const requiredJsonBody = options.bodyEncoding === "json" && options.route.requestBody?.required === true && !options.wholeBodyKey;
121
- if (Object.keys(bodyProps).length > 0 || requiredJsonBody) {
122
- const payload = options.wholeBodyKey ? bodyProps[options.wholeBodyKey] : bodyProps;
123
- if (options.bodyEncoding === "form") {
124
- if (!headers.has("content-type")) {
125
- headers.set("content-type", "application/x-www-form-urlencoded");
126
- }
127
- body = encodeFormBody(payload);
128
- } else {
129
- if (!headers.has("content-type")) {
130
- headers.set("content-type", "application/json");
218
+ properties.set("body", {
219
+ required: requestBody?.required ?? false,
220
+ schema
221
+ });
222
+ return { bodyEncoding, properties, wholeBodyKey: "body" };
223
+ }
224
+ function filterReferencedDefs(node, allDefs) {
225
+ const referenced = /* @__PURE__ */ new Set();
226
+ const stack = [node];
227
+ while (stack.length > 0) {
228
+ const current = stack.pop();
229
+ if (Array.isArray(current)) {
230
+ stack.push(...current);
231
+ continue;
232
+ }
233
+ if (!current || typeof current !== "object") {
234
+ continue;
235
+ }
236
+ for (const [key, value] of Object.entries(
237
+ current
238
+ )) {
239
+ if (key === "$ref" && typeof value === "string" && value.startsWith("#/$defs/")) {
240
+ const [first = ""] = value.slice("#/$defs/".length).split("/");
241
+ const name = first.replaceAll("~1", "/").replaceAll("~0", "~");
242
+ if (allDefs[name] && !referenced.has(name)) {
243
+ referenced.add(name);
244
+ stack.push(allDefs[name]);
245
+ }
246
+ continue;
131
247
  }
132
- body = JSON.stringify(payload);
248
+ stack.push(value);
133
249
  }
134
250
  }
135
- const response = await options.fetchImpl(url.toString(), {
136
- body,
137
- headers,
138
- method: options.route.method.toUpperCase()
139
- });
140
- const text = await response.text();
141
- if (!response.ok) {
142
- throw new UserError(
143
- `${options.route.method.toUpperCase()} ${path} failed with ${response.status}: ${text.slice(0, 2e3)}`
144
- );
251
+ if (referenced.size === 0) {
252
+ return void 0;
145
253
  }
146
- const mediaType = response.headers.get("content-type")?.split(";")[0];
147
- if (mediaType?.toLowerCase().includes("json")) {
148
- try {
149
- const json = JSON.parse(text);
150
- return { json, text: JSON.stringify(json, null, 2) };
151
- } catch {
152
- return { text };
153
- }
254
+ return Object.fromEntries(
255
+ [...referenced].map((name) => [name, allDefs[name]])
256
+ );
257
+ }
258
+ function findMediaType(content, mediaType) {
259
+ if (!content) {
260
+ return void 0;
154
261
  }
155
- return { text };
262
+ const key = Object.hasOwn(content, mediaType) ? mediaType : Object.keys(content).find(
263
+ (candidate) => candidate.split(";")[0].trim().toLowerCase() === mediaType
264
+ );
265
+ return key === void 0 ? void 0 : content[key];
156
266
  }
157
- function resolveBaseUrl(servers, origin, overrideUrl) {
158
- if (overrideUrl) {
159
- return overrideUrl.replace(/\/$/, "");
267
+ function normalizeExclusiveBound(schema, exclusiveKey, boundKey) {
268
+ const { [exclusiveKey]: exclusive, ...rest } = schema;
269
+ if (typeof exclusive !== "boolean") {
270
+ return schema;
160
271
  }
161
- const server = servers?.[0];
162
- if (!server) {
163
- throw new Error(
164
- "The OpenAPI document has no `servers` entry. Pass `baseUrl` to fromOpenAPI() explicitly."
165
- );
272
+ if (!exclusive || typeof rest[boundKey] !== "number") {
273
+ return rest;
166
274
  }
167
- let url = server.url;
168
- for (const [name, variable] of Object.entries(server.variables ?? {})) {
169
- url = url.replaceAll(`{${name}}`, () => variable.default);
275
+ const { [boundKey]: bound, ...unbounded } = rest;
276
+ return { ...unbounded, [exclusiveKey]: bound };
277
+ }
278
+ function normalizeExclusiveBounds(schema) {
279
+ return normalizeExclusiveBound(
280
+ normalizeExclusiveBound(schema, "exclusiveMinimum", "minimum"),
281
+ "exclusiveMaximum",
282
+ "maximum"
283
+ );
284
+ }
285
+ function normalizeNullable(schema) {
286
+ if (!("nullable" in schema)) {
287
+ return schema;
170
288
  }
171
- try {
172
- return new URL(url).toString().replace(/\/$/, "");
173
- } catch {
174
- if (!origin) {
175
- throw new Error(
176
- `The OpenAPI document's servers[0].url ("${url}") is relative, and the spec was not loaded from an http(s) URL, so it cannot be resolved to an absolute address. Pass \`baseUrl\` to fromOpenAPI() explicitly.`
177
- );
178
- }
179
- return new URL(url, origin).toString().replace(/\/$/, "");
289
+ const { nullable, ...rest } = schema;
290
+ if (nullable !== true) {
291
+ return rest;
180
292
  }
181
- }
182
- function appendBracketPairs(params, key, value) {
183
- if (Array.isArray(value)) {
184
- for (const item of value) {
185
- if (item !== null && typeof item === "object") {
186
- appendBracketPairs(params, `${key}[]`, item);
187
- } else {
188
- params.append(key, String(item));
189
- }
190
- }
191
- return;
293
+ const { type } = rest;
294
+ if (typeof type === "string") {
295
+ return { ...rest, type: [type, "null"] };
192
296
  }
193
- if (value !== null && typeof value === "object") {
194
- for (const [subKey, subValue] of Object.entries(
195
- value
196
- )) {
197
- if (subValue !== void 0) {
198
- appendBracketPairs(params, `${key}[${subKey}]`, subValue);
199
- }
200
- }
201
- return;
297
+ if (Array.isArray(type)) {
298
+ return { ...rest, type: [.../* @__PURE__ */ new Set(["null", ...type])] };
202
299
  }
203
- params.append(key, String(value));
300
+ return rest;
204
301
  }
205
- function appendQueryValue(query, name, style, value, explode) {
206
- if (style === "deepObject") {
207
- appendBracketPairs(query, name, value);
208
- return;
209
- }
210
- if (style === "spaceDelimited" || style === "pipeDelimited") {
211
- const items = Array.isArray(value) ? value : [value];
212
- const separator = style === "spaceDelimited" ? " " : "|";
213
- query.append(name, items.map(String).join(separator));
214
- return;
302
+ function rewriteNode(value, mode, stripExamples = false) {
303
+ if (mode === "data") {
304
+ return value;
215
305
  }
216
- if ((style === void 0 || style === "form") && value !== null && typeof value === "object" && !Array.isArray(value)) {
217
- const entries = Object.entries(value);
218
- if (explode === false) {
219
- if (entries.length > 0) {
220
- query.append(
221
- name,
222
- entries.flatMap(([key, item]) => [key, String(item)]).join(",")
223
- );
224
- }
225
- } else {
226
- for (const [key, item] of entries) {
227
- query.append(key, String(item));
228
- }
229
- }
230
- return;
306
+ if (Array.isArray(value)) {
307
+ return value.map((item) => rewriteNode(item, "schema", stripExamples));
231
308
  }
232
- if ((style === void 0 || style === "form") && explode === false && Array.isArray(value) && value.length > 0) {
233
- query.append(name, value.map(String).join(","));
234
- return;
309
+ if (!value || typeof value !== "object") {
310
+ return value;
235
311
  }
236
- for (const item of Array.isArray(value) ? value : [value]) {
237
- query.append(name, String(item));
312
+ const entries = Object.entries(value).filter(
313
+ ([key]) => !stripExamples || mode !== "schema" || !STRIP_KEYS.has(key)
314
+ ).map(([key, entryValue]) => {
315
+ if (mode === "schemaMap") {
316
+ return [key, rewriteNode(entryValue, "schema", stripExamples)];
317
+ }
318
+ if (key === "$ref" && typeof entryValue === "string" && entryValue.startsWith("#/components/schemas/")) {
319
+ return [key, entryValue.replace("#/components/schemas/", "#/$defs/")];
320
+ }
321
+ return [key, rewriteNode(entryValue, childMode(key), stripExamples)];
322
+ });
323
+ const rewritten = Object.fromEntries(entries);
324
+ return mode === "schemaMap" ? rewritten : normalizeExclusiveBounds(normalizeNullable(rewritten));
325
+ }
326
+
327
+ // src/openapi/loadSpec.ts
328
+ async function loadSpec(spec) {
329
+ const document = await SwaggerParser.bundle(
330
+ spec
331
+ );
332
+ if (!document.openapi?.startsWith("3.")) {
333
+ throw new Error(
334
+ `fromOpenAPI only supports OpenAPI 3.x documents (found ${document.openapi ?? document.swagger ?? "an unrecognized version"}). Swagger 2.0 is not supported.`
335
+ );
238
336
  }
337
+ resolveBundledPointerRefs(document);
338
+ return {
339
+ document,
340
+ origin: typeof spec === "string" && isHttpUrl(spec) ? spec : void 0
341
+ };
239
342
  }
240
- function assertNoSubstitutedDotSegments(route, resolvedPath) {
241
- const templateSegments = new Set(route.path.split("/"));
242
- for (const segment of resolvedPath.split("/")) {
243
- if ((segment === "." || segment === "..") && !templateSegments.has(segment)) {
244
- throw new UserError(
245
- `${route.method.toUpperCase()} ${route.path}: path parameter values must not produce a "${segment}" path segment.`
343
+ function isHttpUrl(value) {
344
+ return /^https?:\/\//i.test(value);
345
+ }
346
+ function resolveBundledPointerRefs(document) {
347
+ const hoisted = /* @__PURE__ */ new Map();
348
+ let counter = 0;
349
+ const resolvePointer = (pointer) => {
350
+ let segments;
351
+ try {
352
+ segments = pointer.slice(2).split("/").map(
353
+ (segment) => decodeURIComponent(
354
+ segment.replaceAll("~1", "/").replaceAll("~0", "~")
355
+ )
246
356
  );
357
+ } catch {
358
+ return void 0;
247
359
  }
248
- }
249
- }
250
- function encodeCookieValue(mapping, value) {
251
- return (Array.isArray(value) ? value : [value]).map((item) => {
252
- const text = String(item);
253
- if (mapping.style !== "cookie") {
254
- return encodePathComponent(text);
360
+ let node = document;
361
+ for (const segment of segments) {
362
+ if (!node || typeof node !== "object" || !Object.hasOwn(node, segment)) {
363
+ return void 0;
364
+ }
365
+ node = node[segment];
255
366
  }
256
- if (/[;,]/.test(text)) {
257
- throw new UserError(
258
- `Cookie parameter "${mapping.name}" uses style "cookie" and its value contains ";" or ","`
259
- );
367
+ return node;
368
+ };
369
+ const hoist = (ref) => {
370
+ const existing = hoisted.get(ref);
371
+ if (existing !== void 0) {
372
+ return existing;
260
373
  }
261
- return text;
262
- }).join(",");
263
- }
264
- function encodeFormBody(payload) {
265
- const params = new URLSearchParams();
266
- if (payload && typeof payload === "object" && !Array.isArray(payload)) {
267
- for (const [key, value] of Object.entries(
268
- payload
269
- )) {
270
- if (value === void 0) {
271
- continue;
374
+ const target = resolvePointer(ref);
375
+ if (!target || typeof target !== "object") {
376
+ return void 0;
377
+ }
378
+ const schemas = (document.components ??= {}).schemas ??= {};
379
+ let name;
380
+ do {
381
+ name = `__bundle_${counter++}`;
382
+ } while (Object.hasOwn(schemas, name));
383
+ hoisted.set(ref, name);
384
+ schemas[name] = target;
385
+ walk(target, "schema");
386
+ return name;
387
+ };
388
+ const rewriteRef = (record) => {
389
+ const ref = record.$ref;
390
+ if (typeof ref !== "string" || !ref.startsWith("#/") || ref.startsWith("#/components/schemas/") || ref.startsWith("#/$defs/")) {
391
+ return;
392
+ }
393
+ const name = hoist(ref);
394
+ if (name !== void 0) {
395
+ record.$ref = `#/components/schemas/${name}`;
396
+ }
397
+ };
398
+ const childMode2 = (key, mode) => {
399
+ if (mode === "schemaMap") {
400
+ return "schema";
401
+ }
402
+ if (mode === "schema") {
403
+ return SCHEMA_MAP_KEYS.has(key) ? "schemaMap" : "schema";
404
+ }
405
+ if (key === "schema") {
406
+ return "schema";
407
+ }
408
+ if (mode === "document" && key === "components") {
409
+ return "components";
410
+ }
411
+ return mode === "components" && key === "schemas" ? "schemaMap" : "document";
412
+ };
413
+ const walk = (value, mode) => {
414
+ if (Array.isArray(value)) {
415
+ for (const item of value) {
416
+ walk(item, mode);
272
417
  }
273
- appendBracketPairs(params, key, value);
418
+ return;
274
419
  }
275
- }
276
- return params.toString();
277
- }
278
- function encodePathComponent(value) {
279
- return encodeURIComponent(value).replace(
280
- /[!'()*]/g,
281
- (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`
282
- );
420
+ if (!value || typeof value !== "object") {
421
+ return;
422
+ }
423
+ const record = value;
424
+ for (const [key, entryValue] of Object.entries(record)) {
425
+ if (mode === "schema" && key === "$ref") {
426
+ rewriteRef(record);
427
+ } else if (mode !== "schema" || !DATA_KEYS.has(key)) {
428
+ walk(entryValue, childMode2(key, mode));
429
+ }
430
+ }
431
+ };
432
+ walk(document, "document");
283
433
  }
284
- function encodePathValue(value, style, explode) {
285
- if ((style === void 0 || style === "simple") && Array.isArray(value) && value.every((item) => item === null || typeof item !== "object")) {
286
- return value.map((item) => encodePathComponent(String(item))).join(",");
287
- }
288
- if ((style === void 0 || style === "simple") && value !== null && typeof value === "object" && !Array.isArray(value)) {
289
- const entries = Object.entries(value);
290
- if (entries.every(([, item]) => item === null || typeof item !== "object")) {
291
- return entries.map(([key, item]) => {
292
- const encodedKey = encodePathComponent(key);
293
- const encodedValue = encodePathComponent(String(item));
294
- return explode === true ? `${encodedKey}=${encodedValue}` : `${encodedKey},${encodedValue}`;
295
- }).join(",");
434
+
435
+ // src/openapi/naming.ts
436
+ var MAX_NAME_LENGTH = 56;
437
+ var MAX_BASE_LENGTH = MAX_NAME_LENGTH - 5;
438
+ function generateNames(routes, mcpNames) {
439
+ const names = /* @__PURE__ */ new Map();
440
+ const used = /* @__PURE__ */ new Set();
441
+ for (const route of routes) {
442
+ const base = slugify(baseNameFor(route, mcpNames));
443
+ let candidate = base;
444
+ let suffix = 1;
445
+ while (used.has(candidate)) {
446
+ suffix += 1;
447
+ candidate = `${base}_${suffix}`;
296
448
  }
449
+ used.add(candidate);
450
+ names.set(route, candidate);
297
451
  }
298
- return encodeURIComponent(String(value));
452
+ return names;
299
453
  }
300
- async function resolveHeaders(headers) {
301
- if (!headers) {
302
- return {};
454
+ function baseNameFor(route, mcpNames) {
455
+ if (route.operationId) {
456
+ const override = mcpNames && Object.hasOwn(mcpNames, route.operationId) ? mcpNames[route.operationId] : void 0;
457
+ return override ?? route.operationId.split("__")[0];
303
458
  }
304
- return typeof headers === "function" ? await headers() : { ...headers };
459
+ return route.summary || `${route.method}_${route.path}`;
305
460
  }
306
- function serializeQuery(query, allowReserved) {
307
- const encoded = query.toString();
308
- if (!allowReserved) {
309
- return encoded;
310
- }
311
- return encoded.split("&").map((pair) => {
312
- const [name, value] = pair.split("=");
313
- const expanded = value.replace(
314
- /\+|%25([\da-f]{2})|%(21|24|27|28|29|2c|2f|3a|3b|3f|40|7e)/gi,
315
- (match, triple) => triple ? `%${triple}` : match === "+" ? "%20" : decodeURIComponent(match)
316
- );
317
- return `${name}=${expanded}`;
318
- }).join("&");
461
+ function slugify(value) {
462
+ const slug = value.replace(/[^a-zA-Z0-9_]+/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "").slice(0, MAX_BASE_LENGTH);
463
+ return slug || "operation";
319
464
  }
320
465
 
321
- // src/openapi/schemas.ts
322
- var SCHEMA_MAP_KEYS = /* @__PURE__ */ new Set([
323
- "$defs",
324
- "definitions",
325
- "dependentSchemas",
326
- "patternProperties",
327
- "properties"
328
- ]);
329
- var DATA_KEYS = /* @__PURE__ */ new Set(["const", "default", "enum", "example", "examples"]);
330
- var STRIP_KEYS = /* @__PURE__ */ new Set(["example", "examples"]);
331
- function buildFlatSchema(route, sharedDefs) {
332
- const byName = /* @__PURE__ */ new Map();
333
- for (const param of route.parameters) {
334
- const list = byName.get(param.name) ?? [];
335
- list.push(param);
336
- byName.set(param.name, list);
337
- }
338
- const {
339
- bodyEncoding,
340
- properties: bodyProperties,
341
- unsupportedBodyContentType,
342
- wholeBodyKey
343
- } = extractBodyProperties(
344
- route.method === "get" ? void 0 : route.requestBody,
345
- sharedDefs
466
+ // src/openapi/requestBuilder.ts
467
+ async function executeRequest(options) {
468
+ const baseUrl = resolveBaseUrl(
469
+ options.servers,
470
+ options.origin,
471
+ options.baseUrlOverride
346
472
  );
347
- const properties = /* @__PURE__ */ Object.create(null);
348
- const required = [];
349
- const parameterMap = /* @__PURE__ */ Object.create(null);
350
- const used = new Set(bodyProperties.keys());
351
- for (const [name, occurrences] of byName) {
352
- if (occurrences.length === 1 && !bodyProperties.has(name)) {
353
- used.add(name);
354
- }
355
- }
356
- const allocateAlias = (base) => {
357
- let key = base;
358
- let suffix = 1;
359
- while (used.has(key)) {
360
- suffix += 1;
361
- key = `${base}_${suffix}`;
473
+ const pathParams = /* @__PURE__ */ Object.create(null);
474
+ const queryParts = [];
475
+ const headers = new Headers(await resolveHeaders(options.headers));
476
+ const bodyProps = /* @__PURE__ */ Object.create(null);
477
+ for (const [key, value] of Object.entries(options.args)) {
478
+ const mapping = options.parameterMap[key];
479
+ if (!mapping || value === void 0) {
480
+ continue;
362
481
  }
363
- used.add(key);
364
- return key;
365
- };
366
- for (const [name, occurrences] of byName) {
367
- const collides = name === "__proto__" || occurrences.length > 1 || bodyProperties.has(name);
368
- for (const param of occurrences) {
369
- const base = collides ? `${name}__${param.in}` : name;
370
- const key = collides ? allocateAlias(base) : name;
371
- properties[key] = rewriteComponentRefs(
372
- param.schema ?? { type: "string" }
373
- );
374
- parameterMap[key] = {
375
- allowReserved: param.allowReserved,
376
- explode: param.explode,
377
- in: param.in,
378
- name,
379
- style: param.style
380
- };
381
- if (param.in === "path" || param.required) {
382
- required.push(key);
482
+ switch (mapping.in) {
483
+ case "body":
484
+ bodyProps[mapping.name] = value;
485
+ break;
486
+ case "cookie": {
487
+ const pair = `${mapping.name}=${encodeCookieValue(mapping, value)}`;
488
+ const existing = headers.get("cookie");
489
+ headers.set("cookie", existing ? `${existing}; ${pair}` : pair);
490
+ break;
491
+ }
492
+ case "header":
493
+ headers.set(mapping.name, String(value));
494
+ break;
495
+ case "path":
496
+ pathParams[mapping.name] = encodePathValue(
497
+ value,
498
+ mapping.style,
499
+ mapping.explode
500
+ );
501
+ break;
502
+ case "query": {
503
+ const query = new URLSearchParams();
504
+ appendQueryValue(
505
+ query,
506
+ mapping.name,
507
+ mapping.style,
508
+ value,
509
+ mapping.explode
510
+ );
511
+ if (query.size > 0) {
512
+ queryParts.push(serializeQuery(query, mapping.allowReserved));
513
+ }
514
+ break;
383
515
  }
384
516
  }
385
517
  }
386
- for (const [name, { required: isRequired, schema }] of bodyProperties) {
387
- const key = name === "__proto__" ? allocateAlias(`${name}__body`) : name;
388
- properties[key] = rewriteComponentRefs(schema);
389
- parameterMap[key] = { in: "body", name };
390
- if (isRequired) {
391
- required.push(key);
518
+ let path = options.route.path;
519
+ for (const [name, value] of Object.entries(pathParams)) {
520
+ path = path.replaceAll(`{${name}}`, value);
521
+ }
522
+ assertNoSubstitutedDotSegments(options.route, path);
523
+ const url = new URL(baseUrl.replace(/\/$/, "") + path);
524
+ url.search = queryParts.join("&");
525
+ let body;
526
+ const requiredJsonBody = options.bodyEncoding === "json" && options.route.requestBody?.required === true && !options.wholeBodyKey;
527
+ if (Object.keys(bodyProps).length > 0 || requiredJsonBody) {
528
+ const payload = options.wholeBodyKey ? bodyProps[options.wholeBodyKey] : bodyProps;
529
+ if (options.bodyEncoding === "form") {
530
+ if (!headers.has("content-type")) {
531
+ headers.set("content-type", "application/x-www-form-urlencoded");
532
+ }
533
+ body = encodeFormBody(payload);
534
+ } else {
535
+ if (!headers.has("content-type")) {
536
+ headers.set("content-type", "application/json");
537
+ }
538
+ body = JSON.stringify(payload);
392
539
  }
393
540
  }
394
- const flatSchema = {
395
- additionalProperties: false,
396
- properties,
397
- type: "object",
398
- ...required.length > 0 ? { required } : {}
399
- };
400
- const usedDefs = sharedDefs && filterReferencedDefs(properties, sharedDefs);
401
- if (usedDefs) {
402
- flatSchema.$defs = usedDefs;
541
+ const response = await options.fetchImpl(url.toString(), {
542
+ body,
543
+ headers,
544
+ method: options.route.method.toUpperCase(),
545
+ signal: options.signal
546
+ });
547
+ const text = await response.text();
548
+ if (!response.ok) {
549
+ throw new UserError(
550
+ `${options.route.method.toUpperCase()} ${path} failed with ${response.status}: ${text.slice(0, 2e3)}`
551
+ );
403
552
  }
404
- return {
405
- bodyEncoding,
406
- flatSchema,
407
- parameterMap,
408
- unsupportedBodyContentType,
409
- wholeBodyKey
410
- };
411
- }
412
- function buildSharedDefs(document) {
413
- const schemas = document.components?.schemas;
414
- if (!schemas || Object.keys(schemas).length === 0) {
415
- return void 0;
553
+ const mediaType = response.headers.get("content-type")?.split(";")[0];
554
+ if (mediaType?.toLowerCase().includes("json")) {
555
+ try {
556
+ const json = JSON.parse(text);
557
+ return { json, text: JSON.stringify(json, null, 2) };
558
+ } catch {
559
+ return { text };
560
+ }
416
561
  }
417
- return rewriteNode(schemas, "schemaMap");
562
+ return { text };
418
563
  }
419
- var SUCCESS_STATUS_PATTERN = /^2\d\d$/;
420
- var MAX_OUTPUT_SCHEMA_DEFS = 50;
421
- function buildOutputSchema(route, sharedDefs) {
422
- const successEntry = Object.entries(route.responses ?? {}).find(
423
- ([code]) => SUCCESS_STATUS_PATTERN.test(code)
424
- );
425
- const declaredSchema = findMediaType(
426
- successEntry?.[1].content,
427
- "application/json"
428
- )?.schema;
429
- if (!declaredSchema) {
430
- return void 0;
431
- }
432
- const schema = resolveComponentRef(declaredSchema, sharedDefs);
433
- const rewritten = rewriteComponentRefs(schema, true);
434
- const { type } = rewritten;
435
- const isObjectShaped = type === "object" || Array.isArray(type) && type.includes("object");
436
- if (!isObjectShaped) {
437
- return void 0;
564
+ function resolveBaseUrl(servers, origin, overrideUrl) {
565
+ if (overrideUrl) {
566
+ return overrideUrl.replace(/\/$/, "");
438
567
  }
439
- const normalized = { ...rewritten, type: "object" };
440
- const usedDefs = sharedDefs && filterReferencedDefs(normalized, sharedDefs);
441
- if (usedDefs && Object.keys(usedDefs).length > MAX_OUTPUT_SCHEMA_DEFS) {
442
- return void 0;
568
+ const server = servers?.[0];
569
+ if (!server) {
570
+ throw new Error(
571
+ "The OpenAPI document has no `servers` entry. Pass `baseUrl` to fromOpenAPI() explicitly."
572
+ );
443
573
  }
444
- return {
445
- ...normalized,
446
- ...usedDefs ? {
447
- $defs: rewriteNode(usedDefs, "schemaMap", true)
448
- } : {}
449
- };
450
- }
451
- function resolveComponentRef(schema, sharedDefs) {
452
- const seen = /* @__PURE__ */ new Set();
453
- let current = schema;
454
- let ref = current.$ref;
455
- while (typeof ref === "string") {
456
- const name = componentSchemaName(ref);
457
- const target = name === void 0 ? void 0 : sharedDefs?.[name];
458
- if (name === void 0 || target === void 0 || seen.has(name)) {
459
- break;
574
+ let url = server.url;
575
+ for (const [name, variable] of Object.entries(server.variables ?? {})) {
576
+ url = url.replaceAll(`{${name}}`, () => variable.default);
577
+ }
578
+ try {
579
+ return new URL(url).toString().replace(/\/$/, "");
580
+ } catch {
581
+ if (!origin) {
582
+ throw new Error(
583
+ `The OpenAPI document's servers[0].url ("${url}") is relative, and the spec was not loaded from an http(s) URL, so it cannot be resolved to an absolute address. Pass \`baseUrl\` to fromOpenAPI() explicitly.`
584
+ );
460
585
  }
461
- seen.add(name);
462
- current = target;
463
- ref = current.$ref;
586
+ return new URL(url, origin).toString().replace(/\/$/, "");
464
587
  }
465
- return current;
466
- }
467
- function rewriteComponentRefs(value, stripExamples = false) {
468
- return rewriteNode(value, "schema", stripExamples);
469
588
  }
470
- function childMode(key) {
471
- if (DATA_KEYS.has(key)) {
472
- return "data";
589
+ function appendBracketPairs(params, key, value) {
590
+ if (Array.isArray(value)) {
591
+ for (const item of value) {
592
+ if (item !== null && typeof item === "object") {
593
+ appendBracketPairs(params, `${key}[]`, item);
594
+ } else {
595
+ params.append(key, String(item));
596
+ }
597
+ }
598
+ return;
473
599
  }
474
- return SCHEMA_MAP_KEYS.has(key) ? "schemaMap" : "schema";
475
- }
476
- function componentSchemaName(ref) {
477
- for (const prefix of ["#/components/schemas/", "#/$defs/"]) {
478
- if (ref.startsWith(prefix)) {
479
- return ref.slice(prefix.length);
600
+ if (value !== null && typeof value === "object") {
601
+ for (const [subKey, subValue] of Object.entries(
602
+ value
603
+ )) {
604
+ if (subValue !== void 0) {
605
+ appendBracketPairs(params, `${key}[${subKey}]`, subValue);
606
+ }
480
607
  }
608
+ return;
481
609
  }
482
- return void 0;
610
+ params.append(key, String(value));
483
611
  }
484
- function extractBodyProperties(requestBody, sharedDefs) {
485
- const properties = /* @__PURE__ */ new Map();
486
- const content = requestBody?.content;
487
- if (!content) {
488
- return { properties };
489
- }
490
- const json = findMediaType(content, "application/json");
491
- const form = findMediaType(content, "application/x-www-form-urlencoded");
492
- if (json === void 0 && form === void 0) {
493
- const contentTypes = Object.keys(content);
494
- return contentTypes.length > 0 ? { properties, unsupportedBodyContentType: contentTypes[0] } : { properties };
612
+ function appendQueryValue(query, name, style, value, explode) {
613
+ if (style === "deepObject") {
614
+ appendBracketPairs(query, name, value);
615
+ return;
495
616
  }
496
- const bodyEncoding = json === void 0 ? "form" : "json";
497
- const declaredSchema = bodyEncoding === "json" ? json?.schema : form?.schema;
498
- if (!declaredSchema) {
499
- return { bodyEncoding, properties };
617
+ if (style === "spaceDelimited" || style === "pipeDelimited") {
618
+ const items = Array.isArray(value) ? value : [value];
619
+ const separator = style === "spaceDelimited" ? " " : "|";
620
+ query.append(name, items.map(String).join(separator));
621
+ return;
500
622
  }
501
- const schema = bodyEncoding === "form" ? resolveComponentRef(declaredSchema, sharedDefs) : declaredSchema;
502
- const schemaProperties = schema.properties;
503
- if (schema.type === "object" && schemaProperties && (bodyEncoding === "form" || schema.additionalProperties === false || Object.keys(schemaProperties).length > 0)) {
504
- const requiredNames = new Set(
505
- schema.required ?? []
506
- );
507
- for (const [name, propertySchema] of Object.entries(schemaProperties)) {
508
- properties.set(name, {
509
- required: requiredNames.has(name),
510
- schema: propertySchema
511
- });
623
+ if ((style === void 0 || style === "form") && value !== null && typeof value === "object" && !Array.isArray(value)) {
624
+ const entries = Object.entries(value);
625
+ if (explode === false) {
626
+ if (entries.length > 0) {
627
+ query.append(
628
+ name,
629
+ entries.flatMap(([key, item]) => [key, String(item)]).join(",")
630
+ );
631
+ }
632
+ } else {
633
+ for (const [key, item] of entries) {
634
+ query.append(key, String(item));
635
+ }
512
636
  }
513
- return { bodyEncoding, properties };
637
+ return;
514
638
  }
515
- if (bodyEncoding === "form") {
516
- return {
517
- properties,
518
- unsupportedBodyContentType: "application/x-www-form-urlencoded"
519
- };
639
+ if ((style === void 0 || style === "form") && explode === false && Array.isArray(value) && value.length > 0) {
640
+ query.append(name, value.map(String).join(","));
641
+ return;
642
+ }
643
+ for (const item of Array.isArray(value) ? value : [value]) {
644
+ query.append(name, String(item));
520
645
  }
521
- properties.set("body", {
522
- required: requestBody?.required ?? false,
523
- schema
524
- });
525
- return { bodyEncoding, properties, wholeBodyKey: "body" };
526
646
  }
527
- function filterReferencedDefs(node, allDefs) {
528
- const referenced = /* @__PURE__ */ new Set();
529
- const stack = [node];
530
- while (stack.length > 0) {
531
- const current = stack.pop();
532
- if (Array.isArray(current)) {
533
- stack.push(...current);
534
- continue;
647
+ function assertNoSubstitutedDotSegments(route, resolvedPath) {
648
+ const templateSegments = new Set(route.path.split("/"));
649
+ for (const segment of resolvedPath.split("/")) {
650
+ if ((segment === "." || segment === "..") && !templateSegments.has(segment)) {
651
+ throw new UserError(
652
+ `${route.method.toUpperCase()} ${route.path}: path parameter values must not produce a "${segment}" path segment.`
653
+ );
535
654
  }
536
- if (!current || typeof current !== "object") {
537
- continue;
655
+ }
656
+ }
657
+ function encodeCookieValue(mapping, value) {
658
+ return (Array.isArray(value) ? value : [value]).map((item) => {
659
+ const text = String(item);
660
+ if (mapping.style !== "cookie") {
661
+ return encodePathComponent(text);
538
662
  }
663
+ if (/[;,]/.test(text)) {
664
+ throw new UserError(
665
+ `Cookie parameter "${mapping.name}" uses style "cookie" and its value contains ";" or ","`
666
+ );
667
+ }
668
+ return text;
669
+ }).join(",");
670
+ }
671
+ function encodeFormBody(payload) {
672
+ const params = new URLSearchParams();
673
+ if (payload && typeof payload === "object" && !Array.isArray(payload)) {
539
674
  for (const [key, value] of Object.entries(
540
- current
675
+ payload
541
676
  )) {
542
- if (key === "$ref" && typeof value === "string" && value.startsWith("#/$defs/")) {
543
- const [name = ""] = value.slice("#/$defs/".length).split("/");
544
- if (allDefs[name] && !referenced.has(name)) {
545
- referenced.add(name);
546
- stack.push(allDefs[name]);
547
- }
677
+ if (value === void 0) {
548
678
  continue;
549
679
  }
550
- stack.push(value);
680
+ appendBracketPairs(params, key, value);
551
681
  }
552
682
  }
553
- if (referenced.size === 0) {
554
- return void 0;
555
- }
556
- return Object.fromEntries(
557
- [...referenced].map((name) => [name, allDefs[name]])
558
- );
683
+ return params.toString();
559
684
  }
560
- function findMediaType(content, mediaType) {
561
- if (!content) {
562
- return void 0;
563
- }
564
- const key = Object.hasOwn(content, mediaType) ? mediaType : Object.keys(content).find(
565
- (candidate) => candidate.split(";")[0].trim().toLowerCase() === mediaType
685
+ function encodePathComponent(value) {
686
+ return encodeURIComponent(value).replace(
687
+ /[!'()*]/g,
688
+ (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`
566
689
  );
567
- return key === void 0 ? void 0 : content[key];
568
690
  }
569
- function normalizeExclusiveBound(schema, exclusiveKey, boundKey) {
570
- const { [exclusiveKey]: exclusive, ...rest } = schema;
571
- if (typeof exclusive !== "boolean") {
572
- return schema;
691
+ function encodePathValue(value, style, explode) {
692
+ if ((style === void 0 || style === "simple") && Array.isArray(value) && value.every((item) => item === null || typeof item !== "object")) {
693
+ return value.map((item) => encodePathComponent(String(item))).join(",");
573
694
  }
574
- if (!exclusive || typeof rest[boundKey] !== "number") {
575
- return rest;
695
+ if ((style === void 0 || style === "simple") && value !== null && typeof value === "object" && !Array.isArray(value)) {
696
+ const entries = Object.entries(value);
697
+ if (entries.every(([, item]) => item === null || typeof item !== "object")) {
698
+ return entries.map(([key, item]) => {
699
+ const encodedKey = encodePathComponent(key);
700
+ const encodedValue = encodePathComponent(String(item));
701
+ return explode === true ? `${encodedKey}=${encodedValue}` : `${encodedKey},${encodedValue}`;
702
+ }).join(",");
703
+ }
576
704
  }
577
- const { [boundKey]: bound, ...unbounded } = rest;
578
- return { ...unbounded, [exclusiveKey]: bound };
579
- }
580
- function normalizeExclusiveBounds(schema) {
581
- return normalizeExclusiveBound(
582
- normalizeExclusiveBound(schema, "exclusiveMinimum", "minimum"),
583
- "exclusiveMaximum",
584
- "maximum"
585
- );
705
+ return encodeURIComponent(String(value));
586
706
  }
587
- function normalizeNullable(schema) {
588
- if (!("nullable" in schema)) {
589
- return schema;
590
- }
591
- const { nullable, ...rest } = schema;
592
- if (nullable !== true) {
593
- return rest;
594
- }
595
- const { type } = rest;
596
- if (typeof type === "string") {
597
- return { ...rest, type: [type, "null"] };
598
- }
599
- if (Array.isArray(type)) {
600
- return { ...rest, type: [.../* @__PURE__ */ new Set(["null", ...type])] };
707
+ async function resolveHeaders(headers) {
708
+ if (!headers) {
709
+ return {};
601
710
  }
602
- return rest;
711
+ return typeof headers === "function" ? await headers() : { ...headers };
603
712
  }
604
- function rewriteNode(value, mode, stripExamples = false) {
605
- if (mode === "data") {
606
- return value;
607
- }
608
- if (Array.isArray(value)) {
609
- return value.map((item) => rewriteNode(item, "schema", stripExamples));
610
- }
611
- if (!value || typeof value !== "object") {
612
- return value;
713
+ function serializeQuery(query, allowReserved) {
714
+ const encoded = query.toString();
715
+ if (!allowReserved) {
716
+ return encoded;
613
717
  }
614
- const entries = Object.entries(value).filter(
615
- ([key]) => !stripExamples || mode !== "schema" || !STRIP_KEYS.has(key)
616
- ).map(([key, entryValue]) => {
617
- if (mode === "schemaMap") {
618
- return [key, rewriteNode(entryValue, "schema", stripExamples)];
619
- }
620
- if (key === "$ref" && typeof entryValue === "string" && entryValue.startsWith("#/components/schemas/")) {
621
- return [key, entryValue.replace("#/components/schemas/", "#/$defs/")];
622
- }
623
- return [key, rewriteNode(entryValue, childMode(key), stripExamples)];
624
- });
625
- const rewritten = Object.fromEntries(entries);
626
- return mode === "schemaMap" ? rewritten : normalizeExclusiveBounds(normalizeNullable(rewritten));
718
+ return encoded.split("&").map((pair) => {
719
+ const [name, value] = pair.split("=");
720
+ const expanded = value.replace(
721
+ /\+|%25([\da-f]{2})|%(21|24|27|28|29|2c|2f|3a|3b|3f|40|7e)/gi,
722
+ (match, triple) => triple ? `%${triple}` : match === "+" ? "%20" : decodeURIComponent(match)
723
+ );
724
+ return `${name}=${expanded}`;
725
+ }).join("&");
627
726
  }
628
727
 
629
728
  // src/openapi/resourceMapping.ts
@@ -855,11 +954,12 @@ async function fromOpenAPI(options) {
855
954
  const outputSchema = outputSchemaJson ? jsonSchemaAdapter(outputSchemaJson) : void 0;
856
955
  server.addTool({
857
956
  description: route.summary ?? `${route.method.toUpperCase()} ${route.path}`,
858
- execute: async (args) => {
957
+ execute: async (args, { signal }) => {
859
958
  const result = await executeRequest({
860
959
  ...execOptions,
861
960
  args,
862
961
  bodyEncoding,
962
+ signal,
863
963
  wholeBodyKey
864
964
  });
865
965
  return resolveToolResult(result, outputSchema);