fastmcp 4.18.0 → 4.19.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/openapi/index.cjs +209 -29
- package/dist/openapi/index.cjs.map +1 -1
- package/dist/openapi/index.d.cts +11 -1
- package/dist/openapi/index.d.ts +11 -1
- package/dist/openapi/index.js +205 -25
- package/dist/openapi/index.js.map +1 -1
- package/package.json +1 -1
package/dist/openapi/index.cjs
CHANGED
|
@@ -28,7 +28,7 @@ function isHttpUrl(value) {
|
|
|
28
28
|
// src/openapi/naming.ts
|
|
29
29
|
var MAX_NAME_LENGTH = 56;
|
|
30
30
|
var MAX_BASE_LENGTH = MAX_NAME_LENGTH - 5;
|
|
31
|
-
function
|
|
31
|
+
function generateNames(routes, mcpNames) {
|
|
32
32
|
const names = /* @__PURE__ */ new Map();
|
|
33
33
|
const used = /* @__PURE__ */ new Set();
|
|
34
34
|
for (const route of routes) {
|
|
@@ -104,12 +104,18 @@ async function executeRequest(options) {
|
|
|
104
104
|
url.search = query.toString();
|
|
105
105
|
let body;
|
|
106
106
|
if (Object.keys(bodyProps).length > 0) {
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
const payload = options.wholeBodyKey ? bodyProps[options.wholeBodyKey] : bodyProps;
|
|
108
|
+
if (options.bodyEncoding === "form") {
|
|
109
|
+
if (!headers.has("content-type")) {
|
|
110
|
+
headers.set("content-type", "application/x-www-form-urlencoded");
|
|
111
|
+
}
|
|
112
|
+
body = encodeFormBody(payload);
|
|
113
|
+
} else {
|
|
114
|
+
if (!headers.has("content-type")) {
|
|
115
|
+
headers.set("content-type", "application/json");
|
|
116
|
+
}
|
|
117
|
+
body = JSON.stringify(payload);
|
|
109
118
|
}
|
|
110
|
-
body = JSON.stringify(
|
|
111
|
-
options.wholeBodyKey ? bodyProps[options.wholeBodyKey] : bodyProps
|
|
112
|
-
);
|
|
113
119
|
}
|
|
114
120
|
const response = await options.fetchImpl(url.toString(), {
|
|
115
121
|
body,
|
|
@@ -156,6 +162,25 @@ function resolveBaseUrl(servers, origin, overrideUrl) {
|
|
|
156
162
|
return new URL(url, origin).toString().replace(/\/$/, "");
|
|
157
163
|
}
|
|
158
164
|
}
|
|
165
|
+
function encodeFormBody(payload) {
|
|
166
|
+
const params = new URLSearchParams();
|
|
167
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
168
|
+
for (const [key, value] of Object.entries(
|
|
169
|
+
payload
|
|
170
|
+
)) {
|
|
171
|
+
if (value === void 0) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
for (const item of Array.isArray(value) ? value : [value]) {
|
|
175
|
+
params.append(
|
|
176
|
+
key,
|
|
177
|
+
item !== null && typeof item === "object" ? JSON.stringify(item) : String(item)
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return params.toString();
|
|
183
|
+
}
|
|
159
184
|
async function resolveHeaders(headers) {
|
|
160
185
|
if (!headers) {
|
|
161
186
|
return {};
|
|
@@ -163,6 +188,38 @@ async function resolveHeaders(headers) {
|
|
|
163
188
|
return typeof headers === "function" ? await headers() : { ...headers };
|
|
164
189
|
}
|
|
165
190
|
|
|
191
|
+
// src/openapi/resourceMapping.ts
|
|
192
|
+
function buildResourceMapping(route, name, parameterMap, requiredKeys) {
|
|
193
|
+
const entries = Object.entries(parameterMap);
|
|
194
|
+
if (entries.length === 0) {
|
|
195
|
+
return { kind: "resource", uri: `openapi://${name}${route.path}` };
|
|
196
|
+
}
|
|
197
|
+
const required = new Set(_nullishCoalesce(requiredKeys, () => ( [])));
|
|
198
|
+
const args = [];
|
|
199
|
+
const queryKeys = [];
|
|
200
|
+
let path = route.path;
|
|
201
|
+
for (const [flatKey, mapping] of entries) {
|
|
202
|
+
if (mapping.in === "path") {
|
|
203
|
+
if (flatKey !== mapping.name) {
|
|
204
|
+
path = path.replace(`{${mapping.name}}`, `{${flatKey}}`);
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
queryKeys.push(flatKey);
|
|
208
|
+
}
|
|
209
|
+
args.push({ name: flatKey, required: required.has(flatKey) });
|
|
210
|
+
}
|
|
211
|
+
const uriTemplate = `openapi://${name}${path}` + (queryKeys.length > 0 ? `{?${queryKeys.join(",")}}` : "");
|
|
212
|
+
return { args, kind: "template", uriTemplate };
|
|
213
|
+
}
|
|
214
|
+
function isEligibleForResource(route) {
|
|
215
|
+
return route.parameters.every((param) => {
|
|
216
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
return _optionalChain([param, 'access', _11 => _11.schema, 'optionalAccess', _12 => _12.type]) !== "array";
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
166
223
|
// src/openapi/routes.ts
|
|
167
224
|
var HTTP_METHODS = ["get", "put", "post", "delete", "patch"];
|
|
168
225
|
function extractRoutes(document) {
|
|
@@ -217,7 +274,7 @@ function resolveRef(document, value) {
|
|
|
217
274
|
);
|
|
218
275
|
let node = document;
|
|
219
276
|
for (const segment of segments) {
|
|
220
|
-
node = _optionalChain([node, 'optionalAccess',
|
|
277
|
+
node = _optionalChain([node, 'optionalAccess', _13 => _13[segment]]);
|
|
221
278
|
}
|
|
222
279
|
return node;
|
|
223
280
|
}
|
|
@@ -238,8 +295,14 @@ function buildFlatSchema(route, sharedDefs) {
|
|
|
238
295
|
list.push(param);
|
|
239
296
|
byName.set(param.name, list);
|
|
240
297
|
}
|
|
241
|
-
const {
|
|
242
|
-
|
|
298
|
+
const {
|
|
299
|
+
bodyEncoding,
|
|
300
|
+
properties: bodyProperties,
|
|
301
|
+
unsupportedBodyContentType,
|
|
302
|
+
wholeBodyKey
|
|
303
|
+
} = extractBodyProperties(
|
|
304
|
+
route.method === "get" ? void 0 : route.requestBody,
|
|
305
|
+
sharedDefs
|
|
243
306
|
);
|
|
244
307
|
const properties = {};
|
|
245
308
|
const required = [];
|
|
@@ -274,10 +337,16 @@ function buildFlatSchema(route, sharedDefs) {
|
|
|
274
337
|
if (usedDefs) {
|
|
275
338
|
flatSchema.$defs = usedDefs;
|
|
276
339
|
}
|
|
277
|
-
return {
|
|
340
|
+
return {
|
|
341
|
+
bodyEncoding,
|
|
342
|
+
flatSchema,
|
|
343
|
+
parameterMap,
|
|
344
|
+
unsupportedBodyContentType,
|
|
345
|
+
wholeBodyKey
|
|
346
|
+
};
|
|
278
347
|
}
|
|
279
348
|
function buildSharedDefs(document) {
|
|
280
|
-
const schemas = _optionalChain([document, 'access',
|
|
349
|
+
const schemas = _optionalChain([document, 'access', _14 => _14.components, 'optionalAccess', _15 => _15.schemas]);
|
|
281
350
|
if (!schemas || Object.keys(schemas).length === 0) {
|
|
282
351
|
return void 0;
|
|
283
352
|
}
|
|
@@ -292,12 +361,32 @@ function childMode(key) {
|
|
|
292
361
|
}
|
|
293
362
|
return SCHEMA_MAP_KEYS.has(key) ? "schemaMap" : "schema";
|
|
294
363
|
}
|
|
295
|
-
function
|
|
364
|
+
function componentSchemaName(ref) {
|
|
365
|
+
for (const prefix of ["#/components/schemas/", "#/$defs/"]) {
|
|
366
|
+
if (ref.startsWith(prefix)) {
|
|
367
|
+
return ref.slice(prefix.length);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return void 0;
|
|
371
|
+
}
|
|
372
|
+
function extractBodyProperties(requestBody, sharedDefs) {
|
|
296
373
|
const properties = /* @__PURE__ */ new Map();
|
|
297
|
-
const
|
|
298
|
-
if (!
|
|
374
|
+
const content = _optionalChain([requestBody, 'optionalAccess', _16 => _16.content]);
|
|
375
|
+
if (!content) {
|
|
299
376
|
return { properties };
|
|
300
377
|
}
|
|
378
|
+
const hasJson = "application/json" in content;
|
|
379
|
+
const hasForm = "application/x-www-form-urlencoded" in content;
|
|
380
|
+
if (!hasJson && !hasForm) {
|
|
381
|
+
const contentTypes = Object.keys(content);
|
|
382
|
+
return contentTypes.length > 0 ? { properties, unsupportedBodyContentType: contentTypes[0] } : { properties };
|
|
383
|
+
}
|
|
384
|
+
const bodyEncoding = hasJson ? "json" : "form";
|
|
385
|
+
const declaredSchema = hasJson ? _optionalChain([content, 'access', _17 => _17["application/json"], 'optionalAccess', _18 => _18.schema]) : _optionalChain([content, 'access', _19 => _19["application/x-www-form-urlencoded"], 'optionalAccess', _20 => _20.schema]);
|
|
386
|
+
if (!declaredSchema) {
|
|
387
|
+
return { bodyEncoding, properties };
|
|
388
|
+
}
|
|
389
|
+
const schema = bodyEncoding === "form" ? resolveComponentRef(declaredSchema, sharedDefs) : declaredSchema;
|
|
301
390
|
const schemaProperties = schema.properties;
|
|
302
391
|
if (schema.type === "object" && schemaProperties) {
|
|
303
392
|
const requiredNames = new Set(
|
|
@@ -309,13 +398,19 @@ function extractBodyProperties(requestBody) {
|
|
|
309
398
|
schema: propertySchema
|
|
310
399
|
});
|
|
311
400
|
}
|
|
312
|
-
return { properties };
|
|
401
|
+
return { bodyEncoding, properties };
|
|
402
|
+
}
|
|
403
|
+
if (bodyEncoding === "form") {
|
|
404
|
+
return {
|
|
405
|
+
properties,
|
|
406
|
+
unsupportedBodyContentType: "application/x-www-form-urlencoded"
|
|
407
|
+
};
|
|
313
408
|
}
|
|
314
409
|
properties.set("body", {
|
|
315
|
-
required: _nullishCoalesce(_optionalChain([requestBody, 'optionalAccess',
|
|
410
|
+
required: _nullishCoalesce(_optionalChain([requestBody, 'optionalAccess', _21 => _21.required]), () => ( false)),
|
|
316
411
|
schema
|
|
317
412
|
});
|
|
318
|
-
return { properties, wholeBodyKey: "body" };
|
|
413
|
+
return { bodyEncoding, properties, wholeBodyKey: "body" };
|
|
319
414
|
}
|
|
320
415
|
function filterReferencedDefs(node, allDefs) {
|
|
321
416
|
const referenced = /* @__PURE__ */ new Set();
|
|
@@ -366,6 +461,22 @@ function normalizeNullable(schema) {
|
|
|
366
461
|
}
|
|
367
462
|
return rest;
|
|
368
463
|
}
|
|
464
|
+
function resolveComponentRef(schema, sharedDefs) {
|
|
465
|
+
const seen = /* @__PURE__ */ new Set();
|
|
466
|
+
let current = schema;
|
|
467
|
+
let ref = current.$ref;
|
|
468
|
+
while (typeof ref === "string") {
|
|
469
|
+
const name = componentSchemaName(ref);
|
|
470
|
+
const target = name === void 0 ? void 0 : _optionalChain([sharedDefs, 'optionalAccess', _22 => _22[name]]);
|
|
471
|
+
if (name === void 0 || target === void 0 || seen.has(name)) {
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
seen.add(name);
|
|
475
|
+
current = target;
|
|
476
|
+
ref = current.$ref;
|
|
477
|
+
}
|
|
478
|
+
return current;
|
|
479
|
+
}
|
|
369
480
|
function rewriteNode(value, mode) {
|
|
370
481
|
if (mode === "data") {
|
|
371
482
|
return value;
|
|
@@ -442,40 +553,109 @@ async function fromOpenAPI(options) {
|
|
|
442
553
|
const { document, origin } = await loadSpec(options.spec);
|
|
443
554
|
const routes = extractRoutes(document);
|
|
444
555
|
const selected = selectRoutes(routes, options);
|
|
445
|
-
const names =
|
|
556
|
+
const names = generateNames(selected, options.mcpNames);
|
|
446
557
|
const sharedDefs = buildSharedDefs(document);
|
|
447
558
|
const server = _nullishCoalesce(options.server, () => ( new (0, _chunkSYZRGVQXcjs.FastMCP)({
|
|
448
|
-
name: _nullishCoalesce(_nullishCoalesce(options.name, () => ( _optionalChain([document, 'access',
|
|
559
|
+
name: _nullishCoalesce(_nullishCoalesce(options.name, () => ( _optionalChain([document, 'access', _23 => _23.info, 'optionalAccess', _24 => _24.title]))), () => ( "OpenAPI Server")),
|
|
449
560
|
version: _nullishCoalesce(options.version, () => ( "1.0.0"))
|
|
450
561
|
})));
|
|
562
|
+
const skippedOperations = [];
|
|
451
563
|
for (const route of selected) {
|
|
452
564
|
const name = names.get(route);
|
|
453
565
|
if (!name) {
|
|
454
566
|
continue;
|
|
455
567
|
}
|
|
456
|
-
const {
|
|
568
|
+
const {
|
|
569
|
+
bodyEncoding,
|
|
570
|
+
flatSchema,
|
|
571
|
+
parameterMap,
|
|
572
|
+
unsupportedBodyContentType,
|
|
573
|
+
wholeBodyKey
|
|
574
|
+
} = buildFlatSchema(route, sharedDefs);
|
|
575
|
+
const execOptions = {
|
|
576
|
+
baseUrlOverride: options.baseUrl,
|
|
577
|
+
fetchImpl: _nullishCoalesce(options.fetch, () => ( fetch)),
|
|
578
|
+
headers: options.headers,
|
|
579
|
+
origin,
|
|
580
|
+
parameterMap,
|
|
457
581
|
route,
|
|
458
|
-
|
|
459
|
-
|
|
582
|
+
servers: document.servers
|
|
583
|
+
};
|
|
584
|
+
if (options.resources && route.method === "get" && isEligibleForResource(route)) {
|
|
585
|
+
registerResource(
|
|
586
|
+
server,
|
|
587
|
+
route,
|
|
588
|
+
name,
|
|
589
|
+
parameterMap,
|
|
590
|
+
flatSchema.required,
|
|
591
|
+
execOptions
|
|
592
|
+
);
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
if (unsupportedBodyContentType) {
|
|
596
|
+
skippedOperations.push({
|
|
597
|
+
contentType: unsupportedBodyContentType,
|
|
598
|
+
method: route.method,
|
|
599
|
+
path: route.path
|
|
600
|
+
});
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
460
603
|
server.addTool({
|
|
461
604
|
description: _nullishCoalesce(route.summary, () => ( `${route.method.toUpperCase()} ${route.path}`)),
|
|
462
605
|
execute: async (args) => executeRequest({
|
|
606
|
+
...execOptions,
|
|
463
607
|
args,
|
|
464
|
-
|
|
465
|
-
fetchImpl: _nullishCoalesce(options.fetch, () => ( fetch)),
|
|
466
|
-
headers: options.headers,
|
|
467
|
-
origin,
|
|
468
|
-
parameterMap,
|
|
469
|
-
route,
|
|
470
|
-
servers: document.servers,
|
|
608
|
+
bodyEncoding,
|
|
471
609
|
wholeBodyKey
|
|
472
610
|
}),
|
|
473
611
|
name,
|
|
474
612
|
parameters: _chunkSYZRGVQXcjs.jsonSchemaAdapter.call(void 0, flatSchema)
|
|
475
613
|
});
|
|
476
614
|
}
|
|
615
|
+
if (skippedOperations.length > 0) {
|
|
616
|
+
console.warn(
|
|
617
|
+
`fromOpenAPI: skipped ${skippedOperations.length} operation(s) whose request body can't be turned into tool parameters (supported: application/json, or application/x-www-form-urlencoded with a flat object schema): ` + skippedOperations.map(
|
|
618
|
+
(op) => `${op.method.toUpperCase()} ${op.path} (${op.contentType})`
|
|
619
|
+
).join(", ")
|
|
620
|
+
);
|
|
621
|
+
}
|
|
477
622
|
return server;
|
|
478
623
|
}
|
|
624
|
+
function registerResource(server, route, name, parameterMap, requiredKeys, execOptions) {
|
|
625
|
+
const mapping = buildResourceMapping(route, name, parameterMap, requiredKeys);
|
|
626
|
+
const description = _nullishCoalesce(route.summary, () => ( `GET ${route.path}`));
|
|
627
|
+
if (mapping.kind === "resource") {
|
|
628
|
+
server.addResource({
|
|
629
|
+
description,
|
|
630
|
+
load: async () => wrapAsResourceResult(
|
|
631
|
+
await executeRequest({ ...execOptions, args: {} })
|
|
632
|
+
),
|
|
633
|
+
name,
|
|
634
|
+
uri: mapping.uri
|
|
635
|
+
});
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
server.addResourceTemplate({
|
|
639
|
+
arguments: mapping.args,
|
|
640
|
+
description,
|
|
641
|
+
load: async (args) => wrapAsResourceResult(
|
|
642
|
+
await executeRequest({
|
|
643
|
+
...execOptions,
|
|
644
|
+
args
|
|
645
|
+
})
|
|
646
|
+
),
|
|
647
|
+
name,
|
|
648
|
+
uriTemplate: mapping.uriTemplate
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
function wrapAsResourceResult(text) {
|
|
652
|
+
try {
|
|
653
|
+
JSON.parse(text);
|
|
654
|
+
return { mimeType: "application/json", text };
|
|
655
|
+
} catch (e3) {
|
|
656
|
+
return { mimeType: "text/plain", text };
|
|
657
|
+
}
|
|
658
|
+
}
|
|
479
659
|
|
|
480
660
|
|
|
481
661
|
exports.fromOpenAPI = fromOpenAPI;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/fastmcp/fastmcp/dist/openapi/index.cjs","../../src/openapi/loadSpec.ts","../../src/openapi/naming.ts","../../src/openapi/requestBuilder.ts","../../src/openapi/routes.ts","../../src/openapi/schemas.ts","../../src/openapi/selection.ts","../../src/openapi/fromOpenAPI.ts"],"names":[],"mappings":"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","file":"/home/runner/work/fastmcp/fastmcp/dist/openapi/index.cjs","sourcesContent":[null,"import SwaggerParser from \"@apidevtools/swagger-parser\";\n\nimport type { BundledOpenApiDocument } from \"./types.js\";\n\nexport interface LoadedSpec {\n document: BundledOpenApiDocument;\n /**\n * The spec's own URL, when it was loaded from one. Used to resolve a\n * relative `servers[0].url` against the document's origin.\n */\n origin?: string;\n}\n\n/**\n * Loads and bundles an OpenAPI document, resolving local *and* external\n * `$ref`s (relative paths, absolute URLs, `other.yaml#/fragment`).\n *\n * `spec` is handed to swagger-parser as-is — a URL, file path, or object —\n * rather than being fetched and re-parsed here first. External refs resolve\n * relative to whatever document they were found in, so pre-fetching the\n * entry document and passing its parsed text as an object would resolve\n * every external ref against the wrong base (or none at all).\n */\nexport async function loadSpec(\n spec: Record<string, unknown> | string,\n): Promise<LoadedSpec> {\n const document = (await SwaggerParser.bundle(\n spec as never,\n )) as unknown as BundledOpenApiDocument;\n\n if (!document.openapi?.startsWith(\"3.\")) {\n throw new Error(\n `fromOpenAPI only supports OpenAPI 3.x documents (found ${\n document.openapi ?? document.swagger ?? \"an unrecognized version\"\n }). Swagger 2.0 is not supported.`,\n );\n }\n\n return {\n document,\n origin: typeof spec === \"string\" && isHttpUrl(spec) ? spec : undefined,\n };\n}\n\nfunction isHttpUrl(value: string): boolean {\n return value.startsWith(\"http://\") || value.startsWith(\"https://\");\n}\n","import type { HttpRoute } from \"./types.js\";\n\nconst MAX_NAME_LENGTH = 56;\n// Reserves room for a \"_<n>\" collision suffix so the final name never\n// exceeds MAX_NAME_LENGTH, however many collisions it takes.\nconst MAX_BASE_LENGTH = MAX_NAME_LENGTH - 5;\n\n/**\n * Generates a unique tool name per route.\n *\n * Ports the Python implementation's naming rule\n * (`server/providers/openapi/provider.py:_generate_default_name`): prefer\n * `mcpNames[operationId]`, then `operationId` (FastAPI-style `__` suffixes\n * stripped), falling back to `summary` or `{method}_{path}`; slugified and\n * capped at 56 characters, with `_2`, `_3`, ... appended on collision.\n *\n * Uniqueness is checked against the final (post-suffix) name, not just the\n * base — otherwise a spec whose own operationIds already look auto-suffixed\n * (e.g. both \"foo\" and \"foo_2\" present) could produce two identically-named\n * tools, one of which `FastMCP.addTool` would silently drop.\n */\nexport function generateToolNames(\n routes: HttpRoute[],\n mcpNames: Record<string, string> | undefined,\n): Map<HttpRoute, string> {\n const names = new Map<HttpRoute, string>();\n const used = new Set<string>();\n\n for (const route of routes) {\n const base = slugify(baseNameFor(route, mcpNames));\n let candidate = base;\n let suffix = 1;\n\n while (used.has(candidate)) {\n suffix += 1;\n candidate = `${base}_${suffix}`;\n }\n\n used.add(candidate);\n names.set(route, candidate);\n }\n\n return names;\n}\n\nfunction baseNameFor(\n route: HttpRoute,\n mcpNames: Record<string, string> | undefined,\n): string {\n if (route.operationId) {\n return mcpNames?.[route.operationId] ?? route.operationId.split(\"__\")[0];\n }\n\n return route.summary || `${route.method}_${route.path}`;\n}\n\nfunction slugify(value: string): string {\n const slug = value\n .replace(/[^a-zA-Z0-9_]+/g, \"_\")\n .replace(/_+/g, \"_\")\n .replace(/^_|_$/g, \"\")\n .slice(0, MAX_BASE_LENGTH);\n\n return slug || \"operation\";\n}\n","import type { ParameterMapping } from \"./schemas.js\";\nimport type { FromOpenAPIOptions, HttpRoute, OpenApiServer } from \"./types.js\";\n\nimport { UserError } from \"../FastMCP.js\";\n\nexport interface ExecuteRequestOptions {\n args: Record<string, unknown>;\n baseUrlOverride?: string;\n fetchImpl: typeof fetch;\n headers?: FromOpenAPIOptions[\"headers\"];\n origin?: string;\n parameterMap: Record<string, ParameterMapping>;\n route: HttpRoute;\n servers: OpenApiServer[] | undefined;\n wholeBodyKey?: string;\n}\n\nexport async function executeRequest(\n options: ExecuteRequestOptions,\n): Promise<string> {\n const baseUrl = resolveBaseUrl(\n options.servers,\n options.origin,\n options.baseUrlOverride,\n );\n\n const pathParams: Record<string, string> = {};\n const query = new URLSearchParams();\n // A plain object keys headers case-sensitively, so a caller-supplied\n // header (e.g. \"Content-Type\") wouldn't be recognized as the same header\n // as one this function sets internally (e.g. \"content-type\") — `Headers`\n // normalizes casing, so `.set()` correctly overrides rather than\n // combining into a comma-joined, malformed value.\n const headers = new Headers(await resolveHeaders(options.headers));\n const bodyProps: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(options.args)) {\n const mapping = options.parameterMap[key];\n\n if (!mapping || value === undefined) {\n continue;\n }\n\n switch (mapping.in) {\n case \"body\":\n bodyProps[mapping.name] = value;\n break;\n case \"cookie\": {\n const existing = headers.get(\"cookie\");\n headers.set(\n \"cookie\",\n existing\n ? `${existing}; ${mapping.name}=${String(value)}`\n : `${mapping.name}=${String(value)}`,\n );\n break;\n }\n case \"header\":\n headers.set(mapping.name, String(value));\n break;\n case \"path\":\n pathParams[mapping.name] = String(value);\n break;\n case \"query\":\n for (const item of Array.isArray(value) ? value : [value]) {\n query.append(mapping.name, String(item));\n }\n break;\n }\n }\n\n let path = options.route.path;\n\n for (const [name, value] of Object.entries(pathParams)) {\n path = path.replace(`{${name}}`, encodeURIComponent(value));\n }\n\n const url = new URL(baseUrl.replace(/\\/$/, \"\") + path);\n url.search = query.toString();\n\n let body: string | undefined;\n\n if (Object.keys(bodyProps).length > 0) {\n if (!headers.has(\"content-type\")) {\n headers.set(\"content-type\", \"application/json\");\n }\n\n body = JSON.stringify(\n options.wholeBodyKey ? bodyProps[options.wholeBodyKey] : bodyProps,\n );\n }\n\n const response = await options.fetchImpl(url.toString(), {\n body,\n headers,\n method: options.route.method.toUpperCase(),\n });\n\n const text = await response.text();\n\n if (!response.ok) {\n throw new UserError(\n `${options.route.method.toUpperCase()} ${path} failed with ${response.status}: ${text.slice(0, 2000)}`,\n );\n }\n\n if (response.headers.get(\"content-type\")?.includes(\"json\")) {\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch {\n return text;\n }\n }\n\n return text;\n}\n\n/**\n * Resolves `servers[0].url` the way a real HTTP client needs it resolved,\n * not just the way a schema validator would accept it: a relative URL (e.g.\n * Petstore's own `\"/api/v3\"`) is joined against the document's own origin,\n * not passed through verbatim.\n */\nexport function resolveBaseUrl(\n servers: OpenApiServer[] | undefined,\n origin: string | undefined,\n overrideUrl: string | undefined,\n): string {\n if (overrideUrl) {\n return overrideUrl.replace(/\\/$/, \"\");\n }\n\n const server = servers?.[0];\n\n if (!server) {\n throw new Error(\n \"The OpenAPI document has no `servers` entry. Pass `baseUrl` to fromOpenAPI() explicitly.\",\n );\n }\n\n let url = server.url;\n\n for (const [name, variable] of Object.entries(server.variables ?? {})) {\n url = url.replaceAll(`{${name}}`, variable.default);\n }\n\n try {\n return new URL(url).toString().replace(/\\/$/, \"\");\n } catch {\n if (!origin) {\n throw new Error(\n `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.`,\n );\n }\n\n return new URL(url, origin).toString().replace(/\\/$/, \"\");\n }\n}\n\nasync function resolveHeaders(\n headers: FromOpenAPIOptions[\"headers\"],\n): Promise<Record<string, string>> {\n if (!headers) {\n return {};\n }\n\n return typeof headers === \"function\" ? await headers() : { ...headers };\n}\n","import type {\n BundledOpenApiDocument,\n HttpMethod,\n HttpRoute,\n OpenApiParameter,\n OpenApiParameterRef,\n OpenApiRequestBody,\n} from \"./types.js\";\n\nconst HTTP_METHODS: HttpMethod[] = [\"get\", \"put\", \"post\", \"delete\", \"patch\"];\n\n/**\n * Walks a bundled document's `paths` into a flat list of routes, resolving\n * any structural (non-schema) `$ref`s on parameters and request bodies —\n * e.g. `#/components/parameters/Limit` — against the same document.\n *\n * Bundling (see `loadSpec.ts`) guarantees every remaining `$ref` here is\n * local, so a plain JSON-pointer lookup is enough.\n */\nexport function extractRoutes(document: BundledOpenApiDocument): HttpRoute[] {\n const routes: HttpRoute[] = [];\n\n for (const [path, pathItem] of Object.entries(document.paths ?? {})) {\n const pathLevelParams = (pathItem.parameters ?? []).map((param) =>\n resolveRef<OpenApiParameter>(document, param),\n );\n\n for (const method of HTTP_METHODS) {\n const operation = pathItem[method];\n\n if (!operation) {\n continue;\n }\n\n const operationParams = (operation.parameters ?? []).map((param) =>\n resolveRef<OpenApiParameter>(document, param),\n );\n\n routes.push({\n deprecated: operation.deprecated ?? false,\n method,\n operationId: operation.operationId,\n parameters: mergeParameters(pathLevelParams, operationParams),\n path,\n requestBody: operation.requestBody\n ? resolveRef<OpenApiRequestBody>(document, operation.requestBody)\n : undefined,\n summary: operation.summary,\n tags: operation.tags ?? [],\n });\n }\n }\n\n return routes;\n}\n\nfunction mergeParameters(\n pathLevel: OpenApiParameter[],\n operationLevel: OpenApiParameter[],\n): OpenApiParameter[] {\n const overridden = new Set(\n operationLevel.map((param) => `${param.in}:${param.name}`),\n );\n\n return [\n ...pathLevel.filter(\n (param) => !overridden.has(`${param.in}:${param.name}`),\n ),\n ...operationLevel,\n ];\n}\n\nfunction resolveRef<TValue>(\n document: BundledOpenApiDocument,\n value: OpenApiParameterRef | TValue,\n): TValue {\n if (!value || typeof value !== \"object\" || !(\"$ref\" in value)) {\n return value;\n }\n\n const pointer = value.$ref;\n\n if (!pointer.startsWith(\"#/\")) {\n // Bundling should have already turned every external ref into a local\n // one — if this fires, swagger-parser's output shape has changed.\n throw new Error(`Unexpected external $ref after bundling: ${pointer}`);\n }\n\n // swagger-parser synthesizes these pointers as URI fragments (e.g. a path\n // like \"/pets/{petId}\" becomes \"~1pets~1%7BpetId%7D\"), so each segment\n // needs its \"~1\"/\"~0\" escapes undone *and* percent-decoding, in that order.\n const segments = pointer\n .slice(2)\n .split(\"/\")\n .map((segment) =>\n decodeURIComponent(segment.replaceAll(\"~1\", \"/\").replaceAll(\"~0\", \"~\")),\n );\n\n let node: unknown = document;\n\n for (const segment of segments) {\n node = (node as Record<string, unknown> | undefined)?.[segment];\n }\n\n return node as TValue;\n}\n","import type { JsonSchemaObject } from \"../jsonSchemaAdapter.js\";\nimport type {\n BundledOpenApiDocument,\n HttpRoute,\n OpenApiParameter,\n OpenApiRequestBody,\n OpenApiSchema,\n ParameterLocation,\n} from \"./types.js\";\n\n/**\n * Keys whose values are name-to-schema maps: their child keys are\n * author-chosen names rather than JSON Schema keywords.\n */\nconst SCHEMA_MAP_KEYS = new Set([\n \"$defs\",\n \"definitions\",\n \"dependentSchemas\",\n \"patternProperties\",\n \"properties\",\n]);\n\n/**\n * Keys whose values are arbitrary instance data rather than schemas. A\n * sample payload may well contain a \"$ref\" or \"nullable\" key of its own.\n */\nconst DATA_KEYS = new Set([\"const\", \"default\", \"enum\", \"example\", \"examples\"]);\n\nexport interface FlatSchemaResult {\n flatSchema: JsonSchemaObject;\n parameterMap: Record<string, ParameterMapping>;\n /**\n * Set when the request body's schema is not a flat object (e.g. an array,\n * or a bare non-object `$ref`) — the whole body is exposed as a single\n * property under this key, rather than flattened into individual\n * properties.\n */\n wholeBodyKey?: string;\n}\n\nexport interface ParameterMapping {\n in: \"body\" | ParameterLocation;\n name: string;\n}\n\ntype WalkMode = \"data\" | \"schema\" | \"schemaMap\";\n\n/**\n * Flattens a route's path/query/header/cookie parameters and request body\n * into a single tool input schema.\n *\n * Collision precedence ports the Python implementation's rule\n * (`utilities/openapi/schemas.py:_combine_schemas_and_map_params`): a name\n * that collides across path/query/header/cookie gets suffixed\n * `{name}__{location}`; a request body property with a colliding name always\n * keeps its bare name.\n *\n * `GET` never contributes a request body: `fetch` (and the Fetch spec in\n * general) rejects a body on a GET request, so a tool built from a spec's\n * (legal, if unusual) `GET` + `requestBody` operation would be permanently\n * broken. The request body is simply not flattened into the schema for such\n * a route, rather than surfacing a schema that can never actually be called.\n */\nexport function buildFlatSchema(\n route: HttpRoute,\n sharedDefs: Record<string, OpenApiSchema> | undefined,\n): FlatSchemaResult {\n const byName = new Map<string, OpenApiParameter[]>();\n\n for (const param of route.parameters) {\n const list = byName.get(param.name) ?? [];\n list.push(param);\n byName.set(param.name, list);\n }\n\n const { properties: bodyProperties, wholeBodyKey } = extractBodyProperties(\n route.method === \"get\" ? undefined : route.requestBody,\n );\n\n const properties: Record<string, OpenApiSchema> = {};\n const required: string[] = [];\n const parameterMap: Record<string, ParameterMapping> = {};\n\n for (const [name, occurrences] of byName) {\n const collides = occurrences.length > 1 || bodyProperties.has(name);\n\n for (const param of occurrences) {\n const key = collides ? `${name}__${param.in}` : name;\n\n properties[key] = rewriteComponentRefs(\n param.schema ?? { type: \"string\" },\n );\n parameterMap[key] = { in: param.in, name };\n\n if (param.in === \"path\" || param.required) {\n required.push(key);\n }\n }\n }\n\n for (const [name, { required: isRequired, schema }] of bodyProperties) {\n properties[name] = rewriteComponentRefs(schema);\n parameterMap[name] = { in: \"body\", name };\n\n if (isRequired) {\n required.push(name);\n }\n }\n\n const flatSchema: JsonSchemaObject = {\n additionalProperties: false,\n properties,\n type: \"object\",\n ...(required.length > 0 ? { required } : {}),\n };\n\n // Only the definitions this tool's own schema actually (transitively)\n // references — embedding the whole document's components.schemas into\n // every single tool would multiply the tools/list payload size by the\n // tool count for no benefit.\n const usedDefs = sharedDefs && filterReferencedDefs(properties, sharedDefs);\n\n if (usedDefs) {\n flatSchema.$defs = usedDefs;\n }\n\n return { flatSchema, parameterMap, wholeBodyKey };\n}\n\nexport function buildSharedDefs(\n document: BundledOpenApiDocument,\n): Record<string, OpenApiSchema> | undefined {\n const schemas = document.components?.schemas;\n\n if (!schemas || Object.keys(schemas).length === 0) {\n return undefined;\n }\n\n return rewriteNode(schemas, \"schemaMap\") as Record<string, OpenApiSchema>;\n}\n\n/**\n * Rewrites `$ref`s pointing at `#/components/schemas/...` to `#/$defs/...`,\n * so a per-tool schema that contains one can be handed to AJV standalone,\n * alongside a `$defs` object built from the document's `components.schemas`\n * (see `buildSharedDefs`). Ports the equivalent rewrite from the Python\n * implementation (`utilities/openapi/schemas.py:_replace_ref_with_defs`).\n *\n * Also normalizes OpenAPI 3.0's `nullable` keyword (see `normalizeNullable`),\n * since real specs carry both.\n */\nexport function rewriteComponentRefs<TValue>(value: TValue): TValue {\n return rewriteNode(value, \"schema\") as TValue;\n}\n\nfunction childMode(key: string): WalkMode {\n if (DATA_KEYS.has(key)) {\n return \"data\";\n }\n\n return SCHEMA_MAP_KEYS.has(key) ? \"schemaMap\" : \"schema\";\n}\n\nfunction extractBodyProperties(requestBody: OpenApiRequestBody | undefined): {\n properties: Map<string, { required: boolean; schema: OpenApiSchema }>;\n wholeBodyKey?: string;\n} {\n const properties = new Map<\n string,\n { required: boolean; schema: OpenApiSchema }\n >();\n\n const schema = requestBody?.content?.[\"application/json\"]?.schema;\n\n if (!schema) {\n return { properties };\n }\n\n const schemaProperties = schema.properties as\n | Record<string, OpenApiSchema>\n | undefined;\n\n if (schema.type === \"object\" && schemaProperties) {\n const requiredNames = new Set(\n (schema.required as string[] | undefined) ?? [],\n );\n\n for (const [name, propertySchema] of Object.entries(schemaProperties)) {\n properties.set(name, {\n required: requiredNames.has(name),\n schema: propertySchema,\n });\n }\n\n return { properties };\n }\n\n // Non-object body (array, bare $ref to a scalar/array, etc.) — expose the\n // whole thing as a single \"body\" property rather than flattening it.\n properties.set(\"body\", {\n required: requestBody?.required ?? false,\n schema,\n });\n\n return { properties, wholeBodyKey: \"body\" };\n}\n\n/**\n * Walks a schema fragment for `#/$defs/Name` refs and returns just those\n * definitions (transitively — a referenced def may itself reference\n * others), or `undefined` if none are referenced.\n */\nfunction filterReferencedDefs(\n node: unknown,\n allDefs: Record<string, OpenApiSchema>,\n): Record<string, OpenApiSchema> | undefined {\n const referenced = new Set<string>();\n const stack: unknown[] = [node];\n\n while (stack.length > 0) {\n const current = stack.pop();\n\n if (Array.isArray(current)) {\n stack.push(...current);\n continue;\n }\n\n if (!current || typeof current !== \"object\") {\n continue;\n }\n\n for (const [key, value] of Object.entries(\n current as Record<string, unknown>,\n )) {\n if (\n key === \"$ref\" &&\n typeof value === \"string\" &&\n value.startsWith(\"#/$defs/\")\n ) {\n const name = value.slice(\"#/$defs/\".length);\n\n if (allDefs[name] && !referenced.has(name)) {\n referenced.add(name);\n stack.push(allDefs[name]);\n }\n\n continue;\n }\n\n stack.push(value);\n }\n }\n\n if (referenced.size === 0) {\n return undefined;\n }\n\n return Object.fromEntries(\n [...referenced].map((name) => [name, allDefs[name]]),\n );\n}\n\n/**\n * OpenAPI 3.0's `nullable` keyword only makes sense alongside a sibling\n * `type`, which it widens (`nullable: true` + `type: \"string\"` means\n * \"string or null\") — but it is not itself standard JSON Schema. AJV\n * recognizes the keyword and throws ('\"nullable\" cannot be used without\n * \"type\"') if it finds one with no `type` on the same node, which real\n * specs do produce (e.g. `nullable` sibling to `oneOf`/`allOf`/`$ref`\n * instead of `type`, as in Box's API). Folded into `type` where there is\n * one to widen, dropped otherwise.\n */\nfunction normalizeNullable(\n schema: Record<string, unknown>,\n): Record<string, unknown> {\n if (!(\"nullable\" in schema)) {\n return schema;\n }\n\n const { nullable, type, ...rest } = schema;\n\n if (nullable !== true) {\n return rest;\n }\n\n if (typeof type === \"string\") {\n return { ...rest, type: [type, \"null\"] };\n }\n\n if (Array.isArray(type)) {\n return { ...rest, type: [...new Set([\"null\", ...type])] };\n }\n\n return rest;\n}\n\n/**\n * Walks a schema fragment, distinguishing the three kinds of node it can\n * reach — because only one of them is a schema whose keys are JSON Schema\n * keywords:\n *\n * - `\"schema\"` — a schema object. `$ref`/`nullable` here are keywords.\n * - `\"schemaMap\"` — a name-to-schema map (`properties`, `$defs`, ...). Its\n * keys are author-chosen names, so a property literally named `nullable`\n * or `$ref` is a field, not a keyword, and must survive untouched.\n * - `\"data\"` — arbitrary values (`default`, `enum`, `example`, ...). Not\n * schemas at all; passed through verbatim.\n *\n * Walking every node as a schema (as this originally did) silently deletes\n * a property named `nullable` from the generated tool schema, since\n * `normalizeNullable` cannot tell the keyword from a same-named field.\n */\nfunction rewriteNode(value: unknown, mode: WalkMode): unknown {\n if (mode === \"data\") {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => rewriteNode(item, \"schema\"));\n }\n\n if (!value || typeof value !== \"object\") {\n return value;\n }\n\n const entries = Object.entries(value as Record<string, unknown>).map(\n ([key, entryValue]): [string, unknown] => {\n if (mode === \"schemaMap\") {\n return [key, rewriteNode(entryValue, \"schema\")];\n }\n\n if (\n key === \"$ref\" &&\n typeof entryValue === \"string\" &&\n entryValue.startsWith(\"#/components/schemas/\")\n ) {\n return [key, entryValue.replace(\"#/components/schemas/\", \"#/$defs/\")];\n }\n\n return [key, rewriteNode(entryValue, childMode(key))];\n },\n );\n\n const rewritten = Object.fromEntries(entries) as Record<string, unknown>;\n\n return mode === \"schemaMap\" ? rewritten : normalizeNullable(rewritten);\n}\n","import type {\n FromOpenAPIOptions,\n HttpMethod,\n HttpRoute,\n OperationSummary,\n} from \"./types.js\";\n\n/**\n * If neither `include`/`exclude` nor `maxTools` was given, and a spec still\n * produces more operations than this, `selectRoutes` throws rather than\n * silently generating a wall of tools most clients can't usefully work with.\n */\nexport const DEFAULT_MAX_OPERATIONS = 40;\n\nconst METHOD_PRIORITY: Record<HttpMethod, number> = {\n delete: 4,\n get: 0,\n patch: 3,\n post: 1,\n put: 2,\n};\n\n/**\n * Filters and orders routes into the set that becomes tools.\n *\n * Deprecated operations are excluded by default. Ordering is deterministic\n * (method priority GET→POST→PUT→PATCH→DELETE, then path) so that, combined\n * with `maxTools`, truncation is legible rather than arbitrary.\n */\nexport function selectRoutes(\n routes: HttpRoute[],\n options: Pick<FromOpenAPIOptions, \"exclude\" | \"include\" | \"maxTools\">,\n): HttpRoute[] {\n let selected = routes.filter((route) => !route.deprecated);\n\n if (options.include) {\n const include = options.include;\n selected = selected.filter((route) => include(toSummary(route)));\n }\n\n if (options.exclude) {\n const exclude = options.exclude;\n selected = selected.filter((route) => !exclude(toSummary(route)));\n }\n\n selected = [...selected].sort((a, b) => {\n const byMethod = METHOD_PRIORITY[a.method] - METHOD_PRIORITY[b.method];\n return byMethod !== 0 ? byMethod : a.path.localeCompare(b.path);\n });\n\n const noSelectionGiven = !options.include && !options.exclude;\n\n if (\n noSelectionGiven &&\n options.maxTools === undefined &&\n selected.length > DEFAULT_MAX_OPERATIONS\n ) {\n throw new Error(\n `fromOpenAPI found ${selected.length} operations, which exceeds the default limit of ${DEFAULT_MAX_OPERATIONS}. ` +\n \"This is a deliberate stop, not a bug: turning every operation in a large spec into a tool produces a tool list most MCP clients can't use well. \" +\n \"Pass `include`/`exclude` to choose the operations you actually want, or `maxTools` to raise this limit explicitly.\",\n );\n }\n\n if (options.maxTools !== undefined && selected.length > options.maxTools) {\n throw new Error(\n `fromOpenAPI found ${selected.length} operations, which exceeds maxTools (${options.maxTools}). ` +\n \"Narrow the spec with `include`/`exclude`, or raise `maxTools`.\",\n );\n }\n\n return selected;\n}\n\nfunction toSummary(route: HttpRoute): OperationSummary {\n return {\n deprecated: route.deprecated,\n method: route.method,\n operationId: route.operationId,\n path: route.path,\n tags: route.tags,\n };\n}\n","import type { FromOpenAPIOptions } from \"./types.js\";\n\nimport { FastMCP } from \"../FastMCP.js\";\nimport { jsonSchemaAdapter } from \"../jsonSchemaAdapter.js\";\nimport { loadSpec } from \"./loadSpec.js\";\nimport { generateToolNames } from \"./naming.js\";\nimport { executeRequest } from \"./requestBuilder.js\";\nimport { extractRoutes } from \"./routes.js\";\nimport { buildFlatSchema, buildSharedDefs } from \"./schemas.js\";\nimport { selectRoutes } from \"./selection.js\";\n\n/**\n * Converts an OpenAPI 3.x document into an MCP server, one tool per\n * operation.\n *\n * See docs/openapi.md for the full option reference and known limitations.\n */\nexport async function fromOpenAPI(\n options: FromOpenAPIOptions,\n): Promise<FastMCP> {\n const { document, origin } = await loadSpec(options.spec);\n const routes = extractRoutes(document);\n const selected = selectRoutes(routes, options);\n const names = generateToolNames(selected, options.mcpNames);\n const sharedDefs = buildSharedDefs(document);\n\n const server =\n options.server ??\n new FastMCP({\n name: options.name ?? document.info?.title ?? \"OpenAPI Server\",\n version: options.version ?? \"1.0.0\",\n });\n\n for (const route of selected) {\n const name = names.get(route);\n\n if (!name) {\n continue;\n }\n\n const { flatSchema, parameterMap, wholeBodyKey } = buildFlatSchema(\n route,\n sharedDefs,\n );\n\n server.addTool({\n description:\n route.summary ?? `${route.method.toUpperCase()} ${route.path}`,\n execute: async (args) =>\n executeRequest({\n args: args as Record<string, unknown>,\n baseUrlOverride: options.baseUrl,\n fetchImpl: options.fetch ?? fetch,\n headers: options.headers,\n origin,\n parameterMap,\n route,\n servers: document.servers,\n wholeBodyKey,\n }),\n name,\n parameters: jsonSchemaAdapter(flatSchema),\n });\n }\n\n return server;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/fastmcp/fastmcp/dist/openapi/index.cjs","../../src/openapi/loadSpec.ts","../../src/openapi/naming.ts","../../src/openapi/requestBuilder.ts","../../src/openapi/resourceMapping.ts","../../src/openapi/routes.ts","../../src/openapi/schemas.ts","../../src/openapi/selection.ts","../../src/openapi/fromOpenAPI.ts"],"names":[],"mappings":"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","file":"/home/runner/work/fastmcp/fastmcp/dist/openapi/index.cjs","sourcesContent":[null,"import SwaggerParser from \"@apidevtools/swagger-parser\";\n\nimport type { BundledOpenApiDocument } from \"./types.js\";\n\nexport interface LoadedSpec {\n document: BundledOpenApiDocument;\n /**\n * The spec's own URL, when it was loaded from one. Used to resolve a\n * relative `servers[0].url` against the document's origin.\n */\n origin?: string;\n}\n\n/**\n * Loads and bundles an OpenAPI document, resolving local *and* external\n * `$ref`s (relative paths, absolute URLs, `other.yaml#/fragment`).\n *\n * `spec` is handed to swagger-parser as-is — a URL, file path, or object —\n * rather than being fetched and re-parsed here first. External refs resolve\n * relative to whatever document they were found in, so pre-fetching the\n * entry document and passing its parsed text as an object would resolve\n * every external ref against the wrong base (or none at all).\n */\nexport async function loadSpec(\n spec: Record<string, unknown> | string,\n): Promise<LoadedSpec> {\n const document = (await SwaggerParser.bundle(\n spec as never,\n )) as unknown as BundledOpenApiDocument;\n\n if (!document.openapi?.startsWith(\"3.\")) {\n throw new Error(\n `fromOpenAPI only supports OpenAPI 3.x documents (found ${\n document.openapi ?? document.swagger ?? \"an unrecognized version\"\n }). Swagger 2.0 is not supported.`,\n );\n }\n\n return {\n document,\n origin: typeof spec === \"string\" && isHttpUrl(spec) ? spec : undefined,\n };\n}\n\nfunction isHttpUrl(value: string): boolean {\n return value.startsWith(\"http://\") || value.startsWith(\"https://\");\n}\n","import type { HttpRoute } from \"./types.js\";\n\nconst MAX_NAME_LENGTH = 56;\n// Reserves room for a \"_<n>\" collision suffix so the final name never\n// exceeds MAX_NAME_LENGTH, however many collisions it takes.\nconst MAX_BASE_LENGTH = MAX_NAME_LENGTH - 5;\n\n/**\n * Generates a unique name per route — used both for tools and, when\n * `resources: true`, for the resources/resource templates a `GET` route\n * maps to instead. One pass over the whole selected set keeps names unique\n * regardless of which destination a route ends up at.\n *\n * Ports the Python implementation's naming rule\n * (`server/providers/openapi/provider.py:_generate_default_name`): prefer\n * `mcpNames[operationId]`, then `operationId` (FastAPI-style `__` suffixes\n * stripped), falling back to `summary` or `{method}_{path}`; slugified and\n * capped at 56 characters, with `_2`, `_3`, ... appended on collision.\n *\n * Uniqueness is checked against the final (post-suffix) name, not just the\n * base — otherwise a spec whose own operationIds already look auto-suffixed\n * (e.g. both \"foo\" and \"foo_2\" present) could produce two identically-named\n * tools, one of which `FastMCP.addTool` would silently drop.\n */\nexport function generateNames(\n routes: HttpRoute[],\n mcpNames: Record<string, string> | undefined,\n): Map<HttpRoute, string> {\n const names = new Map<HttpRoute, string>();\n const used = new Set<string>();\n\n for (const route of routes) {\n const base = slugify(baseNameFor(route, mcpNames));\n let candidate = base;\n let suffix = 1;\n\n while (used.has(candidate)) {\n suffix += 1;\n candidate = `${base}_${suffix}`;\n }\n\n used.add(candidate);\n names.set(route, candidate);\n }\n\n return names;\n}\n\nfunction baseNameFor(\n route: HttpRoute,\n mcpNames: Record<string, string> | undefined,\n): string {\n if (route.operationId) {\n return mcpNames?.[route.operationId] ?? route.operationId.split(\"__\")[0];\n }\n\n return route.summary || `${route.method}_${route.path}`;\n}\n\nfunction slugify(value: string): string {\n const slug = value\n .replace(/[^a-zA-Z0-9_]+/g, \"_\")\n .replace(/_+/g, \"_\")\n .replace(/^_|_$/g, \"\")\n .slice(0, MAX_BASE_LENGTH);\n\n return slug || \"operation\";\n}\n","import type { ParameterMapping } from \"./schemas.js\";\nimport type { FromOpenAPIOptions, HttpRoute, OpenApiServer } from \"./types.js\";\n\nimport { UserError } from \"../FastMCP.js\";\n\nexport interface ExecuteRequestOptions {\n args: Record<string, unknown>;\n baseUrlOverride?: string;\n /** How to serialize a request body, if `args` contains any body-mapped values. */\n bodyEncoding?: \"form\" | \"json\";\n fetchImpl: typeof fetch;\n headers?: FromOpenAPIOptions[\"headers\"];\n origin?: string;\n parameterMap: Record<string, ParameterMapping>;\n route: HttpRoute;\n servers: OpenApiServer[] | undefined;\n wholeBodyKey?: string;\n}\n\nexport async function executeRequest(\n options: ExecuteRequestOptions,\n): Promise<string> {\n const baseUrl = resolveBaseUrl(\n options.servers,\n options.origin,\n options.baseUrlOverride,\n );\n\n const pathParams: Record<string, string> = {};\n const query = new URLSearchParams();\n // A plain object keys headers case-sensitively, so a caller-supplied\n // header (e.g. \"Content-Type\") wouldn't be recognized as the same header\n // as one this function sets internally (e.g. \"content-type\") — `Headers`\n // normalizes casing, so `.set()` correctly overrides rather than\n // combining into a comma-joined, malformed value.\n const headers = new Headers(await resolveHeaders(options.headers));\n const bodyProps: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(options.args)) {\n const mapping = options.parameterMap[key];\n\n if (!mapping || value === undefined) {\n continue;\n }\n\n switch (mapping.in) {\n case \"body\":\n bodyProps[mapping.name] = value;\n break;\n case \"cookie\": {\n const existing = headers.get(\"cookie\");\n headers.set(\n \"cookie\",\n existing\n ? `${existing}; ${mapping.name}=${String(value)}`\n : `${mapping.name}=${String(value)}`,\n );\n break;\n }\n case \"header\":\n headers.set(mapping.name, String(value));\n break;\n case \"path\":\n pathParams[mapping.name] = String(value);\n break;\n case \"query\":\n for (const item of Array.isArray(value) ? value : [value]) {\n query.append(mapping.name, String(item));\n }\n break;\n }\n }\n\n let path = options.route.path;\n\n for (const [name, value] of Object.entries(pathParams)) {\n path = path.replace(`{${name}}`, encodeURIComponent(value));\n }\n\n const url = new URL(baseUrl.replace(/\\/$/, \"\") + path);\n url.search = query.toString();\n\n let body: string | undefined;\n\n if (Object.keys(bodyProps).length > 0) {\n const payload = options.wholeBodyKey\n ? bodyProps[options.wholeBodyKey]\n : bodyProps;\n\n if (options.bodyEncoding === \"form\") {\n if (!headers.has(\"content-type\")) {\n headers.set(\"content-type\", \"application/x-www-form-urlencoded\");\n }\n\n body = encodeFormBody(payload);\n } else {\n if (!headers.has(\"content-type\")) {\n headers.set(\"content-type\", \"application/json\");\n }\n\n body = JSON.stringify(payload);\n }\n }\n\n const response = await options.fetchImpl(url.toString(), {\n body,\n headers,\n method: options.route.method.toUpperCase(),\n });\n\n const text = await response.text();\n\n if (!response.ok) {\n throw new UserError(\n `${options.route.method.toUpperCase()} ${path} failed with ${response.status}: ${text.slice(0, 2000)}`,\n );\n }\n\n if (response.headers.get(\"content-type\")?.includes(\"json\")) {\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch {\n return text;\n }\n }\n\n return text;\n}\n\n/**\n * Resolves `servers[0].url` the way a real HTTP client needs it resolved,\n * not just the way a schema validator would accept it: a relative URL (e.g.\n * Petstore's own `\"/api/v3\"`) is joined against the document's own origin,\n * not passed through verbatim.\n */\nexport function resolveBaseUrl(\n servers: OpenApiServer[] | undefined,\n origin: string | undefined,\n overrideUrl: string | undefined,\n): string {\n if (overrideUrl) {\n return overrideUrl.replace(/\\/$/, \"\");\n }\n\n const server = servers?.[0];\n\n if (!server) {\n throw new Error(\n \"The OpenAPI document has no `servers` entry. Pass `baseUrl` to fromOpenAPI() explicitly.\",\n );\n }\n\n let url = server.url;\n\n for (const [name, variable] of Object.entries(server.variables ?? {})) {\n url = url.replaceAll(`{${name}}`, variable.default);\n }\n\n try {\n return new URL(url).toString().replace(/\\/$/, \"\");\n } catch {\n if (!origin) {\n throw new Error(\n `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.`,\n );\n }\n\n return new URL(url, origin).toString().replace(/\\/$/, \"\");\n }\n}\n\n/**\n * Serializes a flattened body payload as `application/x-www-form-urlencoded`.\n * Array values become repeated keys, matching the existing query-parameter\n * convention. A nested object/array *value* is JSON-stringified into a\n * single form value rather than expanded with bracket notation (e.g.\n * Stripe's own `metadata[key]=value` style) — correct for the flat scalar\n * properties that make up the overwhelming majority of real form-encoded\n * APIs (Stripe, Twilio), not a full form-encoding implementation.\n * `URLSearchParams` handles percent-encoding for free.\n */\nfunction encodeFormBody(payload: unknown): string {\n const params = new URLSearchParams();\n\n if (payload && typeof payload === \"object\" && !Array.isArray(payload)) {\n for (const [key, value] of Object.entries(\n payload as Record<string, unknown>,\n )) {\n if (value === undefined) {\n continue;\n }\n\n for (const item of Array.isArray(value) ? value : [value]) {\n params.append(\n key,\n item !== null && typeof item === \"object\"\n ? JSON.stringify(item)\n : String(item),\n );\n }\n }\n }\n\n return params.toString();\n}\n\nasync function resolveHeaders(\n headers: FromOpenAPIOptions[\"headers\"],\n): Promise<Record<string, string>> {\n if (!headers) {\n return {};\n }\n\n return typeof headers === \"function\" ? await headers() : { ...headers };\n}\n","import type { ParameterMapping } from \"./schemas.js\";\nimport type { HttpRoute } from \"./types.js\";\n\nexport type ResourceMapping =\n | {\n args: { name: string; required: boolean }[];\n kind: \"template\";\n uriTemplate: string;\n }\n | { kind: \"resource\"; uri: string };\n\n/**\n * Builds a static resource URI, or a resource template (URI + `arguments`),\n * for an eligible `GET` route — reusing the `parameterMap` and `required`\n * list `buildFlatSchema` already produced for it (path-always-required,\n * collision-suffixed flat keys) rather than re-deriving parameter\n * flattening from scratch.\n *\n * OpenAPI's `{petId}` path-parameter syntax is already valid RFC 6570 simple\n * string expansion, so the route's own path is reused verbatim except where\n * a flat key was collision-suffixed. Query parameters are appended as an\n * RFC 6570 query-expansion segment (`{?a,b}`), which `uri-templates`\n * (already a FastMCP dependency — see its own resource-template dispatch in\n * FastMCP.ts) parses and fills the same way it does path variables.\n */\nexport function buildResourceMapping(\n route: HttpRoute,\n name: string,\n parameterMap: Record<string, ParameterMapping>,\n requiredKeys: string[] | undefined,\n): ResourceMapping {\n const entries = Object.entries(parameterMap);\n\n if (entries.length === 0) {\n return { kind: \"resource\", uri: `openapi://${name}${route.path}` };\n }\n\n const required = new Set(requiredKeys ?? []);\n const args: { name: string; required: boolean }[] = [];\n const queryKeys: string[] = [];\n let path = route.path;\n\n for (const [flatKey, mapping] of entries) {\n if (mapping.in === \"path\") {\n if (flatKey !== mapping.name) {\n path = path.replace(`{${mapping.name}}`, `{${flatKey}}`);\n }\n } else {\n queryKeys.push(flatKey);\n }\n\n args.push({ name: flatKey, required: required.has(flatKey) });\n }\n\n const uriTemplate =\n `openapi://${name}${path}` +\n (queryKeys.length > 0 ? `{?${queryKeys.join(\",\")}}` : \"\");\n\n return { args, kind: \"template\", uriTemplate };\n}\n\n/**\n * Whether a `GET` route can become an MCP resource/resource template\n * instead of a tool, when `resources: true` is passed to `fromOpenAPI`. See\n * docs/openapi.md \"GET → resources\" for the reasoning behind each carve-out:\n *\n * - `header`/`cookie` parameters can't be expressed in a resource URI, and\n * MCP resource reads have no per-call side channel for them.\n * - An array-typed path/query parameter can't be represented consistently\n * between OpenAPI's query serialization (repeated keys) and RFC 6570's\n * array representation (comma-joined or `*`-exploded).\n *\n * A route failing either check falls through to the existing tool path —\n * this only ever *removes* operations from the tool list in favor of a\n * resource, never breaks one.\n */\nexport function isEligibleForResource(route: HttpRoute): boolean {\n return route.parameters.every((param) => {\n if (param.in === \"header\" || param.in === \"cookie\") {\n return false;\n }\n\n return param.schema?.type !== \"array\";\n });\n}\n","import type {\n BundledOpenApiDocument,\n HttpMethod,\n HttpRoute,\n OpenApiParameter,\n OpenApiParameterRef,\n OpenApiRequestBody,\n} from \"./types.js\";\n\nconst HTTP_METHODS: HttpMethod[] = [\"get\", \"put\", \"post\", \"delete\", \"patch\"];\n\n/**\n * Walks a bundled document's `paths` into a flat list of routes, resolving\n * any structural (non-schema) `$ref`s on parameters and request bodies —\n * e.g. `#/components/parameters/Limit` — against the same document.\n *\n * Bundling (see `loadSpec.ts`) guarantees every remaining `$ref` here is\n * local, so a plain JSON-pointer lookup is enough.\n */\nexport function extractRoutes(document: BundledOpenApiDocument): HttpRoute[] {\n const routes: HttpRoute[] = [];\n\n for (const [path, pathItem] of Object.entries(document.paths ?? {})) {\n const pathLevelParams = (pathItem.parameters ?? []).map((param) =>\n resolveRef<OpenApiParameter>(document, param),\n );\n\n for (const method of HTTP_METHODS) {\n const operation = pathItem[method];\n\n if (!operation) {\n continue;\n }\n\n const operationParams = (operation.parameters ?? []).map((param) =>\n resolveRef<OpenApiParameter>(document, param),\n );\n\n routes.push({\n deprecated: operation.deprecated ?? false,\n method,\n operationId: operation.operationId,\n parameters: mergeParameters(pathLevelParams, operationParams),\n path,\n requestBody: operation.requestBody\n ? resolveRef<OpenApiRequestBody>(document, operation.requestBody)\n : undefined,\n summary: operation.summary,\n tags: operation.tags ?? [],\n });\n }\n }\n\n return routes;\n}\n\nfunction mergeParameters(\n pathLevel: OpenApiParameter[],\n operationLevel: OpenApiParameter[],\n): OpenApiParameter[] {\n const overridden = new Set(\n operationLevel.map((param) => `${param.in}:${param.name}`),\n );\n\n return [\n ...pathLevel.filter(\n (param) => !overridden.has(`${param.in}:${param.name}`),\n ),\n ...operationLevel,\n ];\n}\n\nfunction resolveRef<TValue>(\n document: BundledOpenApiDocument,\n value: OpenApiParameterRef | TValue,\n): TValue {\n if (!value || typeof value !== \"object\" || !(\"$ref\" in value)) {\n return value;\n }\n\n const pointer = value.$ref;\n\n if (!pointer.startsWith(\"#/\")) {\n // Bundling should have already turned every external ref into a local\n // one — if this fires, swagger-parser's output shape has changed.\n throw new Error(`Unexpected external $ref after bundling: ${pointer}`);\n }\n\n // swagger-parser synthesizes these pointers as URI fragments (e.g. a path\n // like \"/pets/{petId}\" becomes \"~1pets~1%7BpetId%7D\"), so each segment\n // needs its \"~1\"/\"~0\" escapes undone *and* percent-decoding, in that order.\n const segments = pointer\n .slice(2)\n .split(\"/\")\n .map((segment) =>\n decodeURIComponent(segment.replaceAll(\"~1\", \"/\").replaceAll(\"~0\", \"~\")),\n );\n\n let node: unknown = document;\n\n for (const segment of segments) {\n node = (node as Record<string, unknown> | undefined)?.[segment];\n }\n\n return node as TValue;\n}\n","import type { JsonSchemaObject } from \"../jsonSchemaAdapter.js\";\nimport type {\n BundledOpenApiDocument,\n HttpRoute,\n OpenApiParameter,\n OpenApiRequestBody,\n OpenApiSchema,\n ParameterLocation,\n} from \"./types.js\";\n\n/**\n * Keys whose values are name-to-schema maps: their child keys are\n * author-chosen names rather than JSON Schema keywords.\n */\nconst SCHEMA_MAP_KEYS = new Set([\n \"$defs\",\n \"definitions\",\n \"dependentSchemas\",\n \"patternProperties\",\n \"properties\",\n]);\n\n/**\n * Keys whose values are arbitrary instance data rather than schemas. A\n * sample payload may well contain a \"$ref\" or \"nullable\" key of its own.\n */\nconst DATA_KEYS = new Set([\"const\", \"default\", \"enum\", \"example\", \"examples\"]);\n\n/**\n * Request body content types this module knows how to flatten and encode.\n * `application/json` wins when a route declares both.\n */\nexport const SUPPORTED_BODY_CONTENT_TYPES = [\n \"application/json\",\n \"application/x-www-form-urlencoded\",\n] as const;\n\nexport interface FlatSchemaResult {\n /** How the request body (if any properties were extracted) must be serialized. */\n bodyEncoding?: \"form\" | \"json\";\n flatSchema: JsonSchemaObject;\n parameterMap: Record<string, ParameterMapping>;\n /**\n * Set when `route.requestBody` declares a body this module can't carry:\n * either only in content type(s) it doesn't support (e.g.\n * `multipart/form-data`, `application/json-patch+json`,\n * `application/octet-stream`), or as `application/x-www-form-urlencoded`\n * with a schema that isn't a flat object, which form encoding can't\n * represent. Holds the content type the body was declared in. The caller\n * should not turn this route into a tool with a payload it can never\n * carry — see `fromOpenAPI.ts`.\n */\n unsupportedBodyContentType?: string;\n /**\n * Set when the request body's schema is not a flat object (e.g. an array,\n * or a bare non-object `$ref`) — the whole body is exposed as a single\n * property under this key, rather than flattened into individual\n * properties.\n */\n wholeBodyKey?: string;\n}\n\nexport interface ParameterMapping {\n in: \"body\" | ParameterLocation;\n name: string;\n}\n\ntype WalkMode = \"data\" | \"schema\" | \"schemaMap\";\n\n/**\n * Flattens a route's path/query/header/cookie parameters and request body\n * into a single tool input schema.\n *\n * Collision precedence ports the Python implementation's rule\n * (`utilities/openapi/schemas.py:_combine_schemas_and_map_params`): a name\n * that collides across path/query/header/cookie gets suffixed\n * `{name}__{location}`; a request body property with a colliding name always\n * keeps its bare name.\n *\n * `GET` never contributes a request body: `fetch` (and the Fetch spec in\n * general) rejects a body on a GET request, so a tool built from a spec's\n * (legal, if unusual) `GET` + `requestBody` operation would be permanently\n * broken. The request body is simply not flattened into the schema for such\n * a route, rather than surfacing a schema that can never actually be called.\n */\nexport function buildFlatSchema(\n route: HttpRoute,\n sharedDefs: Record<string, OpenApiSchema> | undefined,\n): FlatSchemaResult {\n const byName = new Map<string, OpenApiParameter[]>();\n\n for (const param of route.parameters) {\n const list = byName.get(param.name) ?? [];\n list.push(param);\n byName.set(param.name, list);\n }\n\n const {\n bodyEncoding,\n properties: bodyProperties,\n unsupportedBodyContentType,\n wholeBodyKey,\n } = extractBodyProperties(\n route.method === \"get\" ? undefined : route.requestBody,\n sharedDefs,\n );\n\n const properties: Record<string, OpenApiSchema> = {};\n const required: string[] = [];\n const parameterMap: Record<string, ParameterMapping> = {};\n\n for (const [name, occurrences] of byName) {\n const collides = occurrences.length > 1 || bodyProperties.has(name);\n\n for (const param of occurrences) {\n const key = collides ? `${name}__${param.in}` : name;\n\n properties[key] = rewriteComponentRefs(\n param.schema ?? { type: \"string\" },\n );\n parameterMap[key] = { in: param.in, name };\n\n if (param.in === \"path\" || param.required) {\n required.push(key);\n }\n }\n }\n\n for (const [name, { required: isRequired, schema }] of bodyProperties) {\n properties[name] = rewriteComponentRefs(schema);\n parameterMap[name] = { in: \"body\", name };\n\n if (isRequired) {\n required.push(name);\n }\n }\n\n const flatSchema: JsonSchemaObject = {\n additionalProperties: false,\n properties,\n type: \"object\",\n ...(required.length > 0 ? { required } : {}),\n };\n\n // Only the definitions this tool's own schema actually (transitively)\n // references — embedding the whole document's components.schemas into\n // every single tool would multiply the tools/list payload size by the\n // tool count for no benefit.\n const usedDefs = sharedDefs && filterReferencedDefs(properties, sharedDefs);\n\n if (usedDefs) {\n flatSchema.$defs = usedDefs;\n }\n\n return {\n bodyEncoding,\n flatSchema,\n parameterMap,\n unsupportedBodyContentType,\n wholeBodyKey,\n };\n}\n\nexport function buildSharedDefs(\n document: BundledOpenApiDocument,\n): Record<string, OpenApiSchema> | undefined {\n const schemas = document.components?.schemas;\n\n if (!schemas || Object.keys(schemas).length === 0) {\n return undefined;\n }\n\n return rewriteNode(schemas, \"schemaMap\") as Record<string, OpenApiSchema>;\n}\n\n/**\n * Rewrites `$ref`s pointing at `#/components/schemas/...` to `#/$defs/...`,\n * so a per-tool schema that contains one can be handed to AJV standalone,\n * alongside a `$defs` object built from the document's `components.schemas`\n * (see `buildSharedDefs`). Ports the equivalent rewrite from the Python\n * implementation (`utilities/openapi/schemas.py:_replace_ref_with_defs`).\n *\n * Also normalizes OpenAPI 3.0's `nullable` keyword (see `normalizeNullable`),\n * since real specs carry both.\n */\nexport function rewriteComponentRefs<TValue>(value: TValue): TValue {\n return rewriteNode(value, \"schema\") as TValue;\n}\n\nfunction childMode(key: string): WalkMode {\n if (DATA_KEYS.has(key)) {\n return \"data\";\n }\n\n return SCHEMA_MAP_KEYS.has(key) ? \"schemaMap\" : \"schema\";\n}\n\nfunction componentSchemaName(ref: string): string | undefined {\n for (const prefix of [\"#/components/schemas/\", \"#/$defs/\"]) {\n if (ref.startsWith(prefix)) {\n return ref.slice(prefix.length);\n }\n }\n\n return undefined;\n}\n\n/**\n * Picks the request body's content type and flattens its schema.\n *\n * `application/json` wins if a route declares both it and\n * `application/x-www-form-urlencoded` (a fixed preference, not declaration\n * order — the latter isn't a reliable signal). A route whose body is only\n * declared under a content type this module doesn't handle at all (e.g.\n * `multipart/form-data`) reports `unsupportedBodyContentType` instead of\n * silently returning an empty property map — that emptiness is exactly what\n * a real Stripe/Twilio operation (both form-urlencoded-only) looked like\n * before this function read anything but JSON, and it produced a tool with\n * no way to carry its actual payload. A bare `content: {}` (no content\n * types at all) still means \"no body,\" not \"unsupported\" — and so does a\n * supported content type with no `schema` at all (a legal, if unusual,\n * \"any JSON body\" declaration): the content type itself is fine, there's\n * just nothing to flatten.\n *\n * A form-urlencoded body is very often declared as a bare `$ref` to a\n * component schema rather than inline — FastAPI emits\n * `#/components/schemas/Body_<operation>` for every form endpoint, and\n * Box's OAuth token/refresh/revoke operations do the same. The document is\n * bundled, not dereferenced (see `loadSpec.ts`), so that `$ref` is resolved\n * here against `sharedDefs` before deciding whether the body is a flat\n * object. Only the form path needs this: a JSON body that isn't a flat\n * object falls back to a single whole-body property, which a `$ref`\n * satisfies as-is.\n *\n * A non-object body (array, `$ref` to a scalar/array, etc.) can't be\n * form-urlencoded at all — form encoding is inherently flat key/value pairs\n * — so that combination is also reported as unsupported, rather than\n * `encodeFormBody` (requestBuilder.ts) silently sending an empty body for\n * data it has no way to represent.\n */\nfunction extractBodyProperties(\n requestBody: OpenApiRequestBody | undefined,\n sharedDefs: Record<string, OpenApiSchema> | undefined,\n): {\n bodyEncoding?: \"form\" | \"json\";\n properties: Map<string, { required: boolean; schema: OpenApiSchema }>;\n unsupportedBodyContentType?: string;\n wholeBodyKey?: string;\n} {\n const properties = new Map<\n string,\n { required: boolean; schema: OpenApiSchema }\n >();\n\n const content = requestBody?.content;\n\n if (!content) {\n return { properties };\n }\n\n const hasJson = \"application/json\" in content;\n const hasForm = \"application/x-www-form-urlencoded\" in content;\n\n if (!hasJson && !hasForm) {\n const contentTypes = Object.keys(content);\n\n return contentTypes.length > 0\n ? { properties, unsupportedBodyContentType: contentTypes[0] }\n : { properties };\n }\n\n const bodyEncoding: \"form\" | \"json\" = hasJson ? \"json\" : \"form\";\n const declaredSchema = hasJson\n ? content[\"application/json\"]?.schema\n : content[\"application/x-www-form-urlencoded\"]?.schema;\n\n if (!declaredSchema) {\n // The content type is declared and supported; it just has no schema\n // (an unconstrained body) — nothing to flatten, but not unsupported.\n return { bodyEncoding, properties };\n }\n\n const schema =\n bodyEncoding === \"form\"\n ? resolveComponentRef(declaredSchema, sharedDefs)\n : declaredSchema;\n\n const schemaProperties = schema.properties as\n | Record<string, OpenApiSchema>\n | undefined;\n\n if (schema.type === \"object\" && schemaProperties) {\n const requiredNames = new Set(\n (schema.required as string[] | undefined) ?? [],\n );\n\n for (const [name, propertySchema] of Object.entries(schemaProperties)) {\n properties.set(name, {\n required: requiredNames.has(name),\n schema: propertySchema,\n });\n }\n\n return { bodyEncoding, properties };\n }\n\n if (bodyEncoding === \"form\") {\n return {\n properties,\n unsupportedBodyContentType: \"application/x-www-form-urlencoded\",\n };\n }\n\n // Non-object JSON body (array, bare $ref to a scalar/array, etc.) — expose\n // the whole thing as a single \"body\" property rather than flattening it.\n properties.set(\"body\", {\n required: requestBody?.required ?? false,\n schema,\n });\n\n return { bodyEncoding, properties, wholeBodyKey: \"body\" };\n}\n\n/**\n * Walks a schema fragment for `#/$defs/Name` refs and returns just those\n * definitions (transitively — a referenced def may itself reference\n * others), or `undefined` if none are referenced.\n */\nfunction filterReferencedDefs(\n node: unknown,\n allDefs: Record<string, OpenApiSchema>,\n): Record<string, OpenApiSchema> | undefined {\n const referenced = new Set<string>();\n const stack: unknown[] = [node];\n\n while (stack.length > 0) {\n const current = stack.pop();\n\n if (Array.isArray(current)) {\n stack.push(...current);\n continue;\n }\n\n if (!current || typeof current !== \"object\") {\n continue;\n }\n\n for (const [key, value] of Object.entries(\n current as Record<string, unknown>,\n )) {\n if (\n key === \"$ref\" &&\n typeof value === \"string\" &&\n value.startsWith(\"#/$defs/\")\n ) {\n const name = value.slice(\"#/$defs/\".length);\n\n if (allDefs[name] && !referenced.has(name)) {\n referenced.add(name);\n stack.push(allDefs[name]);\n }\n\n continue;\n }\n\n stack.push(value);\n }\n }\n\n if (referenced.size === 0) {\n return undefined;\n }\n\n return Object.fromEntries(\n [...referenced].map((name) => [name, allDefs[name]]),\n );\n}\n\n/**\n * OpenAPI 3.0's `nullable` keyword only makes sense alongside a sibling\n * `type`, which it widens (`nullable: true` + `type: \"string\"` means\n * \"string or null\") — but it is not itself standard JSON Schema. AJV\n * recognizes the keyword and throws ('\"nullable\" cannot be used without\n * \"type\"') if it finds one with no `type` on the same node, which real\n * specs do produce (e.g. `nullable` sibling to `oneOf`/`allOf`/`$ref`\n * instead of `type`, as in Box's API). Folded into `type` where there is\n * one to widen, dropped otherwise.\n */\nfunction normalizeNullable(\n schema: Record<string, unknown>,\n): Record<string, unknown> {\n if (!(\"nullable\" in schema)) {\n return schema;\n }\n\n const { nullable, type, ...rest } = schema;\n\n if (nullable !== true) {\n return rest;\n }\n\n if (typeof type === \"string\") {\n return { ...rest, type: [type, \"null\"] };\n }\n\n if (Array.isArray(type)) {\n return { ...rest, type: [...new Set([\"null\", ...type])] };\n }\n\n return rest;\n}\n\n/**\n * Follows a bare `$ref` into `components.schemas` — or its rewritten\n * `#/$defs/` form, which is what `sharedDefs` entries themselves carry —\n * until it reaches a concrete schema. A dangling or cyclic reference is\n * returned as-is rather than failing the whole conversion.\n */\nfunction resolveComponentRef(\n schema: OpenApiSchema,\n sharedDefs: Record<string, OpenApiSchema> | undefined,\n): OpenApiSchema {\n const seen = new Set<string>();\n let current = schema;\n let ref = current.$ref;\n\n while (typeof ref === \"string\") {\n const name = componentSchemaName(ref);\n const target = name === undefined ? undefined : sharedDefs?.[name];\n\n if (name === undefined || target === undefined || seen.has(name)) {\n break;\n }\n\n seen.add(name);\n current = target;\n ref = current.$ref;\n }\n\n return current;\n}\n\n/**\n * Walks a schema fragment, distinguishing the three kinds of node it can\n * reach — because only one of them is a schema whose keys are JSON Schema\n * keywords:\n *\n * - `\"schema\"` — a schema object. `$ref`/`nullable` here are keywords.\n * - `\"schemaMap\"` — a name-to-schema map (`properties`, `$defs`, ...). Its\n * keys are author-chosen names, so a property literally named `nullable`\n * or `$ref` is a field, not a keyword, and must survive untouched.\n * - `\"data\"` — arbitrary values (`default`, `enum`, `example`, ...). Not\n * schemas at all; passed through verbatim.\n *\n * Walking every node as a schema (as this originally did) silently deletes\n * a property named `nullable` from the generated tool schema, since\n * `normalizeNullable` cannot tell the keyword from a same-named field.\n */\nfunction rewriteNode(value: unknown, mode: WalkMode): unknown {\n if (mode === \"data\") {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => rewriteNode(item, \"schema\"));\n }\n\n if (!value || typeof value !== \"object\") {\n return value;\n }\n\n const entries = Object.entries(value as Record<string, unknown>).map(\n ([key, entryValue]): [string, unknown] => {\n if (mode === \"schemaMap\") {\n return [key, rewriteNode(entryValue, \"schema\")];\n }\n\n if (\n key === \"$ref\" &&\n typeof entryValue === \"string\" &&\n entryValue.startsWith(\"#/components/schemas/\")\n ) {\n return [key, entryValue.replace(\"#/components/schemas/\", \"#/$defs/\")];\n }\n\n return [key, rewriteNode(entryValue, childMode(key))];\n },\n );\n\n const rewritten = Object.fromEntries(entries) as Record<string, unknown>;\n\n return mode === \"schemaMap\" ? rewritten : normalizeNullable(rewritten);\n}\n","import type {\n FromOpenAPIOptions,\n HttpMethod,\n HttpRoute,\n OperationSummary,\n} from \"./types.js\";\n\n/**\n * If neither `include`/`exclude` nor `maxTools` was given, and a spec still\n * produces more operations than this, `selectRoutes` throws rather than\n * silently generating a wall of tools most clients can't usefully work with.\n */\nexport const DEFAULT_MAX_OPERATIONS = 40;\n\nconst METHOD_PRIORITY: Record<HttpMethod, number> = {\n delete: 4,\n get: 0,\n patch: 3,\n post: 1,\n put: 2,\n};\n\n/**\n * Filters and orders routes into the set that becomes tools.\n *\n * Deprecated operations are excluded by default. Ordering is deterministic\n * (method priority GET→POST→PUT→PATCH→DELETE, then path) so that, combined\n * with `maxTools`, truncation is legible rather than arbitrary.\n */\nexport function selectRoutes(\n routes: HttpRoute[],\n options: Pick<FromOpenAPIOptions, \"exclude\" | \"include\" | \"maxTools\">,\n): HttpRoute[] {\n let selected = routes.filter((route) => !route.deprecated);\n\n if (options.include) {\n const include = options.include;\n selected = selected.filter((route) => include(toSummary(route)));\n }\n\n if (options.exclude) {\n const exclude = options.exclude;\n selected = selected.filter((route) => !exclude(toSummary(route)));\n }\n\n selected = [...selected].sort((a, b) => {\n const byMethod = METHOD_PRIORITY[a.method] - METHOD_PRIORITY[b.method];\n return byMethod !== 0 ? byMethod : a.path.localeCompare(b.path);\n });\n\n const noSelectionGiven = !options.include && !options.exclude;\n\n if (\n noSelectionGiven &&\n options.maxTools === undefined &&\n selected.length > DEFAULT_MAX_OPERATIONS\n ) {\n throw new Error(\n `fromOpenAPI found ${selected.length} operations, which exceeds the default limit of ${DEFAULT_MAX_OPERATIONS}. ` +\n \"This is a deliberate stop, not a bug: turning every operation in a large spec into a tool produces a tool list most MCP clients can't use well. \" +\n \"Pass `include`/`exclude` to choose the operations you actually want, or `maxTools` to raise this limit explicitly.\",\n );\n }\n\n if (options.maxTools !== undefined && selected.length > options.maxTools) {\n throw new Error(\n `fromOpenAPI found ${selected.length} operations, which exceeds maxTools (${options.maxTools}). ` +\n \"Narrow the spec with `include`/`exclude`, or raise `maxTools`.\",\n );\n }\n\n return selected;\n}\n\nfunction toSummary(route: HttpRoute): OperationSummary {\n return {\n deprecated: route.deprecated,\n method: route.method,\n operationId: route.operationId,\n path: route.path,\n tags: route.tags,\n };\n}\n","import type { ResourceResult } from \"../FastMCP.js\";\nimport type { ParameterMapping } from \"./schemas.js\";\nimport type { HttpRoute } from \"./types.js\";\nimport type { FromOpenAPIOptions } from \"./types.js\";\n\nimport { FastMCP } from \"../FastMCP.js\";\nimport { jsonSchemaAdapter } from \"../jsonSchemaAdapter.js\";\nimport { loadSpec } from \"./loadSpec.js\";\nimport { generateNames } from \"./naming.js\";\nimport { executeRequest } from \"./requestBuilder.js\";\nimport {\n buildResourceMapping,\n isEligibleForResource,\n} from \"./resourceMapping.js\";\nimport { extractRoutes } from \"./routes.js\";\nimport { buildFlatSchema, buildSharedDefs } from \"./schemas.js\";\nimport { selectRoutes } from \"./selection.js\";\n\n/**\n * Converts an OpenAPI 3.x document into an MCP server, one tool (or, with\n * `resources: true`, resource/resource template for an eligible `GET`) per\n * operation.\n *\n * See docs/openapi.md for the full option reference and known limitations.\n */\nexport async function fromOpenAPI(\n options: FromOpenAPIOptions,\n): Promise<FastMCP> {\n const { document, origin } = await loadSpec(options.spec);\n const routes = extractRoutes(document);\n const selected = selectRoutes(routes, options);\n const names = generateNames(selected, options.mcpNames);\n const sharedDefs = buildSharedDefs(document);\n\n const server =\n options.server ??\n new FastMCP({\n name: options.name ?? document.info?.title ?? \"OpenAPI Server\",\n version: options.version ?? \"1.0.0\",\n });\n\n const skippedOperations: {\n contentType: string;\n method: string;\n path: string;\n }[] = [];\n\n for (const route of selected) {\n const name = names.get(route);\n\n if (!name) {\n continue;\n }\n\n const {\n bodyEncoding,\n flatSchema,\n parameterMap,\n unsupportedBodyContentType,\n wholeBodyKey,\n } = buildFlatSchema(route, sharedDefs);\n\n const execOptions = {\n baseUrlOverride: options.baseUrl,\n fetchImpl: options.fetch ?? fetch,\n headers: options.headers,\n origin,\n parameterMap,\n route,\n servers: document.servers,\n };\n\n if (\n options.resources &&\n route.method === \"get\" &&\n isEligibleForResource(route)\n ) {\n registerResource(\n server,\n route,\n name,\n parameterMap,\n flatSchema.required,\n execOptions,\n );\n continue;\n }\n\n if (unsupportedBodyContentType) {\n skippedOperations.push({\n contentType: unsupportedBodyContentType,\n method: route.method,\n path: route.path,\n });\n continue;\n }\n\n server.addTool({\n description:\n route.summary ?? `${route.method.toUpperCase()} ${route.path}`,\n execute: async (args) =>\n executeRequest({\n ...execOptions,\n args: args as Record<string, unknown>,\n bodyEncoding,\n wholeBodyKey,\n }),\n name,\n parameters: jsonSchemaAdapter(flatSchema),\n });\n }\n\n if (skippedOperations.length > 0) {\n console.warn(\n \"fromOpenAPI: skipped \" +\n `${skippedOperations.length} operation(s) whose request body can't be turned into tool parameters ` +\n \"(supported: application/json, or application/x-www-form-urlencoded with a flat object schema): \" +\n skippedOperations\n .map(\n (op) => `${op.method.toUpperCase()} ${op.path} (${op.contentType})`,\n )\n .join(\", \"),\n );\n }\n\n return server;\n}\n\nfunction registerResource(\n server: FastMCP,\n route: HttpRoute,\n name: string,\n parameterMap: Record<string, ParameterMapping>,\n requiredKeys: string[] | undefined,\n execOptions: Omit<\n Parameters<typeof executeRequest>[0],\n \"args\" | \"bodyEncoding\" | \"wholeBodyKey\"\n >,\n): void {\n const mapping = buildResourceMapping(route, name, parameterMap, requiredKeys);\n const description = route.summary ?? `GET ${route.path}`;\n\n if (mapping.kind === \"resource\") {\n server.addResource({\n description,\n load: async () =>\n wrapAsResourceResult(\n await executeRequest({ ...execOptions, args: {} }),\n ),\n name,\n uri: mapping.uri,\n });\n\n return;\n }\n\n server.addResourceTemplate({\n arguments: mapping.args,\n description,\n load: async (args) =>\n wrapAsResourceResult(\n await executeRequest({\n ...execOptions,\n args: args as Record<string, unknown>,\n }),\n ),\n name,\n uriTemplate: mapping.uriTemplate,\n });\n}\n\nfunction wrapAsResourceResult(text: string): ResourceResult {\n try {\n JSON.parse(text);\n return { mimeType: \"application/json\", text };\n } catch {\n return { mimeType: \"text/plain\", text };\n }\n}\n"]}
|
package/dist/openapi/index.d.cts
CHANGED
|
@@ -73,6 +73,15 @@ interface FromOpenAPIOptions {
|
|
|
73
73
|
* @default the spec's `info.title`, or "OpenAPI Server"
|
|
74
74
|
*/
|
|
75
75
|
name?: string;
|
|
76
|
+
/**
|
|
77
|
+
* When `true`, an eligible `GET` operation becomes an MCP resource (no
|
|
78
|
+
* path/query parameters) or resource template (path and/or query
|
|
79
|
+
* parameters) instead of a tool. A `GET` with any `header`/`cookie`
|
|
80
|
+
* parameter, or any array-typed path/query parameter, stays a tool
|
|
81
|
+
* regardless — see docs/openapi.md "GET → resources".
|
|
82
|
+
* @default false
|
|
83
|
+
*/
|
|
84
|
+
resources?: boolean;
|
|
76
85
|
/**
|
|
77
86
|
* An existing `FastMCP` server to register the generated tools onto,
|
|
78
87
|
* instead of creating a new one.
|
|
@@ -161,7 +170,8 @@ type RawPathItem = {
|
|
|
161
170
|
} & Partial<Record<HttpMethod, RawOperation>>;
|
|
162
171
|
|
|
163
172
|
/**
|
|
164
|
-
* Converts an OpenAPI 3.x document into an MCP server, one tool
|
|
173
|
+
* Converts an OpenAPI 3.x document into an MCP server, one tool (or, with
|
|
174
|
+
* `resources: true`, resource/resource template for an eligible `GET`) per
|
|
165
175
|
* operation.
|
|
166
176
|
*
|
|
167
177
|
* See docs/openapi.md for the full option reference and known limitations.
|
package/dist/openapi/index.d.ts
CHANGED
|
@@ -73,6 +73,15 @@ interface FromOpenAPIOptions {
|
|
|
73
73
|
* @default the spec's `info.title`, or "OpenAPI Server"
|
|
74
74
|
*/
|
|
75
75
|
name?: string;
|
|
76
|
+
/**
|
|
77
|
+
* When `true`, an eligible `GET` operation becomes an MCP resource (no
|
|
78
|
+
* path/query parameters) or resource template (path and/or query
|
|
79
|
+
* parameters) instead of a tool. A `GET` with any `header`/`cookie`
|
|
80
|
+
* parameter, or any array-typed path/query parameter, stays a tool
|
|
81
|
+
* regardless — see docs/openapi.md "GET → resources".
|
|
82
|
+
* @default false
|
|
83
|
+
*/
|
|
84
|
+
resources?: boolean;
|
|
76
85
|
/**
|
|
77
86
|
* An existing `FastMCP` server to register the generated tools onto,
|
|
78
87
|
* instead of creating a new one.
|
|
@@ -161,7 +170,8 @@ type RawPathItem = {
|
|
|
161
170
|
} & Partial<Record<HttpMethod, RawOperation>>;
|
|
162
171
|
|
|
163
172
|
/**
|
|
164
|
-
* Converts an OpenAPI 3.x document into an MCP server, one tool
|
|
173
|
+
* Converts an OpenAPI 3.x document into an MCP server, one tool (or, with
|
|
174
|
+
* `resources: true`, resource/resource template for an eligible `GET`) per
|
|
165
175
|
* operation.
|
|
166
176
|
*
|
|
167
177
|
* See docs/openapi.md for the full option reference and known limitations.
|
package/dist/openapi/index.js
CHANGED
|
@@ -28,7 +28,7 @@ function isHttpUrl(value) {
|
|
|
28
28
|
// src/openapi/naming.ts
|
|
29
29
|
var MAX_NAME_LENGTH = 56;
|
|
30
30
|
var MAX_BASE_LENGTH = MAX_NAME_LENGTH - 5;
|
|
31
|
-
function
|
|
31
|
+
function generateNames(routes, mcpNames) {
|
|
32
32
|
const names = /* @__PURE__ */ new Map();
|
|
33
33
|
const used = /* @__PURE__ */ new Set();
|
|
34
34
|
for (const route of routes) {
|
|
@@ -104,12 +104,18 @@ async function executeRequest(options) {
|
|
|
104
104
|
url.search = query.toString();
|
|
105
105
|
let body;
|
|
106
106
|
if (Object.keys(bodyProps).length > 0) {
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
const payload = options.wholeBodyKey ? bodyProps[options.wholeBodyKey] : bodyProps;
|
|
108
|
+
if (options.bodyEncoding === "form") {
|
|
109
|
+
if (!headers.has("content-type")) {
|
|
110
|
+
headers.set("content-type", "application/x-www-form-urlencoded");
|
|
111
|
+
}
|
|
112
|
+
body = encodeFormBody(payload);
|
|
113
|
+
} else {
|
|
114
|
+
if (!headers.has("content-type")) {
|
|
115
|
+
headers.set("content-type", "application/json");
|
|
116
|
+
}
|
|
117
|
+
body = JSON.stringify(payload);
|
|
109
118
|
}
|
|
110
|
-
body = JSON.stringify(
|
|
111
|
-
options.wholeBodyKey ? bodyProps[options.wholeBodyKey] : bodyProps
|
|
112
|
-
);
|
|
113
119
|
}
|
|
114
120
|
const response = await options.fetchImpl(url.toString(), {
|
|
115
121
|
body,
|
|
@@ -156,6 +162,25 @@ function resolveBaseUrl(servers, origin, overrideUrl) {
|
|
|
156
162
|
return new URL(url, origin).toString().replace(/\/$/, "");
|
|
157
163
|
}
|
|
158
164
|
}
|
|
165
|
+
function encodeFormBody(payload) {
|
|
166
|
+
const params = new URLSearchParams();
|
|
167
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
168
|
+
for (const [key, value] of Object.entries(
|
|
169
|
+
payload
|
|
170
|
+
)) {
|
|
171
|
+
if (value === void 0) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
for (const item of Array.isArray(value) ? value : [value]) {
|
|
175
|
+
params.append(
|
|
176
|
+
key,
|
|
177
|
+
item !== null && typeof item === "object" ? JSON.stringify(item) : String(item)
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return params.toString();
|
|
183
|
+
}
|
|
159
184
|
async function resolveHeaders(headers) {
|
|
160
185
|
if (!headers) {
|
|
161
186
|
return {};
|
|
@@ -163,6 +188,38 @@ async function resolveHeaders(headers) {
|
|
|
163
188
|
return typeof headers === "function" ? await headers() : { ...headers };
|
|
164
189
|
}
|
|
165
190
|
|
|
191
|
+
// src/openapi/resourceMapping.ts
|
|
192
|
+
function buildResourceMapping(route, name, parameterMap, requiredKeys) {
|
|
193
|
+
const entries = Object.entries(parameterMap);
|
|
194
|
+
if (entries.length === 0) {
|
|
195
|
+
return { kind: "resource", uri: `openapi://${name}${route.path}` };
|
|
196
|
+
}
|
|
197
|
+
const required = new Set(requiredKeys ?? []);
|
|
198
|
+
const args = [];
|
|
199
|
+
const queryKeys = [];
|
|
200
|
+
let path = route.path;
|
|
201
|
+
for (const [flatKey, mapping] of entries) {
|
|
202
|
+
if (mapping.in === "path") {
|
|
203
|
+
if (flatKey !== mapping.name) {
|
|
204
|
+
path = path.replace(`{${mapping.name}}`, `{${flatKey}}`);
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
queryKeys.push(flatKey);
|
|
208
|
+
}
|
|
209
|
+
args.push({ name: flatKey, required: required.has(flatKey) });
|
|
210
|
+
}
|
|
211
|
+
const uriTemplate = `openapi://${name}${path}` + (queryKeys.length > 0 ? `{?${queryKeys.join(",")}}` : "");
|
|
212
|
+
return { args, kind: "template", uriTemplate };
|
|
213
|
+
}
|
|
214
|
+
function isEligibleForResource(route) {
|
|
215
|
+
return route.parameters.every((param) => {
|
|
216
|
+
if (param.in === "header" || param.in === "cookie") {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
return param.schema?.type !== "array";
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
166
223
|
// src/openapi/routes.ts
|
|
167
224
|
var HTTP_METHODS = ["get", "put", "post", "delete", "patch"];
|
|
168
225
|
function extractRoutes(document) {
|
|
@@ -238,8 +295,14 @@ function buildFlatSchema(route, sharedDefs) {
|
|
|
238
295
|
list.push(param);
|
|
239
296
|
byName.set(param.name, list);
|
|
240
297
|
}
|
|
241
|
-
const {
|
|
242
|
-
|
|
298
|
+
const {
|
|
299
|
+
bodyEncoding,
|
|
300
|
+
properties: bodyProperties,
|
|
301
|
+
unsupportedBodyContentType,
|
|
302
|
+
wholeBodyKey
|
|
303
|
+
} = extractBodyProperties(
|
|
304
|
+
route.method === "get" ? void 0 : route.requestBody,
|
|
305
|
+
sharedDefs
|
|
243
306
|
);
|
|
244
307
|
const properties = {};
|
|
245
308
|
const required = [];
|
|
@@ -274,7 +337,13 @@ function buildFlatSchema(route, sharedDefs) {
|
|
|
274
337
|
if (usedDefs) {
|
|
275
338
|
flatSchema.$defs = usedDefs;
|
|
276
339
|
}
|
|
277
|
-
return {
|
|
340
|
+
return {
|
|
341
|
+
bodyEncoding,
|
|
342
|
+
flatSchema,
|
|
343
|
+
parameterMap,
|
|
344
|
+
unsupportedBodyContentType,
|
|
345
|
+
wholeBodyKey
|
|
346
|
+
};
|
|
278
347
|
}
|
|
279
348
|
function buildSharedDefs(document) {
|
|
280
349
|
const schemas = document.components?.schemas;
|
|
@@ -292,12 +361,32 @@ function childMode(key) {
|
|
|
292
361
|
}
|
|
293
362
|
return SCHEMA_MAP_KEYS.has(key) ? "schemaMap" : "schema";
|
|
294
363
|
}
|
|
295
|
-
function
|
|
364
|
+
function componentSchemaName(ref) {
|
|
365
|
+
for (const prefix of ["#/components/schemas/", "#/$defs/"]) {
|
|
366
|
+
if (ref.startsWith(prefix)) {
|
|
367
|
+
return ref.slice(prefix.length);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return void 0;
|
|
371
|
+
}
|
|
372
|
+
function extractBodyProperties(requestBody, sharedDefs) {
|
|
296
373
|
const properties = /* @__PURE__ */ new Map();
|
|
297
|
-
const
|
|
298
|
-
if (!
|
|
374
|
+
const content = requestBody?.content;
|
|
375
|
+
if (!content) {
|
|
299
376
|
return { properties };
|
|
300
377
|
}
|
|
378
|
+
const hasJson = "application/json" in content;
|
|
379
|
+
const hasForm = "application/x-www-form-urlencoded" in content;
|
|
380
|
+
if (!hasJson && !hasForm) {
|
|
381
|
+
const contentTypes = Object.keys(content);
|
|
382
|
+
return contentTypes.length > 0 ? { properties, unsupportedBodyContentType: contentTypes[0] } : { properties };
|
|
383
|
+
}
|
|
384
|
+
const bodyEncoding = hasJson ? "json" : "form";
|
|
385
|
+
const declaredSchema = hasJson ? content["application/json"]?.schema : content["application/x-www-form-urlencoded"]?.schema;
|
|
386
|
+
if (!declaredSchema) {
|
|
387
|
+
return { bodyEncoding, properties };
|
|
388
|
+
}
|
|
389
|
+
const schema = bodyEncoding === "form" ? resolveComponentRef(declaredSchema, sharedDefs) : declaredSchema;
|
|
301
390
|
const schemaProperties = schema.properties;
|
|
302
391
|
if (schema.type === "object" && schemaProperties) {
|
|
303
392
|
const requiredNames = new Set(
|
|
@@ -309,13 +398,19 @@ function extractBodyProperties(requestBody) {
|
|
|
309
398
|
schema: propertySchema
|
|
310
399
|
});
|
|
311
400
|
}
|
|
312
|
-
return { properties };
|
|
401
|
+
return { bodyEncoding, properties };
|
|
402
|
+
}
|
|
403
|
+
if (bodyEncoding === "form") {
|
|
404
|
+
return {
|
|
405
|
+
properties,
|
|
406
|
+
unsupportedBodyContentType: "application/x-www-form-urlencoded"
|
|
407
|
+
};
|
|
313
408
|
}
|
|
314
409
|
properties.set("body", {
|
|
315
410
|
required: requestBody?.required ?? false,
|
|
316
411
|
schema
|
|
317
412
|
});
|
|
318
|
-
return { properties, wholeBodyKey: "body" };
|
|
413
|
+
return { bodyEncoding, properties, wholeBodyKey: "body" };
|
|
319
414
|
}
|
|
320
415
|
function filterReferencedDefs(node, allDefs) {
|
|
321
416
|
const referenced = /* @__PURE__ */ new Set();
|
|
@@ -366,6 +461,22 @@ function normalizeNullable(schema) {
|
|
|
366
461
|
}
|
|
367
462
|
return rest;
|
|
368
463
|
}
|
|
464
|
+
function resolveComponentRef(schema, sharedDefs) {
|
|
465
|
+
const seen = /* @__PURE__ */ new Set();
|
|
466
|
+
let current = schema;
|
|
467
|
+
let ref = current.$ref;
|
|
468
|
+
while (typeof ref === "string") {
|
|
469
|
+
const name = componentSchemaName(ref);
|
|
470
|
+
const target = name === void 0 ? void 0 : sharedDefs?.[name];
|
|
471
|
+
if (name === void 0 || target === void 0 || seen.has(name)) {
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
seen.add(name);
|
|
475
|
+
current = target;
|
|
476
|
+
ref = current.$ref;
|
|
477
|
+
}
|
|
478
|
+
return current;
|
|
479
|
+
}
|
|
369
480
|
function rewriteNode(value, mode) {
|
|
370
481
|
if (mode === "data") {
|
|
371
482
|
return value;
|
|
@@ -442,40 +553,109 @@ async function fromOpenAPI(options) {
|
|
|
442
553
|
const { document, origin } = await loadSpec(options.spec);
|
|
443
554
|
const routes = extractRoutes(document);
|
|
444
555
|
const selected = selectRoutes(routes, options);
|
|
445
|
-
const names =
|
|
556
|
+
const names = generateNames(selected, options.mcpNames);
|
|
446
557
|
const sharedDefs = buildSharedDefs(document);
|
|
447
558
|
const server = options.server ?? new FastMCP({
|
|
448
559
|
name: options.name ?? document.info?.title ?? "OpenAPI Server",
|
|
449
560
|
version: options.version ?? "1.0.0"
|
|
450
561
|
});
|
|
562
|
+
const skippedOperations = [];
|
|
451
563
|
for (const route of selected) {
|
|
452
564
|
const name = names.get(route);
|
|
453
565
|
if (!name) {
|
|
454
566
|
continue;
|
|
455
567
|
}
|
|
456
|
-
const {
|
|
568
|
+
const {
|
|
569
|
+
bodyEncoding,
|
|
570
|
+
flatSchema,
|
|
571
|
+
parameterMap,
|
|
572
|
+
unsupportedBodyContentType,
|
|
573
|
+
wholeBodyKey
|
|
574
|
+
} = buildFlatSchema(route, sharedDefs);
|
|
575
|
+
const execOptions = {
|
|
576
|
+
baseUrlOverride: options.baseUrl,
|
|
577
|
+
fetchImpl: options.fetch ?? fetch,
|
|
578
|
+
headers: options.headers,
|
|
579
|
+
origin,
|
|
580
|
+
parameterMap,
|
|
457
581
|
route,
|
|
458
|
-
|
|
459
|
-
|
|
582
|
+
servers: document.servers
|
|
583
|
+
};
|
|
584
|
+
if (options.resources && route.method === "get" && isEligibleForResource(route)) {
|
|
585
|
+
registerResource(
|
|
586
|
+
server,
|
|
587
|
+
route,
|
|
588
|
+
name,
|
|
589
|
+
parameterMap,
|
|
590
|
+
flatSchema.required,
|
|
591
|
+
execOptions
|
|
592
|
+
);
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
if (unsupportedBodyContentType) {
|
|
596
|
+
skippedOperations.push({
|
|
597
|
+
contentType: unsupportedBodyContentType,
|
|
598
|
+
method: route.method,
|
|
599
|
+
path: route.path
|
|
600
|
+
});
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
460
603
|
server.addTool({
|
|
461
604
|
description: route.summary ?? `${route.method.toUpperCase()} ${route.path}`,
|
|
462
605
|
execute: async (args) => executeRequest({
|
|
606
|
+
...execOptions,
|
|
463
607
|
args,
|
|
464
|
-
|
|
465
|
-
fetchImpl: options.fetch ?? fetch,
|
|
466
|
-
headers: options.headers,
|
|
467
|
-
origin,
|
|
468
|
-
parameterMap,
|
|
469
|
-
route,
|
|
470
|
-
servers: document.servers,
|
|
608
|
+
bodyEncoding,
|
|
471
609
|
wholeBodyKey
|
|
472
610
|
}),
|
|
473
611
|
name,
|
|
474
612
|
parameters: jsonSchemaAdapter(flatSchema)
|
|
475
613
|
});
|
|
476
614
|
}
|
|
615
|
+
if (skippedOperations.length > 0) {
|
|
616
|
+
console.warn(
|
|
617
|
+
`fromOpenAPI: skipped ${skippedOperations.length} operation(s) whose request body can't be turned into tool parameters (supported: application/json, or application/x-www-form-urlencoded with a flat object schema): ` + skippedOperations.map(
|
|
618
|
+
(op) => `${op.method.toUpperCase()} ${op.path} (${op.contentType})`
|
|
619
|
+
).join(", ")
|
|
620
|
+
);
|
|
621
|
+
}
|
|
477
622
|
return server;
|
|
478
623
|
}
|
|
624
|
+
function registerResource(server, route, name, parameterMap, requiredKeys, execOptions) {
|
|
625
|
+
const mapping = buildResourceMapping(route, name, parameterMap, requiredKeys);
|
|
626
|
+
const description = route.summary ?? `GET ${route.path}`;
|
|
627
|
+
if (mapping.kind === "resource") {
|
|
628
|
+
server.addResource({
|
|
629
|
+
description,
|
|
630
|
+
load: async () => wrapAsResourceResult(
|
|
631
|
+
await executeRequest({ ...execOptions, args: {} })
|
|
632
|
+
),
|
|
633
|
+
name,
|
|
634
|
+
uri: mapping.uri
|
|
635
|
+
});
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
server.addResourceTemplate({
|
|
639
|
+
arguments: mapping.args,
|
|
640
|
+
description,
|
|
641
|
+
load: async (args) => wrapAsResourceResult(
|
|
642
|
+
await executeRequest({
|
|
643
|
+
...execOptions,
|
|
644
|
+
args
|
|
645
|
+
})
|
|
646
|
+
),
|
|
647
|
+
name,
|
|
648
|
+
uriTemplate: mapping.uriTemplate
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
function wrapAsResourceResult(text) {
|
|
652
|
+
try {
|
|
653
|
+
JSON.parse(text);
|
|
654
|
+
return { mimeType: "application/json", text };
|
|
655
|
+
} catch {
|
|
656
|
+
return { mimeType: "text/plain", text };
|
|
657
|
+
}
|
|
658
|
+
}
|
|
479
659
|
export {
|
|
480
660
|
fromOpenAPI
|
|
481
661
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/openapi/loadSpec.ts","../../src/openapi/naming.ts","../../src/openapi/requestBuilder.ts","../../src/openapi/routes.ts","../../src/openapi/schemas.ts","../../src/openapi/selection.ts","../../src/openapi/fromOpenAPI.ts"],"sourcesContent":["import SwaggerParser from \"@apidevtools/swagger-parser\";\n\nimport type { BundledOpenApiDocument } from \"./types.js\";\n\nexport interface LoadedSpec {\n document: BundledOpenApiDocument;\n /**\n * The spec's own URL, when it was loaded from one. Used to resolve a\n * relative `servers[0].url` against the document's origin.\n */\n origin?: string;\n}\n\n/**\n * Loads and bundles an OpenAPI document, resolving local *and* external\n * `$ref`s (relative paths, absolute URLs, `other.yaml#/fragment`).\n *\n * `spec` is handed to swagger-parser as-is — a URL, file path, or object —\n * rather than being fetched and re-parsed here first. External refs resolve\n * relative to whatever document they were found in, so pre-fetching the\n * entry document and passing its parsed text as an object would resolve\n * every external ref against the wrong base (or none at all).\n */\nexport async function loadSpec(\n spec: Record<string, unknown> | string,\n): Promise<LoadedSpec> {\n const document = (await SwaggerParser.bundle(\n spec as never,\n )) as unknown as BundledOpenApiDocument;\n\n if (!document.openapi?.startsWith(\"3.\")) {\n throw new Error(\n `fromOpenAPI only supports OpenAPI 3.x documents (found ${\n document.openapi ?? document.swagger ?? \"an unrecognized version\"\n }). Swagger 2.0 is not supported.`,\n );\n }\n\n return {\n document,\n origin: typeof spec === \"string\" && isHttpUrl(spec) ? spec : undefined,\n };\n}\n\nfunction isHttpUrl(value: string): boolean {\n return value.startsWith(\"http://\") || value.startsWith(\"https://\");\n}\n","import type { HttpRoute } from \"./types.js\";\n\nconst MAX_NAME_LENGTH = 56;\n// Reserves room for a \"_<n>\" collision suffix so the final name never\n// exceeds MAX_NAME_LENGTH, however many collisions it takes.\nconst MAX_BASE_LENGTH = MAX_NAME_LENGTH - 5;\n\n/**\n * Generates a unique tool name per route.\n *\n * Ports the Python implementation's naming rule\n * (`server/providers/openapi/provider.py:_generate_default_name`): prefer\n * `mcpNames[operationId]`, then `operationId` (FastAPI-style `__` suffixes\n * stripped), falling back to `summary` or `{method}_{path}`; slugified and\n * capped at 56 characters, with `_2`, `_3`, ... appended on collision.\n *\n * Uniqueness is checked against the final (post-suffix) name, not just the\n * base — otherwise a spec whose own operationIds already look auto-suffixed\n * (e.g. both \"foo\" and \"foo_2\" present) could produce two identically-named\n * tools, one of which `FastMCP.addTool` would silently drop.\n */\nexport function generateToolNames(\n routes: HttpRoute[],\n mcpNames: Record<string, string> | undefined,\n): Map<HttpRoute, string> {\n const names = new Map<HttpRoute, string>();\n const used = new Set<string>();\n\n for (const route of routes) {\n const base = slugify(baseNameFor(route, mcpNames));\n let candidate = base;\n let suffix = 1;\n\n while (used.has(candidate)) {\n suffix += 1;\n candidate = `${base}_${suffix}`;\n }\n\n used.add(candidate);\n names.set(route, candidate);\n }\n\n return names;\n}\n\nfunction baseNameFor(\n route: HttpRoute,\n mcpNames: Record<string, string> | undefined,\n): string {\n if (route.operationId) {\n return mcpNames?.[route.operationId] ?? route.operationId.split(\"__\")[0];\n }\n\n return route.summary || `${route.method}_${route.path}`;\n}\n\nfunction slugify(value: string): string {\n const slug = value\n .replace(/[^a-zA-Z0-9_]+/g, \"_\")\n .replace(/_+/g, \"_\")\n .replace(/^_|_$/g, \"\")\n .slice(0, MAX_BASE_LENGTH);\n\n return slug || \"operation\";\n}\n","import type { ParameterMapping } from \"./schemas.js\";\nimport type { FromOpenAPIOptions, HttpRoute, OpenApiServer } from \"./types.js\";\n\nimport { UserError } from \"../FastMCP.js\";\n\nexport interface ExecuteRequestOptions {\n args: Record<string, unknown>;\n baseUrlOverride?: string;\n fetchImpl: typeof fetch;\n headers?: FromOpenAPIOptions[\"headers\"];\n origin?: string;\n parameterMap: Record<string, ParameterMapping>;\n route: HttpRoute;\n servers: OpenApiServer[] | undefined;\n wholeBodyKey?: string;\n}\n\nexport async function executeRequest(\n options: ExecuteRequestOptions,\n): Promise<string> {\n const baseUrl = resolveBaseUrl(\n options.servers,\n options.origin,\n options.baseUrlOverride,\n );\n\n const pathParams: Record<string, string> = {};\n const query = new URLSearchParams();\n // A plain object keys headers case-sensitively, so a caller-supplied\n // header (e.g. \"Content-Type\") wouldn't be recognized as the same header\n // as one this function sets internally (e.g. \"content-type\") — `Headers`\n // normalizes casing, so `.set()` correctly overrides rather than\n // combining into a comma-joined, malformed value.\n const headers = new Headers(await resolveHeaders(options.headers));\n const bodyProps: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(options.args)) {\n const mapping = options.parameterMap[key];\n\n if (!mapping || value === undefined) {\n continue;\n }\n\n switch (mapping.in) {\n case \"body\":\n bodyProps[mapping.name] = value;\n break;\n case \"cookie\": {\n const existing = headers.get(\"cookie\");\n headers.set(\n \"cookie\",\n existing\n ? `${existing}; ${mapping.name}=${String(value)}`\n : `${mapping.name}=${String(value)}`,\n );\n break;\n }\n case \"header\":\n headers.set(mapping.name, String(value));\n break;\n case \"path\":\n pathParams[mapping.name] = String(value);\n break;\n case \"query\":\n for (const item of Array.isArray(value) ? value : [value]) {\n query.append(mapping.name, String(item));\n }\n break;\n }\n }\n\n let path = options.route.path;\n\n for (const [name, value] of Object.entries(pathParams)) {\n path = path.replace(`{${name}}`, encodeURIComponent(value));\n }\n\n const url = new URL(baseUrl.replace(/\\/$/, \"\") + path);\n url.search = query.toString();\n\n let body: string | undefined;\n\n if (Object.keys(bodyProps).length > 0) {\n if (!headers.has(\"content-type\")) {\n headers.set(\"content-type\", \"application/json\");\n }\n\n body = JSON.stringify(\n options.wholeBodyKey ? bodyProps[options.wholeBodyKey] : bodyProps,\n );\n }\n\n const response = await options.fetchImpl(url.toString(), {\n body,\n headers,\n method: options.route.method.toUpperCase(),\n });\n\n const text = await response.text();\n\n if (!response.ok) {\n throw new UserError(\n `${options.route.method.toUpperCase()} ${path} failed with ${response.status}: ${text.slice(0, 2000)}`,\n );\n }\n\n if (response.headers.get(\"content-type\")?.includes(\"json\")) {\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch {\n return text;\n }\n }\n\n return text;\n}\n\n/**\n * Resolves `servers[0].url` the way a real HTTP client needs it resolved,\n * not just the way a schema validator would accept it: a relative URL (e.g.\n * Petstore's own `\"/api/v3\"`) is joined against the document's own origin,\n * not passed through verbatim.\n */\nexport function resolveBaseUrl(\n servers: OpenApiServer[] | undefined,\n origin: string | undefined,\n overrideUrl: string | undefined,\n): string {\n if (overrideUrl) {\n return overrideUrl.replace(/\\/$/, \"\");\n }\n\n const server = servers?.[0];\n\n if (!server) {\n throw new Error(\n \"The OpenAPI document has no `servers` entry. Pass `baseUrl` to fromOpenAPI() explicitly.\",\n );\n }\n\n let url = server.url;\n\n for (const [name, variable] of Object.entries(server.variables ?? {})) {\n url = url.replaceAll(`{${name}}`, variable.default);\n }\n\n try {\n return new URL(url).toString().replace(/\\/$/, \"\");\n } catch {\n if (!origin) {\n throw new Error(\n `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.`,\n );\n }\n\n return new URL(url, origin).toString().replace(/\\/$/, \"\");\n }\n}\n\nasync function resolveHeaders(\n headers: FromOpenAPIOptions[\"headers\"],\n): Promise<Record<string, string>> {\n if (!headers) {\n return {};\n }\n\n return typeof headers === \"function\" ? await headers() : { ...headers };\n}\n","import type {\n BundledOpenApiDocument,\n HttpMethod,\n HttpRoute,\n OpenApiParameter,\n OpenApiParameterRef,\n OpenApiRequestBody,\n} from \"./types.js\";\n\nconst HTTP_METHODS: HttpMethod[] = [\"get\", \"put\", \"post\", \"delete\", \"patch\"];\n\n/**\n * Walks a bundled document's `paths` into a flat list of routes, resolving\n * any structural (non-schema) `$ref`s on parameters and request bodies —\n * e.g. `#/components/parameters/Limit` — against the same document.\n *\n * Bundling (see `loadSpec.ts`) guarantees every remaining `$ref` here is\n * local, so a plain JSON-pointer lookup is enough.\n */\nexport function extractRoutes(document: BundledOpenApiDocument): HttpRoute[] {\n const routes: HttpRoute[] = [];\n\n for (const [path, pathItem] of Object.entries(document.paths ?? {})) {\n const pathLevelParams = (pathItem.parameters ?? []).map((param) =>\n resolveRef<OpenApiParameter>(document, param),\n );\n\n for (const method of HTTP_METHODS) {\n const operation = pathItem[method];\n\n if (!operation) {\n continue;\n }\n\n const operationParams = (operation.parameters ?? []).map((param) =>\n resolveRef<OpenApiParameter>(document, param),\n );\n\n routes.push({\n deprecated: operation.deprecated ?? false,\n method,\n operationId: operation.operationId,\n parameters: mergeParameters(pathLevelParams, operationParams),\n path,\n requestBody: operation.requestBody\n ? resolveRef<OpenApiRequestBody>(document, operation.requestBody)\n : undefined,\n summary: operation.summary,\n tags: operation.tags ?? [],\n });\n }\n }\n\n return routes;\n}\n\nfunction mergeParameters(\n pathLevel: OpenApiParameter[],\n operationLevel: OpenApiParameter[],\n): OpenApiParameter[] {\n const overridden = new Set(\n operationLevel.map((param) => `${param.in}:${param.name}`),\n );\n\n return [\n ...pathLevel.filter(\n (param) => !overridden.has(`${param.in}:${param.name}`),\n ),\n ...operationLevel,\n ];\n}\n\nfunction resolveRef<TValue>(\n document: BundledOpenApiDocument,\n value: OpenApiParameterRef | TValue,\n): TValue {\n if (!value || typeof value !== \"object\" || !(\"$ref\" in value)) {\n return value;\n }\n\n const pointer = value.$ref;\n\n if (!pointer.startsWith(\"#/\")) {\n // Bundling should have already turned every external ref into a local\n // one — if this fires, swagger-parser's output shape has changed.\n throw new Error(`Unexpected external $ref after bundling: ${pointer}`);\n }\n\n // swagger-parser synthesizes these pointers as URI fragments (e.g. a path\n // like \"/pets/{petId}\" becomes \"~1pets~1%7BpetId%7D\"), so each segment\n // needs its \"~1\"/\"~0\" escapes undone *and* percent-decoding, in that order.\n const segments = pointer\n .slice(2)\n .split(\"/\")\n .map((segment) =>\n decodeURIComponent(segment.replaceAll(\"~1\", \"/\").replaceAll(\"~0\", \"~\")),\n );\n\n let node: unknown = document;\n\n for (const segment of segments) {\n node = (node as Record<string, unknown> | undefined)?.[segment];\n }\n\n return node as TValue;\n}\n","import type { JsonSchemaObject } from \"../jsonSchemaAdapter.js\";\nimport type {\n BundledOpenApiDocument,\n HttpRoute,\n OpenApiParameter,\n OpenApiRequestBody,\n OpenApiSchema,\n ParameterLocation,\n} from \"./types.js\";\n\n/**\n * Keys whose values are name-to-schema maps: their child keys are\n * author-chosen names rather than JSON Schema keywords.\n */\nconst SCHEMA_MAP_KEYS = new Set([\n \"$defs\",\n \"definitions\",\n \"dependentSchemas\",\n \"patternProperties\",\n \"properties\",\n]);\n\n/**\n * Keys whose values are arbitrary instance data rather than schemas. A\n * sample payload may well contain a \"$ref\" or \"nullable\" key of its own.\n */\nconst DATA_KEYS = new Set([\"const\", \"default\", \"enum\", \"example\", \"examples\"]);\n\nexport interface FlatSchemaResult {\n flatSchema: JsonSchemaObject;\n parameterMap: Record<string, ParameterMapping>;\n /**\n * Set when the request body's schema is not a flat object (e.g. an array,\n * or a bare non-object `$ref`) — the whole body is exposed as a single\n * property under this key, rather than flattened into individual\n * properties.\n */\n wholeBodyKey?: string;\n}\n\nexport interface ParameterMapping {\n in: \"body\" | ParameterLocation;\n name: string;\n}\n\ntype WalkMode = \"data\" | \"schema\" | \"schemaMap\";\n\n/**\n * Flattens a route's path/query/header/cookie parameters and request body\n * into a single tool input schema.\n *\n * Collision precedence ports the Python implementation's rule\n * (`utilities/openapi/schemas.py:_combine_schemas_and_map_params`): a name\n * that collides across path/query/header/cookie gets suffixed\n * `{name}__{location}`; a request body property with a colliding name always\n * keeps its bare name.\n *\n * `GET` never contributes a request body: `fetch` (and the Fetch spec in\n * general) rejects a body on a GET request, so a tool built from a spec's\n * (legal, if unusual) `GET` + `requestBody` operation would be permanently\n * broken. The request body is simply not flattened into the schema for such\n * a route, rather than surfacing a schema that can never actually be called.\n */\nexport function buildFlatSchema(\n route: HttpRoute,\n sharedDefs: Record<string, OpenApiSchema> | undefined,\n): FlatSchemaResult {\n const byName = new Map<string, OpenApiParameter[]>();\n\n for (const param of route.parameters) {\n const list = byName.get(param.name) ?? [];\n list.push(param);\n byName.set(param.name, list);\n }\n\n const { properties: bodyProperties, wholeBodyKey } = extractBodyProperties(\n route.method === \"get\" ? undefined : route.requestBody,\n );\n\n const properties: Record<string, OpenApiSchema> = {};\n const required: string[] = [];\n const parameterMap: Record<string, ParameterMapping> = {};\n\n for (const [name, occurrences] of byName) {\n const collides = occurrences.length > 1 || bodyProperties.has(name);\n\n for (const param of occurrences) {\n const key = collides ? `${name}__${param.in}` : name;\n\n properties[key] = rewriteComponentRefs(\n param.schema ?? { type: \"string\" },\n );\n parameterMap[key] = { in: param.in, name };\n\n if (param.in === \"path\" || param.required) {\n required.push(key);\n }\n }\n }\n\n for (const [name, { required: isRequired, schema }] of bodyProperties) {\n properties[name] = rewriteComponentRefs(schema);\n parameterMap[name] = { in: \"body\", name };\n\n if (isRequired) {\n required.push(name);\n }\n }\n\n const flatSchema: JsonSchemaObject = {\n additionalProperties: false,\n properties,\n type: \"object\",\n ...(required.length > 0 ? { required } : {}),\n };\n\n // Only the definitions this tool's own schema actually (transitively)\n // references — embedding the whole document's components.schemas into\n // every single tool would multiply the tools/list payload size by the\n // tool count for no benefit.\n const usedDefs = sharedDefs && filterReferencedDefs(properties, sharedDefs);\n\n if (usedDefs) {\n flatSchema.$defs = usedDefs;\n }\n\n return { flatSchema, parameterMap, wholeBodyKey };\n}\n\nexport function buildSharedDefs(\n document: BundledOpenApiDocument,\n): Record<string, OpenApiSchema> | undefined {\n const schemas = document.components?.schemas;\n\n if (!schemas || Object.keys(schemas).length === 0) {\n return undefined;\n }\n\n return rewriteNode(schemas, \"schemaMap\") as Record<string, OpenApiSchema>;\n}\n\n/**\n * Rewrites `$ref`s pointing at `#/components/schemas/...` to `#/$defs/...`,\n * so a per-tool schema that contains one can be handed to AJV standalone,\n * alongside a `$defs` object built from the document's `components.schemas`\n * (see `buildSharedDefs`). Ports the equivalent rewrite from the Python\n * implementation (`utilities/openapi/schemas.py:_replace_ref_with_defs`).\n *\n * Also normalizes OpenAPI 3.0's `nullable` keyword (see `normalizeNullable`),\n * since real specs carry both.\n */\nexport function rewriteComponentRefs<TValue>(value: TValue): TValue {\n return rewriteNode(value, \"schema\") as TValue;\n}\n\nfunction childMode(key: string): WalkMode {\n if (DATA_KEYS.has(key)) {\n return \"data\";\n }\n\n return SCHEMA_MAP_KEYS.has(key) ? \"schemaMap\" : \"schema\";\n}\n\nfunction extractBodyProperties(requestBody: OpenApiRequestBody | undefined): {\n properties: Map<string, { required: boolean; schema: OpenApiSchema }>;\n wholeBodyKey?: string;\n} {\n const properties = new Map<\n string,\n { required: boolean; schema: OpenApiSchema }\n >();\n\n const schema = requestBody?.content?.[\"application/json\"]?.schema;\n\n if (!schema) {\n return { properties };\n }\n\n const schemaProperties = schema.properties as\n | Record<string, OpenApiSchema>\n | undefined;\n\n if (schema.type === \"object\" && schemaProperties) {\n const requiredNames = new Set(\n (schema.required as string[] | undefined) ?? [],\n );\n\n for (const [name, propertySchema] of Object.entries(schemaProperties)) {\n properties.set(name, {\n required: requiredNames.has(name),\n schema: propertySchema,\n });\n }\n\n return { properties };\n }\n\n // Non-object body (array, bare $ref to a scalar/array, etc.) — expose the\n // whole thing as a single \"body\" property rather than flattening it.\n properties.set(\"body\", {\n required: requestBody?.required ?? false,\n schema,\n });\n\n return { properties, wholeBodyKey: \"body\" };\n}\n\n/**\n * Walks a schema fragment for `#/$defs/Name` refs and returns just those\n * definitions (transitively — a referenced def may itself reference\n * others), or `undefined` if none are referenced.\n */\nfunction filterReferencedDefs(\n node: unknown,\n allDefs: Record<string, OpenApiSchema>,\n): Record<string, OpenApiSchema> | undefined {\n const referenced = new Set<string>();\n const stack: unknown[] = [node];\n\n while (stack.length > 0) {\n const current = stack.pop();\n\n if (Array.isArray(current)) {\n stack.push(...current);\n continue;\n }\n\n if (!current || typeof current !== \"object\") {\n continue;\n }\n\n for (const [key, value] of Object.entries(\n current as Record<string, unknown>,\n )) {\n if (\n key === \"$ref\" &&\n typeof value === \"string\" &&\n value.startsWith(\"#/$defs/\")\n ) {\n const name = value.slice(\"#/$defs/\".length);\n\n if (allDefs[name] && !referenced.has(name)) {\n referenced.add(name);\n stack.push(allDefs[name]);\n }\n\n continue;\n }\n\n stack.push(value);\n }\n }\n\n if (referenced.size === 0) {\n return undefined;\n }\n\n return Object.fromEntries(\n [...referenced].map((name) => [name, allDefs[name]]),\n );\n}\n\n/**\n * OpenAPI 3.0's `nullable` keyword only makes sense alongside a sibling\n * `type`, which it widens (`nullable: true` + `type: \"string\"` means\n * \"string or null\") — but it is not itself standard JSON Schema. AJV\n * recognizes the keyword and throws ('\"nullable\" cannot be used without\n * \"type\"') if it finds one with no `type` on the same node, which real\n * specs do produce (e.g. `nullable` sibling to `oneOf`/`allOf`/`$ref`\n * instead of `type`, as in Box's API). Folded into `type` where there is\n * one to widen, dropped otherwise.\n */\nfunction normalizeNullable(\n schema: Record<string, unknown>,\n): Record<string, unknown> {\n if (!(\"nullable\" in schema)) {\n return schema;\n }\n\n const { nullable, type, ...rest } = schema;\n\n if (nullable !== true) {\n return rest;\n }\n\n if (typeof type === \"string\") {\n return { ...rest, type: [type, \"null\"] };\n }\n\n if (Array.isArray(type)) {\n return { ...rest, type: [...new Set([\"null\", ...type])] };\n }\n\n return rest;\n}\n\n/**\n * Walks a schema fragment, distinguishing the three kinds of node it can\n * reach — because only one of them is a schema whose keys are JSON Schema\n * keywords:\n *\n * - `\"schema\"` — a schema object. `$ref`/`nullable` here are keywords.\n * - `\"schemaMap\"` — a name-to-schema map (`properties`, `$defs`, ...). Its\n * keys are author-chosen names, so a property literally named `nullable`\n * or `$ref` is a field, not a keyword, and must survive untouched.\n * - `\"data\"` — arbitrary values (`default`, `enum`, `example`, ...). Not\n * schemas at all; passed through verbatim.\n *\n * Walking every node as a schema (as this originally did) silently deletes\n * a property named `nullable` from the generated tool schema, since\n * `normalizeNullable` cannot tell the keyword from a same-named field.\n */\nfunction rewriteNode(value: unknown, mode: WalkMode): unknown {\n if (mode === \"data\") {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => rewriteNode(item, \"schema\"));\n }\n\n if (!value || typeof value !== \"object\") {\n return value;\n }\n\n const entries = Object.entries(value as Record<string, unknown>).map(\n ([key, entryValue]): [string, unknown] => {\n if (mode === \"schemaMap\") {\n return [key, rewriteNode(entryValue, \"schema\")];\n }\n\n if (\n key === \"$ref\" &&\n typeof entryValue === \"string\" &&\n entryValue.startsWith(\"#/components/schemas/\")\n ) {\n return [key, entryValue.replace(\"#/components/schemas/\", \"#/$defs/\")];\n }\n\n return [key, rewriteNode(entryValue, childMode(key))];\n },\n );\n\n const rewritten = Object.fromEntries(entries) as Record<string, unknown>;\n\n return mode === \"schemaMap\" ? rewritten : normalizeNullable(rewritten);\n}\n","import type {\n FromOpenAPIOptions,\n HttpMethod,\n HttpRoute,\n OperationSummary,\n} from \"./types.js\";\n\n/**\n * If neither `include`/`exclude` nor `maxTools` was given, and a spec still\n * produces more operations than this, `selectRoutes` throws rather than\n * silently generating a wall of tools most clients can't usefully work with.\n */\nexport const DEFAULT_MAX_OPERATIONS = 40;\n\nconst METHOD_PRIORITY: Record<HttpMethod, number> = {\n delete: 4,\n get: 0,\n patch: 3,\n post: 1,\n put: 2,\n};\n\n/**\n * Filters and orders routes into the set that becomes tools.\n *\n * Deprecated operations are excluded by default. Ordering is deterministic\n * (method priority GET→POST→PUT→PATCH→DELETE, then path) so that, combined\n * with `maxTools`, truncation is legible rather than arbitrary.\n */\nexport function selectRoutes(\n routes: HttpRoute[],\n options: Pick<FromOpenAPIOptions, \"exclude\" | \"include\" | \"maxTools\">,\n): HttpRoute[] {\n let selected = routes.filter((route) => !route.deprecated);\n\n if (options.include) {\n const include = options.include;\n selected = selected.filter((route) => include(toSummary(route)));\n }\n\n if (options.exclude) {\n const exclude = options.exclude;\n selected = selected.filter((route) => !exclude(toSummary(route)));\n }\n\n selected = [...selected].sort((a, b) => {\n const byMethod = METHOD_PRIORITY[a.method] - METHOD_PRIORITY[b.method];\n return byMethod !== 0 ? byMethod : a.path.localeCompare(b.path);\n });\n\n const noSelectionGiven = !options.include && !options.exclude;\n\n if (\n noSelectionGiven &&\n options.maxTools === undefined &&\n selected.length > DEFAULT_MAX_OPERATIONS\n ) {\n throw new Error(\n `fromOpenAPI found ${selected.length} operations, which exceeds the default limit of ${DEFAULT_MAX_OPERATIONS}. ` +\n \"This is a deliberate stop, not a bug: turning every operation in a large spec into a tool produces a tool list most MCP clients can't use well. \" +\n \"Pass `include`/`exclude` to choose the operations you actually want, or `maxTools` to raise this limit explicitly.\",\n );\n }\n\n if (options.maxTools !== undefined && selected.length > options.maxTools) {\n throw new Error(\n `fromOpenAPI found ${selected.length} operations, which exceeds maxTools (${options.maxTools}). ` +\n \"Narrow the spec with `include`/`exclude`, or raise `maxTools`.\",\n );\n }\n\n return selected;\n}\n\nfunction toSummary(route: HttpRoute): OperationSummary {\n return {\n deprecated: route.deprecated,\n method: route.method,\n operationId: route.operationId,\n path: route.path,\n tags: route.tags,\n };\n}\n","import type { FromOpenAPIOptions } from \"./types.js\";\n\nimport { FastMCP } from \"../FastMCP.js\";\nimport { jsonSchemaAdapter } from \"../jsonSchemaAdapter.js\";\nimport { loadSpec } from \"./loadSpec.js\";\nimport { generateToolNames } from \"./naming.js\";\nimport { executeRequest } from \"./requestBuilder.js\";\nimport { extractRoutes } from \"./routes.js\";\nimport { buildFlatSchema, buildSharedDefs } from \"./schemas.js\";\nimport { selectRoutes } from \"./selection.js\";\n\n/**\n * Converts an OpenAPI 3.x document into an MCP server, one tool per\n * operation.\n *\n * See docs/openapi.md for the full option reference and known limitations.\n */\nexport async function fromOpenAPI(\n options: FromOpenAPIOptions,\n): Promise<FastMCP> {\n const { document, origin } = await loadSpec(options.spec);\n const routes = extractRoutes(document);\n const selected = selectRoutes(routes, options);\n const names = generateToolNames(selected, options.mcpNames);\n const sharedDefs = buildSharedDefs(document);\n\n const server =\n options.server ??\n new FastMCP({\n name: options.name ?? document.info?.title ?? \"OpenAPI Server\",\n version: options.version ?? \"1.0.0\",\n });\n\n for (const route of selected) {\n const name = names.get(route);\n\n if (!name) {\n continue;\n }\n\n const { flatSchema, parameterMap, wholeBodyKey } = buildFlatSchema(\n route,\n sharedDefs,\n );\n\n server.addTool({\n description:\n route.summary ?? `${route.method.toUpperCase()} ${route.path}`,\n execute: async (args) =>\n executeRequest({\n args: args as Record<string, unknown>,\n baseUrlOverride: options.baseUrl,\n fetchImpl: options.fetch ?? fetch,\n headers: options.headers,\n origin,\n parameterMap,\n route,\n servers: document.servers,\n wholeBodyKey,\n }),\n name,\n parameters: jsonSchemaAdapter(flatSchema),\n });\n }\n\n return server;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/openapi/loadSpec.ts","../../src/openapi/naming.ts","../../src/openapi/requestBuilder.ts","../../src/openapi/resourceMapping.ts","../../src/openapi/routes.ts","../../src/openapi/schemas.ts","../../src/openapi/selection.ts","../../src/openapi/fromOpenAPI.ts"],"sourcesContent":["import SwaggerParser from \"@apidevtools/swagger-parser\";\n\nimport type { BundledOpenApiDocument } from \"./types.js\";\n\nexport interface LoadedSpec {\n document: BundledOpenApiDocument;\n /**\n * The spec's own URL, when it was loaded from one. Used to resolve a\n * relative `servers[0].url` against the document's origin.\n */\n origin?: string;\n}\n\n/**\n * Loads and bundles an OpenAPI document, resolving local *and* external\n * `$ref`s (relative paths, absolute URLs, `other.yaml#/fragment`).\n *\n * `spec` is handed to swagger-parser as-is — a URL, file path, or object —\n * rather than being fetched and re-parsed here first. External refs resolve\n * relative to whatever document they were found in, so pre-fetching the\n * entry document and passing its parsed text as an object would resolve\n * every external ref against the wrong base (or none at all).\n */\nexport async function loadSpec(\n spec: Record<string, unknown> | string,\n): Promise<LoadedSpec> {\n const document = (await SwaggerParser.bundle(\n spec as never,\n )) as unknown as BundledOpenApiDocument;\n\n if (!document.openapi?.startsWith(\"3.\")) {\n throw new Error(\n `fromOpenAPI only supports OpenAPI 3.x documents (found ${\n document.openapi ?? document.swagger ?? \"an unrecognized version\"\n }). Swagger 2.0 is not supported.`,\n );\n }\n\n return {\n document,\n origin: typeof spec === \"string\" && isHttpUrl(spec) ? spec : undefined,\n };\n}\n\nfunction isHttpUrl(value: string): boolean {\n return value.startsWith(\"http://\") || value.startsWith(\"https://\");\n}\n","import type { HttpRoute } from \"./types.js\";\n\nconst MAX_NAME_LENGTH = 56;\n// Reserves room for a \"_<n>\" collision suffix so the final name never\n// exceeds MAX_NAME_LENGTH, however many collisions it takes.\nconst MAX_BASE_LENGTH = MAX_NAME_LENGTH - 5;\n\n/**\n * Generates a unique name per route — used both for tools and, when\n * `resources: true`, for the resources/resource templates a `GET` route\n * maps to instead. One pass over the whole selected set keeps names unique\n * regardless of which destination a route ends up at.\n *\n * Ports the Python implementation's naming rule\n * (`server/providers/openapi/provider.py:_generate_default_name`): prefer\n * `mcpNames[operationId]`, then `operationId` (FastAPI-style `__` suffixes\n * stripped), falling back to `summary` or `{method}_{path}`; slugified and\n * capped at 56 characters, with `_2`, `_3`, ... appended on collision.\n *\n * Uniqueness is checked against the final (post-suffix) name, not just the\n * base — otherwise a spec whose own operationIds already look auto-suffixed\n * (e.g. both \"foo\" and \"foo_2\" present) could produce two identically-named\n * tools, one of which `FastMCP.addTool` would silently drop.\n */\nexport function generateNames(\n routes: HttpRoute[],\n mcpNames: Record<string, string> | undefined,\n): Map<HttpRoute, string> {\n const names = new Map<HttpRoute, string>();\n const used = new Set<string>();\n\n for (const route of routes) {\n const base = slugify(baseNameFor(route, mcpNames));\n let candidate = base;\n let suffix = 1;\n\n while (used.has(candidate)) {\n suffix += 1;\n candidate = `${base}_${suffix}`;\n }\n\n used.add(candidate);\n names.set(route, candidate);\n }\n\n return names;\n}\n\nfunction baseNameFor(\n route: HttpRoute,\n mcpNames: Record<string, string> | undefined,\n): string {\n if (route.operationId) {\n return mcpNames?.[route.operationId] ?? route.operationId.split(\"__\")[0];\n }\n\n return route.summary || `${route.method}_${route.path}`;\n}\n\nfunction slugify(value: string): string {\n const slug = value\n .replace(/[^a-zA-Z0-9_]+/g, \"_\")\n .replace(/_+/g, \"_\")\n .replace(/^_|_$/g, \"\")\n .slice(0, MAX_BASE_LENGTH);\n\n return slug || \"operation\";\n}\n","import type { ParameterMapping } from \"./schemas.js\";\nimport type { FromOpenAPIOptions, HttpRoute, OpenApiServer } from \"./types.js\";\n\nimport { UserError } from \"../FastMCP.js\";\n\nexport interface ExecuteRequestOptions {\n args: Record<string, unknown>;\n baseUrlOverride?: string;\n /** How to serialize a request body, if `args` contains any body-mapped values. */\n bodyEncoding?: \"form\" | \"json\";\n fetchImpl: typeof fetch;\n headers?: FromOpenAPIOptions[\"headers\"];\n origin?: string;\n parameterMap: Record<string, ParameterMapping>;\n route: HttpRoute;\n servers: OpenApiServer[] | undefined;\n wholeBodyKey?: string;\n}\n\nexport async function executeRequest(\n options: ExecuteRequestOptions,\n): Promise<string> {\n const baseUrl = resolveBaseUrl(\n options.servers,\n options.origin,\n options.baseUrlOverride,\n );\n\n const pathParams: Record<string, string> = {};\n const query = new URLSearchParams();\n // A plain object keys headers case-sensitively, so a caller-supplied\n // header (e.g. \"Content-Type\") wouldn't be recognized as the same header\n // as one this function sets internally (e.g. \"content-type\") — `Headers`\n // normalizes casing, so `.set()` correctly overrides rather than\n // combining into a comma-joined, malformed value.\n const headers = new Headers(await resolveHeaders(options.headers));\n const bodyProps: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(options.args)) {\n const mapping = options.parameterMap[key];\n\n if (!mapping || value === undefined) {\n continue;\n }\n\n switch (mapping.in) {\n case \"body\":\n bodyProps[mapping.name] = value;\n break;\n case \"cookie\": {\n const existing = headers.get(\"cookie\");\n headers.set(\n \"cookie\",\n existing\n ? `${existing}; ${mapping.name}=${String(value)}`\n : `${mapping.name}=${String(value)}`,\n );\n break;\n }\n case \"header\":\n headers.set(mapping.name, String(value));\n break;\n case \"path\":\n pathParams[mapping.name] = String(value);\n break;\n case \"query\":\n for (const item of Array.isArray(value) ? value : [value]) {\n query.append(mapping.name, String(item));\n }\n break;\n }\n }\n\n let path = options.route.path;\n\n for (const [name, value] of Object.entries(pathParams)) {\n path = path.replace(`{${name}}`, encodeURIComponent(value));\n }\n\n const url = new URL(baseUrl.replace(/\\/$/, \"\") + path);\n url.search = query.toString();\n\n let body: string | undefined;\n\n if (Object.keys(bodyProps).length > 0) {\n const payload = options.wholeBodyKey\n ? bodyProps[options.wholeBodyKey]\n : bodyProps;\n\n if (options.bodyEncoding === \"form\") {\n if (!headers.has(\"content-type\")) {\n headers.set(\"content-type\", \"application/x-www-form-urlencoded\");\n }\n\n body = encodeFormBody(payload);\n } else {\n if (!headers.has(\"content-type\")) {\n headers.set(\"content-type\", \"application/json\");\n }\n\n body = JSON.stringify(payload);\n }\n }\n\n const response = await options.fetchImpl(url.toString(), {\n body,\n headers,\n method: options.route.method.toUpperCase(),\n });\n\n const text = await response.text();\n\n if (!response.ok) {\n throw new UserError(\n `${options.route.method.toUpperCase()} ${path} failed with ${response.status}: ${text.slice(0, 2000)}`,\n );\n }\n\n if (response.headers.get(\"content-type\")?.includes(\"json\")) {\n try {\n return JSON.stringify(JSON.parse(text), null, 2);\n } catch {\n return text;\n }\n }\n\n return text;\n}\n\n/**\n * Resolves `servers[0].url` the way a real HTTP client needs it resolved,\n * not just the way a schema validator would accept it: a relative URL (e.g.\n * Petstore's own `\"/api/v3\"`) is joined against the document's own origin,\n * not passed through verbatim.\n */\nexport function resolveBaseUrl(\n servers: OpenApiServer[] | undefined,\n origin: string | undefined,\n overrideUrl: string | undefined,\n): string {\n if (overrideUrl) {\n return overrideUrl.replace(/\\/$/, \"\");\n }\n\n const server = servers?.[0];\n\n if (!server) {\n throw new Error(\n \"The OpenAPI document has no `servers` entry. Pass `baseUrl` to fromOpenAPI() explicitly.\",\n );\n }\n\n let url = server.url;\n\n for (const [name, variable] of Object.entries(server.variables ?? {})) {\n url = url.replaceAll(`{${name}}`, variable.default);\n }\n\n try {\n return new URL(url).toString().replace(/\\/$/, \"\");\n } catch {\n if (!origin) {\n throw new Error(\n `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.`,\n );\n }\n\n return new URL(url, origin).toString().replace(/\\/$/, \"\");\n }\n}\n\n/**\n * Serializes a flattened body payload as `application/x-www-form-urlencoded`.\n * Array values become repeated keys, matching the existing query-parameter\n * convention. A nested object/array *value* is JSON-stringified into a\n * single form value rather than expanded with bracket notation (e.g.\n * Stripe's own `metadata[key]=value` style) — correct for the flat scalar\n * properties that make up the overwhelming majority of real form-encoded\n * APIs (Stripe, Twilio), not a full form-encoding implementation.\n * `URLSearchParams` handles percent-encoding for free.\n */\nfunction encodeFormBody(payload: unknown): string {\n const params = new URLSearchParams();\n\n if (payload && typeof payload === \"object\" && !Array.isArray(payload)) {\n for (const [key, value] of Object.entries(\n payload as Record<string, unknown>,\n )) {\n if (value === undefined) {\n continue;\n }\n\n for (const item of Array.isArray(value) ? value : [value]) {\n params.append(\n key,\n item !== null && typeof item === \"object\"\n ? JSON.stringify(item)\n : String(item),\n );\n }\n }\n }\n\n return params.toString();\n}\n\nasync function resolveHeaders(\n headers: FromOpenAPIOptions[\"headers\"],\n): Promise<Record<string, string>> {\n if (!headers) {\n return {};\n }\n\n return typeof headers === \"function\" ? await headers() : { ...headers };\n}\n","import type { ParameterMapping } from \"./schemas.js\";\nimport type { HttpRoute } from \"./types.js\";\n\nexport type ResourceMapping =\n | {\n args: { name: string; required: boolean }[];\n kind: \"template\";\n uriTemplate: string;\n }\n | { kind: \"resource\"; uri: string };\n\n/**\n * Builds a static resource URI, or a resource template (URI + `arguments`),\n * for an eligible `GET` route — reusing the `parameterMap` and `required`\n * list `buildFlatSchema` already produced for it (path-always-required,\n * collision-suffixed flat keys) rather than re-deriving parameter\n * flattening from scratch.\n *\n * OpenAPI's `{petId}` path-parameter syntax is already valid RFC 6570 simple\n * string expansion, so the route's own path is reused verbatim except where\n * a flat key was collision-suffixed. Query parameters are appended as an\n * RFC 6570 query-expansion segment (`{?a,b}`), which `uri-templates`\n * (already a FastMCP dependency — see its own resource-template dispatch in\n * FastMCP.ts) parses and fills the same way it does path variables.\n */\nexport function buildResourceMapping(\n route: HttpRoute,\n name: string,\n parameterMap: Record<string, ParameterMapping>,\n requiredKeys: string[] | undefined,\n): ResourceMapping {\n const entries = Object.entries(parameterMap);\n\n if (entries.length === 0) {\n return { kind: \"resource\", uri: `openapi://${name}${route.path}` };\n }\n\n const required = new Set(requiredKeys ?? []);\n const args: { name: string; required: boolean }[] = [];\n const queryKeys: string[] = [];\n let path = route.path;\n\n for (const [flatKey, mapping] of entries) {\n if (mapping.in === \"path\") {\n if (flatKey !== mapping.name) {\n path = path.replace(`{${mapping.name}}`, `{${flatKey}}`);\n }\n } else {\n queryKeys.push(flatKey);\n }\n\n args.push({ name: flatKey, required: required.has(flatKey) });\n }\n\n const uriTemplate =\n `openapi://${name}${path}` +\n (queryKeys.length > 0 ? `{?${queryKeys.join(\",\")}}` : \"\");\n\n return { args, kind: \"template\", uriTemplate };\n}\n\n/**\n * Whether a `GET` route can become an MCP resource/resource template\n * instead of a tool, when `resources: true` is passed to `fromOpenAPI`. See\n * docs/openapi.md \"GET → resources\" for the reasoning behind each carve-out:\n *\n * - `header`/`cookie` parameters can't be expressed in a resource URI, and\n * MCP resource reads have no per-call side channel for them.\n * - An array-typed path/query parameter can't be represented consistently\n * between OpenAPI's query serialization (repeated keys) and RFC 6570's\n * array representation (comma-joined or `*`-exploded).\n *\n * A route failing either check falls through to the existing tool path —\n * this only ever *removes* operations from the tool list in favor of a\n * resource, never breaks one.\n */\nexport function isEligibleForResource(route: HttpRoute): boolean {\n return route.parameters.every((param) => {\n if (param.in === \"header\" || param.in === \"cookie\") {\n return false;\n }\n\n return param.schema?.type !== \"array\";\n });\n}\n","import type {\n BundledOpenApiDocument,\n HttpMethod,\n HttpRoute,\n OpenApiParameter,\n OpenApiParameterRef,\n OpenApiRequestBody,\n} from \"./types.js\";\n\nconst HTTP_METHODS: HttpMethod[] = [\"get\", \"put\", \"post\", \"delete\", \"patch\"];\n\n/**\n * Walks a bundled document's `paths` into a flat list of routes, resolving\n * any structural (non-schema) `$ref`s on parameters and request bodies —\n * e.g. `#/components/parameters/Limit` — against the same document.\n *\n * Bundling (see `loadSpec.ts`) guarantees every remaining `$ref` here is\n * local, so a plain JSON-pointer lookup is enough.\n */\nexport function extractRoutes(document: BundledOpenApiDocument): HttpRoute[] {\n const routes: HttpRoute[] = [];\n\n for (const [path, pathItem] of Object.entries(document.paths ?? {})) {\n const pathLevelParams = (pathItem.parameters ?? []).map((param) =>\n resolveRef<OpenApiParameter>(document, param),\n );\n\n for (const method of HTTP_METHODS) {\n const operation = pathItem[method];\n\n if (!operation) {\n continue;\n }\n\n const operationParams = (operation.parameters ?? []).map((param) =>\n resolveRef<OpenApiParameter>(document, param),\n );\n\n routes.push({\n deprecated: operation.deprecated ?? false,\n method,\n operationId: operation.operationId,\n parameters: mergeParameters(pathLevelParams, operationParams),\n path,\n requestBody: operation.requestBody\n ? resolveRef<OpenApiRequestBody>(document, operation.requestBody)\n : undefined,\n summary: operation.summary,\n tags: operation.tags ?? [],\n });\n }\n }\n\n return routes;\n}\n\nfunction mergeParameters(\n pathLevel: OpenApiParameter[],\n operationLevel: OpenApiParameter[],\n): OpenApiParameter[] {\n const overridden = new Set(\n operationLevel.map((param) => `${param.in}:${param.name}`),\n );\n\n return [\n ...pathLevel.filter(\n (param) => !overridden.has(`${param.in}:${param.name}`),\n ),\n ...operationLevel,\n ];\n}\n\nfunction resolveRef<TValue>(\n document: BundledOpenApiDocument,\n value: OpenApiParameterRef | TValue,\n): TValue {\n if (!value || typeof value !== \"object\" || !(\"$ref\" in value)) {\n return value;\n }\n\n const pointer = value.$ref;\n\n if (!pointer.startsWith(\"#/\")) {\n // Bundling should have already turned every external ref into a local\n // one — if this fires, swagger-parser's output shape has changed.\n throw new Error(`Unexpected external $ref after bundling: ${pointer}`);\n }\n\n // swagger-parser synthesizes these pointers as URI fragments (e.g. a path\n // like \"/pets/{petId}\" becomes \"~1pets~1%7BpetId%7D\"), so each segment\n // needs its \"~1\"/\"~0\" escapes undone *and* percent-decoding, in that order.\n const segments = pointer\n .slice(2)\n .split(\"/\")\n .map((segment) =>\n decodeURIComponent(segment.replaceAll(\"~1\", \"/\").replaceAll(\"~0\", \"~\")),\n );\n\n let node: unknown = document;\n\n for (const segment of segments) {\n node = (node as Record<string, unknown> | undefined)?.[segment];\n }\n\n return node as TValue;\n}\n","import type { JsonSchemaObject } from \"../jsonSchemaAdapter.js\";\nimport type {\n BundledOpenApiDocument,\n HttpRoute,\n OpenApiParameter,\n OpenApiRequestBody,\n OpenApiSchema,\n ParameterLocation,\n} from \"./types.js\";\n\n/**\n * Keys whose values are name-to-schema maps: their child keys are\n * author-chosen names rather than JSON Schema keywords.\n */\nconst SCHEMA_MAP_KEYS = new Set([\n \"$defs\",\n \"definitions\",\n \"dependentSchemas\",\n \"patternProperties\",\n \"properties\",\n]);\n\n/**\n * Keys whose values are arbitrary instance data rather than schemas. A\n * sample payload may well contain a \"$ref\" or \"nullable\" key of its own.\n */\nconst DATA_KEYS = new Set([\"const\", \"default\", \"enum\", \"example\", \"examples\"]);\n\n/**\n * Request body content types this module knows how to flatten and encode.\n * `application/json` wins when a route declares both.\n */\nexport const SUPPORTED_BODY_CONTENT_TYPES = [\n \"application/json\",\n \"application/x-www-form-urlencoded\",\n] as const;\n\nexport interface FlatSchemaResult {\n /** How the request body (if any properties were extracted) must be serialized. */\n bodyEncoding?: \"form\" | \"json\";\n flatSchema: JsonSchemaObject;\n parameterMap: Record<string, ParameterMapping>;\n /**\n * Set when `route.requestBody` declares a body this module can't carry:\n * either only in content type(s) it doesn't support (e.g.\n * `multipart/form-data`, `application/json-patch+json`,\n * `application/octet-stream`), or as `application/x-www-form-urlencoded`\n * with a schema that isn't a flat object, which form encoding can't\n * represent. Holds the content type the body was declared in. The caller\n * should not turn this route into a tool with a payload it can never\n * carry — see `fromOpenAPI.ts`.\n */\n unsupportedBodyContentType?: string;\n /**\n * Set when the request body's schema is not a flat object (e.g. an array,\n * or a bare non-object `$ref`) — the whole body is exposed as a single\n * property under this key, rather than flattened into individual\n * properties.\n */\n wholeBodyKey?: string;\n}\n\nexport interface ParameterMapping {\n in: \"body\" | ParameterLocation;\n name: string;\n}\n\ntype WalkMode = \"data\" | \"schema\" | \"schemaMap\";\n\n/**\n * Flattens a route's path/query/header/cookie parameters and request body\n * into a single tool input schema.\n *\n * Collision precedence ports the Python implementation's rule\n * (`utilities/openapi/schemas.py:_combine_schemas_and_map_params`): a name\n * that collides across path/query/header/cookie gets suffixed\n * `{name}__{location}`; a request body property with a colliding name always\n * keeps its bare name.\n *\n * `GET` never contributes a request body: `fetch` (and the Fetch spec in\n * general) rejects a body on a GET request, so a tool built from a spec's\n * (legal, if unusual) `GET` + `requestBody` operation would be permanently\n * broken. The request body is simply not flattened into the schema for such\n * a route, rather than surfacing a schema that can never actually be called.\n */\nexport function buildFlatSchema(\n route: HttpRoute,\n sharedDefs: Record<string, OpenApiSchema> | undefined,\n): FlatSchemaResult {\n const byName = new Map<string, OpenApiParameter[]>();\n\n for (const param of route.parameters) {\n const list = byName.get(param.name) ?? [];\n list.push(param);\n byName.set(param.name, list);\n }\n\n const {\n bodyEncoding,\n properties: bodyProperties,\n unsupportedBodyContentType,\n wholeBodyKey,\n } = extractBodyProperties(\n route.method === \"get\" ? undefined : route.requestBody,\n sharedDefs,\n );\n\n const properties: Record<string, OpenApiSchema> = {};\n const required: string[] = [];\n const parameterMap: Record<string, ParameterMapping> = {};\n\n for (const [name, occurrences] of byName) {\n const collides = occurrences.length > 1 || bodyProperties.has(name);\n\n for (const param of occurrences) {\n const key = collides ? `${name}__${param.in}` : name;\n\n properties[key] = rewriteComponentRefs(\n param.schema ?? { type: \"string\" },\n );\n parameterMap[key] = { in: param.in, name };\n\n if (param.in === \"path\" || param.required) {\n required.push(key);\n }\n }\n }\n\n for (const [name, { required: isRequired, schema }] of bodyProperties) {\n properties[name] = rewriteComponentRefs(schema);\n parameterMap[name] = { in: \"body\", name };\n\n if (isRequired) {\n required.push(name);\n }\n }\n\n const flatSchema: JsonSchemaObject = {\n additionalProperties: false,\n properties,\n type: \"object\",\n ...(required.length > 0 ? { required } : {}),\n };\n\n // Only the definitions this tool's own schema actually (transitively)\n // references — embedding the whole document's components.schemas into\n // every single tool would multiply the tools/list payload size by the\n // tool count for no benefit.\n const usedDefs = sharedDefs && filterReferencedDefs(properties, sharedDefs);\n\n if (usedDefs) {\n flatSchema.$defs = usedDefs;\n }\n\n return {\n bodyEncoding,\n flatSchema,\n parameterMap,\n unsupportedBodyContentType,\n wholeBodyKey,\n };\n}\n\nexport function buildSharedDefs(\n document: BundledOpenApiDocument,\n): Record<string, OpenApiSchema> | undefined {\n const schemas = document.components?.schemas;\n\n if (!schemas || Object.keys(schemas).length === 0) {\n return undefined;\n }\n\n return rewriteNode(schemas, \"schemaMap\") as Record<string, OpenApiSchema>;\n}\n\n/**\n * Rewrites `$ref`s pointing at `#/components/schemas/...` to `#/$defs/...`,\n * so a per-tool schema that contains one can be handed to AJV standalone,\n * alongside a `$defs` object built from the document's `components.schemas`\n * (see `buildSharedDefs`). Ports the equivalent rewrite from the Python\n * implementation (`utilities/openapi/schemas.py:_replace_ref_with_defs`).\n *\n * Also normalizes OpenAPI 3.0's `nullable` keyword (see `normalizeNullable`),\n * since real specs carry both.\n */\nexport function rewriteComponentRefs<TValue>(value: TValue): TValue {\n return rewriteNode(value, \"schema\") as TValue;\n}\n\nfunction childMode(key: string): WalkMode {\n if (DATA_KEYS.has(key)) {\n return \"data\";\n }\n\n return SCHEMA_MAP_KEYS.has(key) ? \"schemaMap\" : \"schema\";\n}\n\nfunction componentSchemaName(ref: string): string | undefined {\n for (const prefix of [\"#/components/schemas/\", \"#/$defs/\"]) {\n if (ref.startsWith(prefix)) {\n return ref.slice(prefix.length);\n }\n }\n\n return undefined;\n}\n\n/**\n * Picks the request body's content type and flattens its schema.\n *\n * `application/json` wins if a route declares both it and\n * `application/x-www-form-urlencoded` (a fixed preference, not declaration\n * order — the latter isn't a reliable signal). A route whose body is only\n * declared under a content type this module doesn't handle at all (e.g.\n * `multipart/form-data`) reports `unsupportedBodyContentType` instead of\n * silently returning an empty property map — that emptiness is exactly what\n * a real Stripe/Twilio operation (both form-urlencoded-only) looked like\n * before this function read anything but JSON, and it produced a tool with\n * no way to carry its actual payload. A bare `content: {}` (no content\n * types at all) still means \"no body,\" not \"unsupported\" — and so does a\n * supported content type with no `schema` at all (a legal, if unusual,\n * \"any JSON body\" declaration): the content type itself is fine, there's\n * just nothing to flatten.\n *\n * A form-urlencoded body is very often declared as a bare `$ref` to a\n * component schema rather than inline — FastAPI emits\n * `#/components/schemas/Body_<operation>` for every form endpoint, and\n * Box's OAuth token/refresh/revoke operations do the same. The document is\n * bundled, not dereferenced (see `loadSpec.ts`), so that `$ref` is resolved\n * here against `sharedDefs` before deciding whether the body is a flat\n * object. Only the form path needs this: a JSON body that isn't a flat\n * object falls back to a single whole-body property, which a `$ref`\n * satisfies as-is.\n *\n * A non-object body (array, `$ref` to a scalar/array, etc.) can't be\n * form-urlencoded at all — form encoding is inherently flat key/value pairs\n * — so that combination is also reported as unsupported, rather than\n * `encodeFormBody` (requestBuilder.ts) silently sending an empty body for\n * data it has no way to represent.\n */\nfunction extractBodyProperties(\n requestBody: OpenApiRequestBody | undefined,\n sharedDefs: Record<string, OpenApiSchema> | undefined,\n): {\n bodyEncoding?: \"form\" | \"json\";\n properties: Map<string, { required: boolean; schema: OpenApiSchema }>;\n unsupportedBodyContentType?: string;\n wholeBodyKey?: string;\n} {\n const properties = new Map<\n string,\n { required: boolean; schema: OpenApiSchema }\n >();\n\n const content = requestBody?.content;\n\n if (!content) {\n return { properties };\n }\n\n const hasJson = \"application/json\" in content;\n const hasForm = \"application/x-www-form-urlencoded\" in content;\n\n if (!hasJson && !hasForm) {\n const contentTypes = Object.keys(content);\n\n return contentTypes.length > 0\n ? { properties, unsupportedBodyContentType: contentTypes[0] }\n : { properties };\n }\n\n const bodyEncoding: \"form\" | \"json\" = hasJson ? \"json\" : \"form\";\n const declaredSchema = hasJson\n ? content[\"application/json\"]?.schema\n : content[\"application/x-www-form-urlencoded\"]?.schema;\n\n if (!declaredSchema) {\n // The content type is declared and supported; it just has no schema\n // (an unconstrained body) — nothing to flatten, but not unsupported.\n return { bodyEncoding, properties };\n }\n\n const schema =\n bodyEncoding === \"form\"\n ? resolveComponentRef(declaredSchema, sharedDefs)\n : declaredSchema;\n\n const schemaProperties = schema.properties as\n | Record<string, OpenApiSchema>\n | undefined;\n\n if (schema.type === \"object\" && schemaProperties) {\n const requiredNames = new Set(\n (schema.required as string[] | undefined) ?? [],\n );\n\n for (const [name, propertySchema] of Object.entries(schemaProperties)) {\n properties.set(name, {\n required: requiredNames.has(name),\n schema: propertySchema,\n });\n }\n\n return { bodyEncoding, properties };\n }\n\n if (bodyEncoding === \"form\") {\n return {\n properties,\n unsupportedBodyContentType: \"application/x-www-form-urlencoded\",\n };\n }\n\n // Non-object JSON body (array, bare $ref to a scalar/array, etc.) — expose\n // the whole thing as a single \"body\" property rather than flattening it.\n properties.set(\"body\", {\n required: requestBody?.required ?? false,\n schema,\n });\n\n return { bodyEncoding, properties, wholeBodyKey: \"body\" };\n}\n\n/**\n * Walks a schema fragment for `#/$defs/Name` refs and returns just those\n * definitions (transitively — a referenced def may itself reference\n * others), or `undefined` if none are referenced.\n */\nfunction filterReferencedDefs(\n node: unknown,\n allDefs: Record<string, OpenApiSchema>,\n): Record<string, OpenApiSchema> | undefined {\n const referenced = new Set<string>();\n const stack: unknown[] = [node];\n\n while (stack.length > 0) {\n const current = stack.pop();\n\n if (Array.isArray(current)) {\n stack.push(...current);\n continue;\n }\n\n if (!current || typeof current !== \"object\") {\n continue;\n }\n\n for (const [key, value] of Object.entries(\n current as Record<string, unknown>,\n )) {\n if (\n key === \"$ref\" &&\n typeof value === \"string\" &&\n value.startsWith(\"#/$defs/\")\n ) {\n const name = value.slice(\"#/$defs/\".length);\n\n if (allDefs[name] && !referenced.has(name)) {\n referenced.add(name);\n stack.push(allDefs[name]);\n }\n\n continue;\n }\n\n stack.push(value);\n }\n }\n\n if (referenced.size === 0) {\n return undefined;\n }\n\n return Object.fromEntries(\n [...referenced].map((name) => [name, allDefs[name]]),\n );\n}\n\n/**\n * OpenAPI 3.0's `nullable` keyword only makes sense alongside a sibling\n * `type`, which it widens (`nullable: true` + `type: \"string\"` means\n * \"string or null\") — but it is not itself standard JSON Schema. AJV\n * recognizes the keyword and throws ('\"nullable\" cannot be used without\n * \"type\"') if it finds one with no `type` on the same node, which real\n * specs do produce (e.g. `nullable` sibling to `oneOf`/`allOf`/`$ref`\n * instead of `type`, as in Box's API). Folded into `type` where there is\n * one to widen, dropped otherwise.\n */\nfunction normalizeNullable(\n schema: Record<string, unknown>,\n): Record<string, unknown> {\n if (!(\"nullable\" in schema)) {\n return schema;\n }\n\n const { nullable, type, ...rest } = schema;\n\n if (nullable !== true) {\n return rest;\n }\n\n if (typeof type === \"string\") {\n return { ...rest, type: [type, \"null\"] };\n }\n\n if (Array.isArray(type)) {\n return { ...rest, type: [...new Set([\"null\", ...type])] };\n }\n\n return rest;\n}\n\n/**\n * Follows a bare `$ref` into `components.schemas` — or its rewritten\n * `#/$defs/` form, which is what `sharedDefs` entries themselves carry —\n * until it reaches a concrete schema. A dangling or cyclic reference is\n * returned as-is rather than failing the whole conversion.\n */\nfunction resolveComponentRef(\n schema: OpenApiSchema,\n sharedDefs: Record<string, OpenApiSchema> | undefined,\n): OpenApiSchema {\n const seen = new Set<string>();\n let current = schema;\n let ref = current.$ref;\n\n while (typeof ref === \"string\") {\n const name = componentSchemaName(ref);\n const target = name === undefined ? undefined : sharedDefs?.[name];\n\n if (name === undefined || target === undefined || seen.has(name)) {\n break;\n }\n\n seen.add(name);\n current = target;\n ref = current.$ref;\n }\n\n return current;\n}\n\n/**\n * Walks a schema fragment, distinguishing the three kinds of node it can\n * reach — because only one of them is a schema whose keys are JSON Schema\n * keywords:\n *\n * - `\"schema\"` — a schema object. `$ref`/`nullable` here are keywords.\n * - `\"schemaMap\"` — a name-to-schema map (`properties`, `$defs`, ...). Its\n * keys are author-chosen names, so a property literally named `nullable`\n * or `$ref` is a field, not a keyword, and must survive untouched.\n * - `\"data\"` — arbitrary values (`default`, `enum`, `example`, ...). Not\n * schemas at all; passed through verbatim.\n *\n * Walking every node as a schema (as this originally did) silently deletes\n * a property named `nullable` from the generated tool schema, since\n * `normalizeNullable` cannot tell the keyword from a same-named field.\n */\nfunction rewriteNode(value: unknown, mode: WalkMode): unknown {\n if (mode === \"data\") {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => rewriteNode(item, \"schema\"));\n }\n\n if (!value || typeof value !== \"object\") {\n return value;\n }\n\n const entries = Object.entries(value as Record<string, unknown>).map(\n ([key, entryValue]): [string, unknown] => {\n if (mode === \"schemaMap\") {\n return [key, rewriteNode(entryValue, \"schema\")];\n }\n\n if (\n key === \"$ref\" &&\n typeof entryValue === \"string\" &&\n entryValue.startsWith(\"#/components/schemas/\")\n ) {\n return [key, entryValue.replace(\"#/components/schemas/\", \"#/$defs/\")];\n }\n\n return [key, rewriteNode(entryValue, childMode(key))];\n },\n );\n\n const rewritten = Object.fromEntries(entries) as Record<string, unknown>;\n\n return mode === \"schemaMap\" ? rewritten : normalizeNullable(rewritten);\n}\n","import type {\n FromOpenAPIOptions,\n HttpMethod,\n HttpRoute,\n OperationSummary,\n} from \"./types.js\";\n\n/**\n * If neither `include`/`exclude` nor `maxTools` was given, and a spec still\n * produces more operations than this, `selectRoutes` throws rather than\n * silently generating a wall of tools most clients can't usefully work with.\n */\nexport const DEFAULT_MAX_OPERATIONS = 40;\n\nconst METHOD_PRIORITY: Record<HttpMethod, number> = {\n delete: 4,\n get: 0,\n patch: 3,\n post: 1,\n put: 2,\n};\n\n/**\n * Filters and orders routes into the set that becomes tools.\n *\n * Deprecated operations are excluded by default. Ordering is deterministic\n * (method priority GET→POST→PUT→PATCH→DELETE, then path) so that, combined\n * with `maxTools`, truncation is legible rather than arbitrary.\n */\nexport function selectRoutes(\n routes: HttpRoute[],\n options: Pick<FromOpenAPIOptions, \"exclude\" | \"include\" | \"maxTools\">,\n): HttpRoute[] {\n let selected = routes.filter((route) => !route.deprecated);\n\n if (options.include) {\n const include = options.include;\n selected = selected.filter((route) => include(toSummary(route)));\n }\n\n if (options.exclude) {\n const exclude = options.exclude;\n selected = selected.filter((route) => !exclude(toSummary(route)));\n }\n\n selected = [...selected].sort((a, b) => {\n const byMethod = METHOD_PRIORITY[a.method] - METHOD_PRIORITY[b.method];\n return byMethod !== 0 ? byMethod : a.path.localeCompare(b.path);\n });\n\n const noSelectionGiven = !options.include && !options.exclude;\n\n if (\n noSelectionGiven &&\n options.maxTools === undefined &&\n selected.length > DEFAULT_MAX_OPERATIONS\n ) {\n throw new Error(\n `fromOpenAPI found ${selected.length} operations, which exceeds the default limit of ${DEFAULT_MAX_OPERATIONS}. ` +\n \"This is a deliberate stop, not a bug: turning every operation in a large spec into a tool produces a tool list most MCP clients can't use well. \" +\n \"Pass `include`/`exclude` to choose the operations you actually want, or `maxTools` to raise this limit explicitly.\",\n );\n }\n\n if (options.maxTools !== undefined && selected.length > options.maxTools) {\n throw new Error(\n `fromOpenAPI found ${selected.length} operations, which exceeds maxTools (${options.maxTools}). ` +\n \"Narrow the spec with `include`/`exclude`, or raise `maxTools`.\",\n );\n }\n\n return selected;\n}\n\nfunction toSummary(route: HttpRoute): OperationSummary {\n return {\n deprecated: route.deprecated,\n method: route.method,\n operationId: route.operationId,\n path: route.path,\n tags: route.tags,\n };\n}\n","import type { ResourceResult } from \"../FastMCP.js\";\nimport type { ParameterMapping } from \"./schemas.js\";\nimport type { HttpRoute } from \"./types.js\";\nimport type { FromOpenAPIOptions } from \"./types.js\";\n\nimport { FastMCP } from \"../FastMCP.js\";\nimport { jsonSchemaAdapter } from \"../jsonSchemaAdapter.js\";\nimport { loadSpec } from \"./loadSpec.js\";\nimport { generateNames } from \"./naming.js\";\nimport { executeRequest } from \"./requestBuilder.js\";\nimport {\n buildResourceMapping,\n isEligibleForResource,\n} from \"./resourceMapping.js\";\nimport { extractRoutes } from \"./routes.js\";\nimport { buildFlatSchema, buildSharedDefs } from \"./schemas.js\";\nimport { selectRoutes } from \"./selection.js\";\n\n/**\n * Converts an OpenAPI 3.x document into an MCP server, one tool (or, with\n * `resources: true`, resource/resource template for an eligible `GET`) per\n * operation.\n *\n * See docs/openapi.md for the full option reference and known limitations.\n */\nexport async function fromOpenAPI(\n options: FromOpenAPIOptions,\n): Promise<FastMCP> {\n const { document, origin } = await loadSpec(options.spec);\n const routes = extractRoutes(document);\n const selected = selectRoutes(routes, options);\n const names = generateNames(selected, options.mcpNames);\n const sharedDefs = buildSharedDefs(document);\n\n const server =\n options.server ??\n new FastMCP({\n name: options.name ?? document.info?.title ?? \"OpenAPI Server\",\n version: options.version ?? \"1.0.0\",\n });\n\n const skippedOperations: {\n contentType: string;\n method: string;\n path: string;\n }[] = [];\n\n for (const route of selected) {\n const name = names.get(route);\n\n if (!name) {\n continue;\n }\n\n const {\n bodyEncoding,\n flatSchema,\n parameterMap,\n unsupportedBodyContentType,\n wholeBodyKey,\n } = buildFlatSchema(route, sharedDefs);\n\n const execOptions = {\n baseUrlOverride: options.baseUrl,\n fetchImpl: options.fetch ?? fetch,\n headers: options.headers,\n origin,\n parameterMap,\n route,\n servers: document.servers,\n };\n\n if (\n options.resources &&\n route.method === \"get\" &&\n isEligibleForResource(route)\n ) {\n registerResource(\n server,\n route,\n name,\n parameterMap,\n flatSchema.required,\n execOptions,\n );\n continue;\n }\n\n if (unsupportedBodyContentType) {\n skippedOperations.push({\n contentType: unsupportedBodyContentType,\n method: route.method,\n path: route.path,\n });\n continue;\n }\n\n server.addTool({\n description:\n route.summary ?? `${route.method.toUpperCase()} ${route.path}`,\n execute: async (args) =>\n executeRequest({\n ...execOptions,\n args: args as Record<string, unknown>,\n bodyEncoding,\n wholeBodyKey,\n }),\n name,\n parameters: jsonSchemaAdapter(flatSchema),\n });\n }\n\n if (skippedOperations.length > 0) {\n console.warn(\n \"fromOpenAPI: skipped \" +\n `${skippedOperations.length} operation(s) whose request body can't be turned into tool parameters ` +\n \"(supported: application/json, or application/x-www-form-urlencoded with a flat object schema): \" +\n skippedOperations\n .map(\n (op) => `${op.method.toUpperCase()} ${op.path} (${op.contentType})`,\n )\n .join(\", \"),\n );\n }\n\n return server;\n}\n\nfunction registerResource(\n server: FastMCP,\n route: HttpRoute,\n name: string,\n parameterMap: Record<string, ParameterMapping>,\n requiredKeys: string[] | undefined,\n execOptions: Omit<\n Parameters<typeof executeRequest>[0],\n \"args\" | \"bodyEncoding\" | \"wholeBodyKey\"\n >,\n): void {\n const mapping = buildResourceMapping(route, name, parameterMap, requiredKeys);\n const description = route.summary ?? `GET ${route.path}`;\n\n if (mapping.kind === \"resource\") {\n server.addResource({\n description,\n load: async () =>\n wrapAsResourceResult(\n await executeRequest({ ...execOptions, args: {} }),\n ),\n name,\n uri: mapping.uri,\n });\n\n return;\n }\n\n server.addResourceTemplate({\n arguments: mapping.args,\n description,\n load: async (args) =>\n wrapAsResourceResult(\n await executeRequest({\n ...execOptions,\n args: args as Record<string, unknown>,\n }),\n ),\n name,\n uriTemplate: mapping.uriTemplate,\n });\n}\n\nfunction wrapAsResourceResult(text: string): ResourceResult {\n try {\n JSON.parse(text);\n return { mimeType: \"application/json\", text };\n } catch {\n return { mimeType: \"text/plain\", text };\n }\n}\n"],"mappings":"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","names":[]}
|