fastmcp 4.20.12 → 4.20.14
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
CHANGED
|
@@ -257,10 +257,7 @@ var HTTP_METHODS = ["get", "put", "post", "delete", "patch"];
|
|
|
257
257
|
function extractRoutes(document) {
|
|
258
258
|
const routes = [];
|
|
259
259
|
for (const [path, rawPathItem] of Object.entries(_nullishCoalesce(document.paths, () => ( {})))) {
|
|
260
|
-
const pathItem =
|
|
261
|
-
...resolveRef(document, rawPathItem),
|
|
262
|
-
...rawPathItem
|
|
263
|
-
};
|
|
260
|
+
const pathItem = resolvePathItem(document, rawPathItem);
|
|
264
261
|
const pathLevelParams = (_nullishCoalesce(pathItem.parameters, () => ( []))).map(
|
|
265
262
|
(param) => resolveRef(document, param)
|
|
266
263
|
);
|
|
@@ -280,6 +277,9 @@ function extractRoutes(document) {
|
|
|
280
277
|
path,
|
|
281
278
|
requestBody: operation.requestBody ? resolveRef(document, operation.requestBody) : void 0,
|
|
282
279
|
responses: resolveResponses(document, operation.responses),
|
|
280
|
+
servers: [operation.servers, pathItem.servers, document.servers].find(
|
|
281
|
+
(servers) => _optionalChain([servers, 'optionalAccess', _13 => _13.length])
|
|
282
|
+
),
|
|
283
283
|
summary: operation.summary,
|
|
284
284
|
tags: _nullishCoalesce(operation.tags, () => ( []))
|
|
285
285
|
});
|
|
@@ -298,6 +298,16 @@ function mergeParameters(pathLevel, operationLevel) {
|
|
|
298
298
|
...operationLevel
|
|
299
299
|
];
|
|
300
300
|
}
|
|
301
|
+
function resolvePathItem(document, pathItem) {
|
|
302
|
+
const visited = /* @__PURE__ */ new Set();
|
|
303
|
+
let resolved = pathItem;
|
|
304
|
+
while (resolved.$ref && !visited.has(resolved.$ref)) {
|
|
305
|
+
visited.add(resolved.$ref);
|
|
306
|
+
const { $ref, ...siblings } = resolved;
|
|
307
|
+
resolved = { ...resolveRef(document, { $ref }), ...siblings };
|
|
308
|
+
}
|
|
309
|
+
return resolved;
|
|
310
|
+
}
|
|
301
311
|
function resolveRef(document, value) {
|
|
302
312
|
if (!value || typeof value !== "object" || !("$ref" in value)) {
|
|
303
313
|
return value;
|
|
@@ -311,7 +321,7 @@ function resolveRef(document, value) {
|
|
|
311
321
|
);
|
|
312
322
|
let node = document;
|
|
313
323
|
for (const segment of segments) {
|
|
314
|
-
node = _optionalChain([node, 'optionalAccess',
|
|
324
|
+
node = _optionalChain([node, 'optionalAccess', _14 => _14[segment]]);
|
|
315
325
|
}
|
|
316
326
|
return node;
|
|
317
327
|
}
|
|
@@ -395,7 +405,7 @@ function buildFlatSchema(route, sharedDefs) {
|
|
|
395
405
|
};
|
|
396
406
|
}
|
|
397
407
|
function buildSharedDefs(document) {
|
|
398
|
-
const schemas = _optionalChain([document, 'access',
|
|
408
|
+
const schemas = _optionalChain([document, 'access', _15 => _15.components, 'optionalAccess', _16 => _16.schemas]);
|
|
399
409
|
if (!schemas || Object.keys(schemas).length === 0) {
|
|
400
410
|
return void 0;
|
|
401
411
|
}
|
|
@@ -407,7 +417,7 @@ function buildOutputSchema(route, sharedDefs) {
|
|
|
407
417
|
const successEntry = Object.entries(_nullishCoalesce(route.responses, () => ( {}))).find(
|
|
408
418
|
([code]) => SUCCESS_STATUS_PATTERN.test(code)
|
|
409
419
|
);
|
|
410
|
-
const declaredSchema = _optionalChain([successEntry, 'optionalAccess',
|
|
420
|
+
const declaredSchema = _optionalChain([successEntry, 'optionalAccess', _17 => _17[1], 'access', _18 => _18.content, 'optionalAccess', _19 => _19["application/json"], 'optionalAccess', _20 => _20.schema]);
|
|
411
421
|
if (!declaredSchema) {
|
|
412
422
|
return void 0;
|
|
413
423
|
}
|
|
@@ -449,7 +459,7 @@ function componentSchemaName(ref) {
|
|
|
449
459
|
}
|
|
450
460
|
function extractBodyProperties(requestBody, sharedDefs) {
|
|
451
461
|
const properties = /* @__PURE__ */ new Map();
|
|
452
|
-
const content = _optionalChain([requestBody, 'optionalAccess',
|
|
462
|
+
const content = _optionalChain([requestBody, 'optionalAccess', _21 => _21.content]);
|
|
453
463
|
if (!content) {
|
|
454
464
|
return { properties };
|
|
455
465
|
}
|
|
@@ -460,7 +470,7 @@ function extractBodyProperties(requestBody, sharedDefs) {
|
|
|
460
470
|
return contentTypes.length > 0 ? { properties, unsupportedBodyContentType: contentTypes[0] } : { properties };
|
|
461
471
|
}
|
|
462
472
|
const bodyEncoding = hasJson ? "json" : "form";
|
|
463
|
-
const declaredSchema = hasJson ? _optionalChain([content, 'access',
|
|
473
|
+
const declaredSchema = hasJson ? _optionalChain([content, 'access', _22 => _22["application/json"], 'optionalAccess', _23 => _23.schema]) : _optionalChain([content, 'access', _24 => _24["application/x-www-form-urlencoded"], 'optionalAccess', _25 => _25.schema]);
|
|
464
474
|
if (!declaredSchema) {
|
|
465
475
|
return { bodyEncoding, properties };
|
|
466
476
|
}
|
|
@@ -485,7 +495,7 @@ function extractBodyProperties(requestBody, sharedDefs) {
|
|
|
485
495
|
};
|
|
486
496
|
}
|
|
487
497
|
properties.set("body", {
|
|
488
|
-
required: _nullishCoalesce(_optionalChain([requestBody, 'optionalAccess',
|
|
498
|
+
required: _nullishCoalesce(_optionalChain([requestBody, 'optionalAccess', _26 => _26.required]), () => ( false)),
|
|
489
499
|
schema
|
|
490
500
|
});
|
|
491
501
|
return { bodyEncoding, properties, wholeBodyKey: "body" };
|
|
@@ -545,7 +555,7 @@ function resolveComponentRef(schema, sharedDefs) {
|
|
|
545
555
|
let ref = current.$ref;
|
|
546
556
|
while (typeof ref === "string") {
|
|
547
557
|
const name = componentSchemaName(ref);
|
|
548
|
-
const target = name === void 0 ? void 0 : _optionalChain([sharedDefs, 'optionalAccess',
|
|
558
|
+
const target = name === void 0 ? void 0 : _optionalChain([sharedDefs, 'optionalAccess', _27 => _27[name]]);
|
|
549
559
|
if (name === void 0 || target === void 0 || seen.has(name)) {
|
|
550
560
|
break;
|
|
551
561
|
}
|
|
@@ -634,7 +644,7 @@ async function fromOpenAPI(options) {
|
|
|
634
644
|
const names = generateNames(selected, options.mcpNames);
|
|
635
645
|
const sharedDefs = buildSharedDefs(document);
|
|
636
646
|
const server = _nullishCoalesce(options.server, () => ( new (0, _chunkMA2QZQFNcjs.FastMCP)({
|
|
637
|
-
name: _nullishCoalesce(_nullishCoalesce(options.name, () => ( _optionalChain([document, 'access',
|
|
647
|
+
name: _nullishCoalesce(_nullishCoalesce(options.name, () => ( _optionalChain([document, 'access', _28 => _28.info, 'optionalAccess', _29 => _29.title]))), () => ( "OpenAPI Server")),
|
|
638
648
|
version: _nullishCoalesce(options.version, () => ( "1.0.0"))
|
|
639
649
|
})));
|
|
640
650
|
const skippedOperations = [];
|
|
@@ -657,7 +667,7 @@ async function fromOpenAPI(options) {
|
|
|
657
667
|
origin,
|
|
658
668
|
parameterMap,
|
|
659
669
|
route,
|
|
660
|
-
servers:
|
|
670
|
+
servers: route.servers
|
|
661
671
|
};
|
|
662
672
|
if (options.resources && route.method === "get" && isEligibleForResource(route)) {
|
|
663
673
|
registerResource(
|
|
@@ -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/resourceMapping.ts","../../src/openapi/routes.ts","../../src/openapi/schemas.ts","../../src/openapi/selection.ts","../../src/openapi/fromOpenAPI.ts"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACF,yDAA8B;AAC9B,iCAA8B;AAC9B;AACA;ACPA,0HAA0B;AAuB1B,MAAA,SAAsB,QAAA,CACpB,IAAA,EACqB;AACrB,EAAA,MAAM,SAAA,EAAY,MAAM,uBAAA,CAAc,MAAA;AAAA,IACpC;AAAA,EACF,CAAA;AAEA,EAAA,GAAA,CAAI,iBAAC,QAAA,mBAAS,OAAA,6BAAS,UAAA,mBAAW,IAAI,GAAA,EAAG;AACvC,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,uDAAA,oCACE,QAAA,CAAS,OAAA,UAAW,QAAA,CAAS,SAAA,UAAW,2BAC1C,CAAA,gCAAA;AAAA,IACF,CAAA;AAAA,EACF;AAEA,EAAA,OAAO;AAAA,IACL,QAAA;AAAA,IACA,MAAA,EAAQ,OAAO,KAAA,IAAS,SAAA,GAAY,SAAA,CAAU,IAAI,EAAA,EAAI,KAAA,EAAO,KAAA;AAAA,EAC/D,CAAA;AACF;AAEA,SAAS,SAAA,CAAU,KAAA,EAAwB;AACzC,EAAA,OAAO,KAAA,CAAM,UAAA,CAAW,SAAS,EAAA,GAAK,KAAA,CAAM,UAAA,CAAW,UAAU,CAAA;AACnE;ADpBA;AACA;AEzBA,IAAM,gBAAA,EAAkB,EAAA;AAGxB,IAAM,gBAAA,EAAkB,gBAAA,EAAkB,CAAA;AAmBnC,SAAS,aAAA,CACd,MAAA,EACA,QAAA,EACwB;AACxB,EAAA,MAAM,MAAA,kBAAQ,IAAI,GAAA,CAAuB,CAAA;AACzC,EAAA,MAAM,KAAA,kBAAO,IAAI,GAAA,CAAY,CAAA;AAE7B,EAAA,IAAA,CAAA,MAAW,MAAA,GAAS,MAAA,EAAQ;AAC1B,IAAA,MAAM,KAAA,EAAO,OAAA,CAAQ,WAAA,CAAY,KAAA,EAAO,QAAQ,CAAC,CAAA;AACjD,IAAA,IAAI,UAAA,EAAY,IAAA;AAChB,IAAA,IAAI,OAAA,EAAS,CAAA;AAEb,IAAA,MAAA,CAAO,IAAA,CAAK,GAAA,CAAI,SAAS,CAAA,EAAG;AAC1B,MAAA,OAAA,GAAU,CAAA;AACV,MAAA,UAAA,EAAY,CAAA,EAAA;AACd,IAAA;AAES,IAAA;AACC,IAAA;AACZ,EAAA;AAEO,EAAA;AACT;AAES;AAIG,EAAA;AACD,IAAA;AACT,EAAA;AAEa,EAAA;AACf;AAEiB;AACF,EAAA;AAME,EAAA;AACjB;AFXmB;AACA;AG/BG;AAGJ,EAAA;AACN,IAAA;AACA,IAAA;AACA,IAAA;AACV,EAAA;AAEM,EAAA;AACQ,EAAA;AAME,EAAA;AACV,EAAA;AAEW,EAAA;AACT,IAAA;AAED,IAAA;AACH,MAAA;AACF,IAAA;AAEQ,IAAA;AACD,MAAA;AACO,QAAA;AACV,QAAA;AACG,MAAA;AACG,QAAA;AACE,QAAA;AACN,UAAA;AACA,UAAA;AAGF,QAAA;AACA,QAAA;AACF,MAAA;AACK,MAAA;AACK,QAAA;AACR,QAAA;AACG,MAAA;AACQ,QAAA;AACX,QAAA;AACG,MAAA;AACH,QAAA;AACA,QAAA;AACJ,IAAA;AACF,EAAA;AAEW,EAAA;AAEC,EAAA;AACE,IAAA;AACd,EAAA;AAEgB,EAAA;AACH,EAAA;AAET,EAAA;AAEY,EAAA;AACR,IAAA;AAIM,IAAA;AACG,MAAA;AACH,QAAA;AACV,MAAA;AAEO,MAAA;AACF,IAAA;AACQ,MAAA;AACH,QAAA;AACV,MAAA;AAEY,MAAA;AACd,IAAA;AACF,EAAA;AAEiB,EAAA;AACf,IAAA;AACA,IAAA;AACQ,IAAA;AACT,EAAA;AAEY,EAAA;AAEC,EAAA;AACF,IAAA;AACG,MAAA;AACb,IAAA;AACF,EAAA;AAEa,EAAA;AACP,IAAA;AACoB,MAAA;AACb,MAAA;AACH,IAAA;AACG,MAAA;AACX,IAAA;AACF,EAAA;AAEc,EAAA;AAChB;AAQgB;AAKG,EAAA;AACR,IAAA;AACT,EAAA;AAEe,EAAA;AAEF,EAAA;AACD,IAAA;AACR,MAAA;AACF,IAAA;AACF,EAAA;AAEiB,EAAA;AAEL,EAAA;AACA,IAAA;AACZ,EAAA;AAEI,EAAA;AACa,IAAA;AACT,EAAA;AACO,IAAA;AACD,MAAA;AACR,QAAA;AACF,MAAA;AACF,IAAA;AAEe,IAAA;AACjB,EAAA;AACF;AAoBS;AAKG,EAAA;AACG,IAAA;AACI,MAAA;AACX,QAAA;AACK,MAAA;AACE,QAAA;AACT,MAAA;AACF,IAAA;AAEA,IAAA;AACF,EAAA;AAEc,EAAA;AACA,IAAA;AACV,MAAA;AACC,IAAA;AACG,MAAA;AACF,QAAA;AACF,MAAA;AACF,IAAA;AAEA,IAAA;AACF,EAAA;AAEc,EAAA;AAChB;AAUS;AAMO,EAAA;AACZ,IAAA;AACA,IAAA;AACF,EAAA;AAEc,EAAA;AACE,IAAA;AACR,IAAA;AACO,IAAA;AACb,IAAA;AACF,EAAA;AAEW,EAAA;AACI,IAAA;AACf,EAAA;AACF;AAWS;AACQ,EAAA;AAEA,EAAA;AACD,IAAA;AACV,MAAA;AACC,IAAA;AACG,MAAA;AACF,QAAA;AACF,MAAA;AAEA,MAAA;AACF,IAAA;AACF,EAAA;AAEc,EAAA;AAChB;AAEe;AAGC,EAAA;AACJ,IAAA;AACV,EAAA;AAEc,EAAA;AAChB;AHxEmB;AACA;AIrMH;AAME,EAAA;AAEJ,EAAA;AACK,IAAA;AACjB,EAAA;AAEiB,EAAA;AACoC,EAAA;AAC/C,EAAA;AACW,EAAA;AAEL,EAAA;AACE,IAAA;AACN,MAAA;AACK,QAAA;AACT,MAAA;AACK,IAAA;AACK,MAAA;AACZ,IAAA;AAEY,IAAA;AACd,EAAA;AAEM,EAAA;AAIS,EAAA;AACjB;AAiBgB;AACD,EAAA;AACD,IAAA;AACD,MAAA;AACT,IAAA;AAEa,IAAA;AACd,EAAA;AACH;AJyKmB;AACA;AKnPgB;AAUnB;AACe,EAAA;AAEjB,EAAA;AAGJ,IAAA;AACD,MAAA;AACA,MAAA;AACL,IAAA;AACM,IAAA;AACJ,MAAA;AACF,IAAA;AAEW,IAAA;AACH,MAAA;AAED,MAAA;AACH,QAAA;AACF,MAAA;AAEM,MAAA;AACJ,QAAA;AACF,MAAA;AAEY,MAAA;AACV,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AAGW,QAAA;AACF,QAAA;AACH,QAAA;AACP,MAAA;AACH,IAAA;AACF,EAAA;AAEO,EAAA;AACT;AAES;AAID,EAAA;AACW,IAAA;AACjB,EAAA;AAEO,EAAA;AACQ,IAAA;AACC,MAAA;AACd,IAAA;AACG,IAAA;AACL,EAAA;AACF;AAES;AAIO,EAAA;AACL,IAAA;AACT,EAAA;AAEgB,EAAA;AAEH,EAAA;AAGD,IAAA;AACZ,EAAA;AAKiB,EAAA;AAIb,IAAA;AACF,EAAA;AAEkB,EAAA;AAET,EAAA;AAC8C,IAAA;AACzD,EAAA;AAEO,EAAA;AACT;AAGS;AAMF,EAAA;AACI,IAAA;AACT,EAAA;AAEc,EAAA;AACG,IAAA;AACb,MAAA;AAC4B,MAAA;AAC7B,IAAA;AACH,EAAA;AACF;ALmMmB;AACA;AM3Tb;AACJ,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD;AAMiB;AAmBC;AA6DH;AAIC,EAAA;AAEJ,EAAA;AACI,IAAA;AACE,IAAA;AACJ,IAAA;AACb,EAAA;AAEM,EAAA;AACJ,IAAA;AACY,IAAA;AACZ,IAAA;AACA,IAAA;AACE,EAAA;AACI,IAAA;AACN,IAAA;AACF,EAAA;AAEM,EAAA;AACqB,EAAA;AACrB,EAAA;AAEM,EAAA;AACJ,IAAA;AAEK,IAAA;AACG,MAAA;AAED,MAAA;AACH,yBAAA;AACR,MAAA;AACa,MAAA;AAEH,MAAA;AACC,QAAA;AACX,MAAA;AACF,IAAA;AACF,EAAA;AAEY,EAAA;AACK,IAAA;AACF,IAAA;AAET,IAAA;AACO,MAAA;AACX,IAAA;AACF,EAAA;AAEM,EAAA;AACJ,IAAA;AACA,IAAA;AACM,IAAA;AACO,IAAA;AACf,EAAA;AAMiB,EAAA;AAEH,EAAA;AACD,IAAA;AACb,EAAA;AAEO,EAAA;AACL,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,EAAA;AACF;AAEgB;AAGE,EAAA;AAEA,EAAA;AACP,IAAA;AACT,EAAA;AAEO,EAAA;AACT;AAEM;AAEA;AAqDU;AAIR,EAAA;AACJ,IAAA;AACF,EAAA;AAEM,EAAA;AAGD,EAAA;AACI,IAAA;AACT,EAAA;AAEe,EAAA;AACT,EAAA;AACW,EAAA;AAEX,EAAA;AAGD,EAAA;AACI,IAAA;AACT,EAAA;AAIM,EAAA;AACW,EAAA;AAED,EAAA;AACP,IAAA;AACT,EAAA;AAEO,EAAA;AACF,IAAA;AAEC,IAAA;AACS,MAAA;AAKR,IAAA;AACP,EAAA;AACF;AAYgB;AAIP,EAAA;AACT;AAEmB;AACH,EAAA;AACL,IAAA;AACT,EAAA;AAEO,EAAA;AACT;AAES;AACI,EAAA;AACD,IAAA;AACK,MAAA;AACb,IAAA;AACF,EAAA;AAEO,EAAA;AACT;AAmCS;AASD,EAAA;AAKU,EAAA;AAEF,EAAA;AACH,IAAA;AACX,EAAA;AAEgB,EAAA;AACA,EAAA;AAEC,EAAA;AACT,IAAA;AAEC,IAAA;AAGT,EAAA;AAEM,EAAA;AACA,EAAA;AAID,EAAA;AAGM,IAAA;AACX,EAAA;AAGE,EAAA;AAII,EAAA;AAIK,EAAA;AACH,IAAA;AACI,uBAAA;AACV,IAAA;AAEY,IAAA;AACC,MAAA;AACC,QAAA;AACF,QAAA;AACT,MAAA;AACH,IAAA;AAES,IAAA;AACX,EAAA;AAEI,EAAA;AACK,IAAA;AACL,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAIe,EAAA;AACH,IAAA;AACV,IAAA;AACD,EAAA;AAEQ,EAAA;AACX;AAOS;AAID,EAAA;AACoB,EAAA;AAEb,EAAA;AACL,IAAA;AAEI,IAAA;AACG,MAAA;AACX,MAAA;AACF,IAAA;AAEK,IAAA;AACH,MAAA;AACF,IAAA;AAEY,IAAA;AACV,MAAA;AACC,IAAA;AAES,MAAA;AAIF,QAAA;AAEF,QAAA;AACF,UAAA;AACM,UAAA;AACR,QAAA;AAEA,QAAA;AACF,MAAA;AAEW,MAAA;AACb,IAAA;AACF,EAAA;AAEe,EAAA;AACN,IAAA;AACT,EAAA;AAEc,EAAA;AACE,IAAA;AAChB,EAAA;AACF;AAYS;AAGD,EAAA;AACG,IAAA;AACT,EAAA;AAEQ,EAAA;AAES,EAAA;AACR,IAAA;AACT,EAAA;AAEW,EAAA;AACG,IAAA;AACd,EAAA;AAEU,EAAA;AACI,IAAA;AACd,EAAA;AAEO,EAAA;AACT;AAQS;AAIM,EAAA;AACC,EAAA;AACJ,EAAA;AAEI,EAAA;AACC,IAAA;AACE,IAAA;AAEF,IAAA;AACX,MAAA;AACF,IAAA;AAEa,IAAA;AACH,IAAA;AACI,IAAA;AAChB,EAAA;AAEO,EAAA;AACT;AAkBS;AAKM,EAAA;AACJ,IAAA;AACT,EAAA;AAEU,EAAA;AACK,IAAA;AACf,EAAA;AAEc,EAAA;AACL,IAAA;AACT,EAAA;AAEgB,EAAA;AAEA,IAAA;AAEF,EAAA;AACG,IAAA;AACE,MAAA;AACf,IAAA;AAGU,IAAA;AAIK,MAAA;AACf,IAAA;AAEa,IAAA;AACd,EAAA;AAEG,EAAA;AAEU,EAAA;AAClB;AN5CmB;AACA;AO1jBN;AAEP;AACI,EAAA;AACH,EAAA;AACE,EAAA;AACD,EAAA;AACD,EAAA;AACP;AASgB;AAIC,EAAA;AAEH,EAAA;AACJ,IAAA;AACK,IAAA;AACb,EAAA;AAEY,EAAA;AACJ,IAAA;AACK,IAAA;AACb,EAAA;AAEe,EAAA;AACP,IAAA;AACC,IAAA;AACR,EAAA;AAEK,EAAA;AAGJ,EAAA;AAIU,IAAA;AACR,MAAA;AAGF,IAAA;AACF,EAAA;AAEY,EAAA;AACA,IAAA;AACR,MAAA;AAEF,IAAA;AACF,EAAA;AAEO,EAAA;AACT;AAEmB;AACV,EAAA;AACO,IAAA;AACE,IAAA;AACD,IAAA;AACD,IAAA;AACA,IAAA;AACd,EAAA;AACF;APiiBmB;AACA;AQvlBG;AAGZ,EAAA;AACO,EAAA;AACE,EAAA;AACH,EAAA;AACR,EAAA;AAGJ,EAAA;AAEgB,IAAA;AACL,IAAA;AACV,EAAA;AAEG,EAAA;AAMK,EAAA;AACI,IAAA;AAEF,IAAA;AACT,MAAA;AACF,IAAA;AAEM,IAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACE,IAAA;AAEE,IAAA;AACJ,MAAA;AACW,MAAA;AACF,MAAA;AACT,MAAA;AACA,MAAA;AACA,MAAA;AACS,MAAA;AACX,IAAA;AAGU,IAAA;AAIR,MAAA;AACE,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACW,QAAA;AACX,QAAA;AACF,MAAA;AACA,MAAA;AACF,IAAA;AAEI,IAAA;AACF,MAAA;AACE,QAAA;AACQ,QAAA;AACF,QAAA;AACP,MAAA;AACD,MAAA;AACF,IAAA;AAEM,IAAA;AACA,IAAA;AAIS,IAAA;AAEX,MAAA;AACO,MAAA;AACD,QAAA;AACD,UAAA;AACH,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AAEM,QAAA;AACT,MAAA;AACA,MAAA;AACY,MAAA;AACR,MAAA;AACL,IAAA;AACH,EAAA;AAEI,EAAA;AACM,IAAA;AACN,MAAA;AAKiB,QAAA;AAEH,MAAA;AAChB,IAAA;AACF,EAAA;AAEO,EAAA;AACT;AAES;AAWS,EAAA;AACV,EAAA;AAEM,EAAA;AACH,IAAA;AACL,MAAA;AACM,MAAA;AAEI,QAAA;AACR,MAAA;AACF,MAAA;AACa,MAAA;AACd,IAAA;AAED,IAAA;AACF,EAAA;AAEO,EAAA;AACM,IAAA;AACX,IAAA;AACa,IAAA;AAEH,MAAA;AACD,QAAA;AACH,QAAA;AACD,MAAA;AACH,IAAA;AACF,IAAA;AACa,IAAA;AACd,EAAA;AACH;AAwBe;AAIC,EAAA;AAGX,EAAA;AAKM,IAAA;AACT,EAAA;AAEM,EAAA;AAEC,EAAA;AACT;AAES;AAKH,EAAA;AACa,IAAA;AACN,IAAA;AACH,EAAA;AACG,IAAA;AACX,EAAA;AACF;ARigBmB;AACA;AACA","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 interface ExecuteRequestResult {\n /** The response body, parsed, when the response's content-type indicated JSON and it parsed successfully. */\n json?: unknown;\n /** The response body as text — pretty-printed if `json` is set. */\n text: string;\n}\n\nexport async function executeRequest(\n options: ExecuteRequestOptions,\n): Promise<ExecuteRequestResult> {\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 appendQueryValue(query, mapping.name, mapping.style, value);\n break;\n }\n }\n\n let path = options.route.path;\n\n for (const [name, value] of Object.entries(pathParams)) {\n path = path.replaceAll(`{${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 const json: unknown = JSON.parse(text);\n return { json, text: JSON.stringify(json, 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 * Appends `value` under `key`, expanding nested structure with bracket\n * notation rather than JSON-encoding it:\n *\n * - a plain object → `key[subkey]=...` recursively;\n * - an array of scalars → repeated `key=...` entries (the existing,\n * unchanged convention for both query arrays and form arrays);\n * - an array containing an object → each such item bracket-expands under\n * `key[]` (PHP/Rails-style, and what Stripe's own list-of-objects form\n * fields expect);\n * - anything else (including a scalar where an object/array was expected —\n * e.g. a caller passing a plain value for a `deepObject`-styled query\n * param) → `key=value` directly, rather than assuming a shape that isn't\n * there.\n *\n * Shared between `encodeFormBody` (request bodies) and `deepObject` query\n * parameters (`appendQueryValue`) — both need the same expansion.\n */\nfunction appendBracketPairs(\n params: URLSearchParams,\n key: string,\n value: unknown,\n): void {\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item !== null && typeof item === \"object\") {\n appendBracketPairs(params, `${key}[]`, item);\n } else {\n params.append(key, String(item));\n }\n }\n\n return;\n }\n\n if (value !== null && typeof value === \"object\") {\n for (const [subKey, subValue] of Object.entries(\n value as Record<string, unknown>,\n )) {\n if (subValue !== undefined) {\n appendBracketPairs(params, `${key}[${subKey}]`, subValue);\n }\n }\n\n return;\n }\n\n params.append(key, String(value));\n}\n\n/**\n * Appends a query parameter's value using the serialization its declared\n * `style` requires. `deepObject` and `spaceDelimited`/`pipeDelimited` are\n * real, if less common, OpenAPI styles — Stripe alone uses `deepObject` 354\n * times across its filter/expand-style query params. Anything else (no\n * style, or the OpenAPI default `style: \"form\"`) keeps the existing\n * repeated-key serialization.\n */\nfunction appendQueryValue(\n query: URLSearchParams,\n name: string,\n style: string | undefined,\n value: unknown,\n): void {\n if (style === \"deepObject\") {\n appendBracketPairs(query, name, value);\n return;\n }\n\n if (style === \"spaceDelimited\" || style === \"pipeDelimited\") {\n const items = Array.isArray(value) ? value : [value];\n const separator = style === \"spaceDelimited\" ? \" \" : \"|\";\n query.append(name, items.map(String).join(separator));\n return;\n }\n\n for (const item of Array.isArray(value) ? value : [value]) {\n query.append(name, String(item));\n }\n}\n\n/**\n * Serializes a flattened body payload as `application/x-www-form-urlencoded`,\n * bracket-expanding nested objects/arrays (e.g. Stripe's own\n * `metadata[key]=value` style) via `appendBracketPairs` — the same helper\n * used for `deepObject`-styled query parameters, since both are the same\n * underlying problem: serializing non-scalar values into a position that\n * expects flat key/value pairs, not a JSON blob. `URLSearchParams` handles\n * 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 appendBracketPairs(params, key, value);\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.replaceAll(`{${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 OpenApiResponse,\n RawPathItem,\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 path items, 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, rawPathItem] of Object.entries(document.paths ?? {})) {\n // Bundling can leave shared path items as local refs. Preserve any\n // sibling fields, such as parameters defined alongside the ref.\n const pathItem = {\n ...resolveRef<RawPathItem>(document, rawPathItem),\n ...rawPathItem,\n };\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 responses: resolveResponses(document, operation.responses),\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\n/** A response object can itself be `$ref`'d to `#/components/responses/X`. */\nfunction resolveResponses(\n document: BundledOpenApiDocument,\n rawResponses:\n | Record<string, OpenApiParameterRef | OpenApiResponse>\n | undefined,\n): Record<string, OpenApiResponse> | undefined {\n if (!rawResponses) {\n return undefined;\n }\n\n return Object.fromEntries(\n Object.entries(rawResponses).map(([code, response]) => [\n code,\n resolveRef<OpenApiResponse>(document, response),\n ]),\n );\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 * Keys whose values are dropped entirely — not just left unwalked as\n * `DATA_KEYS` are — when building a schema that gets handed to AJV. Real\n * specs (Box) embed full, realistic sample objects under `example`, which\n * can coincidentally contain fields shaped like JSON Schema keywords (e.g.\n * Box's own `$id` concept on a metadata object, reusing the same example\n * value across multiple schemas). AJV's `$id`-discovery pass doesn't know\n * these are documentation rather than schema, and throws\n * (\"reference ... resolves to more than one schema\") on the collision.\n * These keys carry zero validation meaning, so dropping them removes the\n * only thing AJV could misinterpret this way.\n *\n * Stripping is opt-in (`stripExamples`) and only `buildOutputSchema` opts\n * in. Tool *input* schemas keep their examples: they are useful signal for\n * a model filling in arguments, and the collision has never been observed\n * on that path — Box's input schemas compile fine on `main` today.\n */\nconst STRIP_KEYS = new Set([\"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 /** Only meaningful for `in: \"query\"` — see `OpenApiParameter.style`. */\n style?: 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, style: param.style };\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\nconst SUCCESS_STATUS_PATTERN = /^2\\d\\d$/;\n\nconst MAX_OUTPUT_SCHEMA_DEFS = 50;\n\n/**\n * Builds a tool's `outputSchema` from the route's first declared `2xx`\n * `application/json` response, or `undefined` if there isn't a usable one.\n *\n * Requires the schema to resolve to an explicit `type: \"object\"` — a bare\n * `$ref` (very common; a response schema is often just\n * `{ $ref: \"#/components/schemas/Pet\" }`) is followed via the same\n * `resolveComponentRef` chain-following already used for form-body `$ref`s,\n * so this still covers the common case without needing the schema to spell\n * out `type` inline. This is deliberately **not** \"anything not explicitly\n * non-object\": the MCP SDK's client-side `tools/list` response validation\n * requires an advertised `outputSchema.type` to literally be the *string*\n * `\"object\"` — a bare, unresolved `$ref` (no top-level `type` at all) fails\n * that validation and breaks `tools/list` for *every* tool in the response,\n * not just the one with the bad schema. Confirmed the hard way: an earlier,\n * more permissive version of this function did exactly that against a real\n * spec.\n *\n * The object-shape check happens on the schema *after* `rewriteComponentRefs`\n * (which folds `nullable: true` into `type: [\"object\", \"null\"]`), not\n * before — checking beforehand would miss that an inline `{ type: \"object\",\n * nullable: true }` response schema turns into an *array*-valued `type`\n * post-rewrite, which fails that same literal-string protocol requirement\n * just as a bare `$ref` does. When the resolved type is `[\"object\", \"null\"]`\n * (or any array containing `\"object\"`), the advertised type is normalized\n * back down to the literal string `\"object\"` — dropping the `\"null\"`\n * alternative is safe because a genuinely `null` response then simply fails\n * the runtime pre-validation safety net below and falls back to plain text,\n * rather than the *type declaration itself* breaking the whole tool list.\n * `fromOpenAPI.ts`'s pre-validation against the *actual* response is what\n * that safety net is for; getting the static shape right here is purely\n * about protocol validity.\n *\n * Unlike `buildFlatSchema`'s tool input schema, this does **not** set\n * `additionalProperties: false` — an undocumented extra field in a real\n * response is the most common form of spec/API drift, and forcing strict\n * mode here would make that safety net reject constantly.\n *\n * Also skips wiring when the schema transitively references more than\n * `MAX_OUTPUT_SCHEMA_DEFS` definitions. This isn't rare: real \"core\"\n * response objects (Stripe's `Charge`, `Customer`, `PaymentIntent`, ...)\n * routinely embed dozens of other resource types, which themselves embed\n * more — measured directly against Stripe's real spec, the *median*\n * operation's output schema pulled in 868 definitions, and the full\n * tools/list response across all 588 operations would have been ~320MB.\n * A schema this large is also of limited practical use as structured\n * output regardless of size — an LLM isn't better served by a 900-type\n * validation schema than by the same data as text. Skipped operations\n * keep today's plain-text-only behavior; nothing breaks, they just don't\n * get `structuredContent`.\n */\nexport function buildOutputSchema(\n route: HttpRoute,\n sharedDefs: Record<string, OpenApiSchema> | undefined,\n): JsonSchemaObject | undefined {\n const successEntry = Object.entries(route.responses ?? {}).find(([code]) =>\n SUCCESS_STATUS_PATTERN.test(code),\n );\n\n const declaredSchema =\n successEntry?.[1].content?.[\"application/json\"]?.schema;\n\n if (!declaredSchema) {\n return undefined;\n }\n\n const schema = resolveComponentRef(declaredSchema, sharedDefs);\n const rewritten = rewriteComponentRefs(schema, true) as OpenApiSchema;\n const { type } = rewritten;\n\n const isObjectShaped =\n type === \"object\" || (Array.isArray(type) && type.includes(\"object\"));\n\n if (!isObjectShaped) {\n return undefined;\n }\n\n // The protocol requires the literal string \"object\", not an array — see\n // the doc comment above for why dropping \"null\" here is safe.\n const normalized = { ...rewritten, type: \"object\" };\n const usedDefs = sharedDefs && filterReferencedDefs(normalized, sharedDefs);\n\n if (usedDefs && Object.keys(usedDefs).length > MAX_OUTPUT_SCHEMA_DEFS) {\n return undefined;\n }\n\n return {\n ...normalized,\n ...(usedDefs\n ? {\n $defs: rewriteNode(usedDefs, \"schemaMap\", true) as Record<\n string,\n OpenApiSchema\n >,\n }\n : {}),\n } as JsonSchemaObject;\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>(\n value: TValue,\n stripExamples = false,\n): TValue {\n return rewriteNode(value, \"schema\", stripExamples) 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(\n value: unknown,\n mode: WalkMode,\n stripExamples = false,\n): unknown {\n if (mode === \"data\") {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => rewriteNode(item, \"schema\", stripExamples));\n }\n\n if (!value || typeof value !== \"object\") {\n return value;\n }\n\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(\n ([key]) => !stripExamples || mode !== \"schema\" || !STRIP_KEYS.has(key),\n )\n .map(([key, entryValue]): [string, unknown] => {\n if (mode === \"schemaMap\") {\n return [key, rewriteNode(entryValue, \"schema\", stripExamples)];\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), stripExamples)];\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, type ExecuteRequestResult } from \"./requestBuilder.js\";\nimport {\n buildResourceMapping,\n isEligibleForResource,\n} from \"./resourceMapping.js\";\nimport { extractRoutes } from \"./routes.js\";\nimport {\n buildFlatSchema,\n buildOutputSchema,\n buildSharedDefs,\n} 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 const outputSchemaJson = buildOutputSchema(route, sharedDefs);\n const outputSchema = outputSchemaJson\n ? jsonSchemaAdapter(outputSchemaJson)\n : undefined;\n\n server.addTool({\n description:\n route.summary ?? `${route.method.toUpperCase()} ${route.path}`,\n execute: async (args) => {\n const result = await executeRequest({\n ...execOptions,\n args: args as Record<string, unknown>,\n bodyEncoding,\n wholeBodyKey,\n });\n\n return resolveToolResult(result, outputSchema);\n },\n name,\n parameters: jsonSchemaAdapter(flatSchema),\n ...(outputSchema ? { outputSchema } : {}),\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\n/**\n * Decides whether a tool call returns the parsed response object (letting\n * FastMCP populate `structuredContent` against `outputSchema`) or the plain\n * text fallback that always works.\n *\n * Real API responses commonly drift from their declared OpenAPI schema, and\n * FastMCP treats an `outputSchema` mismatch as a hard tool error (not a\n * silent fallback) — so a successful HTTP call could otherwise turn into a\n * failed MCP tool call purely from schema drift. This pre-validates against\n * the *exact same* schema instance that's attached as `Tool.outputSchema`\n * (AJV compilation is memoized and deterministic, so this agrees with\n * FastMCP's own re-validation), and only returns the object when it passes.\n *\n * The `Array.isArray` guard is required independently of AJV validation: a\n * schema describing array-shaped data can validate successfully, but\n * FastMCP's `structuredContent` is a plain-object field (`z.record(...)`)\n * that rejects an array at the `ContentResultZodSchema.parse` step — a\n * *different* check than AJV's, positioned after our pre-validation would\n * already have said \"fine.\" Without this guard, an array-typed response\n * schema reintroduces exactly the failure mode this function exists to\n * prevent.\n */\nasync function resolveToolResult(\n result: ExecuteRequestResult,\n outputSchema: ReturnType<typeof jsonSchemaAdapter> | undefined,\n): Promise<unknown> {\n const { json, text } = result;\n\n if (\n !outputSchema ||\n json === null ||\n typeof json !== \"object\" ||\n Array.isArray(json)\n ) {\n return text;\n }\n\n const validation = await outputSchema[\"~standard\"].validate(json);\n\n return validation.issues ? text : json;\n}\n\nfunction wrapAsResourceResult({ text }: ExecuteRequestResult): ResourceResult {\n // Content-sniffed rather than relying on executeRequest's header-gated\n // `json` field (which exists for the tool/outputSchema path): a server\n // that returns valid JSON without a matching content-type header should\n // still be recognized here, same as before this field existed.\n try {\n JSON.parse(text);\n return { mimeType: \"application/json\", text };\n } catch {\n return { mimeType: \"text/plain\", text };\n }\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 interface ExecuteRequestResult {\n /** The response body, parsed, when the response's content-type indicated JSON and it parsed successfully. */\n json?: unknown;\n /** The response body as text — pretty-printed if `json` is set. */\n text: string;\n}\n\nexport async function executeRequest(\n options: ExecuteRequestOptions,\n): Promise<ExecuteRequestResult> {\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 appendQueryValue(query, mapping.name, mapping.style, value);\n break;\n }\n }\n\n let path = options.route.path;\n\n for (const [name, value] of Object.entries(pathParams)) {\n path = path.replaceAll(`{${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 const json: unknown = JSON.parse(text);\n return { json, text: JSON.stringify(json, 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 * Appends `value` under `key`, expanding nested structure with bracket\n * notation rather than JSON-encoding it:\n *\n * - a plain object → `key[subkey]=...` recursively;\n * - an array of scalars → repeated `key=...` entries (the existing,\n * unchanged convention for both query arrays and form arrays);\n * - an array containing an object → each such item bracket-expands under\n * `key[]` (PHP/Rails-style, and what Stripe's own list-of-objects form\n * fields expect);\n * - anything else (including a scalar where an object/array was expected —\n * e.g. a caller passing a plain value for a `deepObject`-styled query\n * param) → `key=value` directly, rather than assuming a shape that isn't\n * there.\n *\n * Shared between `encodeFormBody` (request bodies) and `deepObject` query\n * parameters (`appendQueryValue`) — both need the same expansion.\n */\nfunction appendBracketPairs(\n params: URLSearchParams,\n key: string,\n value: unknown,\n): void {\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item !== null && typeof item === \"object\") {\n appendBracketPairs(params, `${key}[]`, item);\n } else {\n params.append(key, String(item));\n }\n }\n\n return;\n }\n\n if (value !== null && typeof value === \"object\") {\n for (const [subKey, subValue] of Object.entries(\n value as Record<string, unknown>,\n )) {\n if (subValue !== undefined) {\n appendBracketPairs(params, `${key}[${subKey}]`, subValue);\n }\n }\n\n return;\n }\n\n params.append(key, String(value));\n}\n\n/**\n * Appends a query parameter's value using the serialization its declared\n * `style` requires. `deepObject` and `spaceDelimited`/`pipeDelimited` are\n * real, if less common, OpenAPI styles — Stripe alone uses `deepObject` 354\n * times across its filter/expand-style query params. Anything else (no\n * style, or the OpenAPI default `style: \"form\"`) keeps the existing\n * repeated-key serialization.\n */\nfunction appendQueryValue(\n query: URLSearchParams,\n name: string,\n style: string | undefined,\n value: unknown,\n): void {\n if (style === \"deepObject\") {\n appendBracketPairs(query, name, value);\n return;\n }\n\n if (style === \"spaceDelimited\" || style === \"pipeDelimited\") {\n const items = Array.isArray(value) ? value : [value];\n const separator = style === \"spaceDelimited\" ? \" \" : \"|\";\n query.append(name, items.map(String).join(separator));\n return;\n }\n\n for (const item of Array.isArray(value) ? value : [value]) {\n query.append(name, String(item));\n }\n}\n\n/**\n * Serializes a flattened body payload as `application/x-www-form-urlencoded`,\n * bracket-expanding nested objects/arrays (e.g. Stripe's own\n * `metadata[key]=value` style) via `appendBracketPairs` — the same helper\n * used for `deepObject`-styled query parameters, since both are the same\n * underlying problem: serializing non-scalar values into a position that\n * expects flat key/value pairs, not a JSON blob. `URLSearchParams` handles\n * 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 appendBracketPairs(params, key, value);\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.replaceAll(`{${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 OpenApiResponse,\n RawPathItem,\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 path items, 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, rawPathItem] of Object.entries(document.paths ?? {})) {\n const pathItem = resolvePathItem(document, rawPathItem);\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 responses: resolveResponses(document, operation.responses),\n servers: [operation.servers, pathItem.servers, document.servers].find(\n (servers) => servers?.length,\n ),\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\n/**\n * Resolves a path item's `$ref`, following chains of them — bundling leaves\n * shared path items as local refs, and doesn't collapse a chain whose\n * intermediate items have sibling fields.\n *\n * A referring item's sibling fields (e.g. `parameters`) replace the\n * referenced item's rather than merging with them: bundling inlines a shared\n * path item at one referrer with that referrer's siblings folded in, so\n * merging would leak them into every other path sharing the item.\n */\nfunction resolvePathItem(\n document: BundledOpenApiDocument,\n pathItem: RawPathItem,\n): RawPathItem {\n const visited = new Set<string>();\n let resolved = pathItem;\n\n while (resolved.$ref && !visited.has(resolved.$ref)) {\n visited.add(resolved.$ref);\n\n const { $ref, ...siblings } = resolved;\n\n resolved = { ...resolveRef<RawPathItem>(document, { $ref }), ...siblings };\n }\n\n return resolved;\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\n/** A response object can itself be `$ref`'d to `#/components/responses/X`. */\nfunction resolveResponses(\n document: BundledOpenApiDocument,\n rawResponses:\n | Record<string, OpenApiParameterRef | OpenApiResponse>\n | undefined,\n): Record<string, OpenApiResponse> | undefined {\n if (!rawResponses) {\n return undefined;\n }\n\n return Object.fromEntries(\n Object.entries(rawResponses).map(([code, response]) => [\n code,\n resolveRef<OpenApiResponse>(document, response),\n ]),\n );\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 * Keys whose values are dropped entirely — not just left unwalked as\n * `DATA_KEYS` are — when building a schema that gets handed to AJV. Real\n * specs (Box) embed full, realistic sample objects under `example`, which\n * can coincidentally contain fields shaped like JSON Schema keywords (e.g.\n * Box's own `$id` concept on a metadata object, reusing the same example\n * value across multiple schemas). AJV's `$id`-discovery pass doesn't know\n * these are documentation rather than schema, and throws\n * (\"reference ... resolves to more than one schema\") on the collision.\n * These keys carry zero validation meaning, so dropping them removes the\n * only thing AJV could misinterpret this way.\n *\n * Stripping is opt-in (`stripExamples`) and only `buildOutputSchema` opts\n * in. Tool *input* schemas keep their examples: they are useful signal for\n * a model filling in arguments, and the collision has never been observed\n * on that path — Box's input schemas compile fine on `main` today.\n */\nconst STRIP_KEYS = new Set([\"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 /** Only meaningful for `in: \"query\"` — see `OpenApiParameter.style`. */\n style?: 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, style: param.style };\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\nconst SUCCESS_STATUS_PATTERN = /^2\\d\\d$/;\n\nconst MAX_OUTPUT_SCHEMA_DEFS = 50;\n\n/**\n * Builds a tool's `outputSchema` from the route's first declared `2xx`\n * `application/json` response, or `undefined` if there isn't a usable one.\n *\n * Requires the schema to resolve to an explicit `type: \"object\"` — a bare\n * `$ref` (very common; a response schema is often just\n * `{ $ref: \"#/components/schemas/Pet\" }`) is followed via the same\n * `resolveComponentRef` chain-following already used for form-body `$ref`s,\n * so this still covers the common case without needing the schema to spell\n * out `type` inline. This is deliberately **not** \"anything not explicitly\n * non-object\": the MCP SDK's client-side `tools/list` response validation\n * requires an advertised `outputSchema.type` to literally be the *string*\n * `\"object\"` — a bare, unresolved `$ref` (no top-level `type` at all) fails\n * that validation and breaks `tools/list` for *every* tool in the response,\n * not just the one with the bad schema. Confirmed the hard way: an earlier,\n * more permissive version of this function did exactly that against a real\n * spec.\n *\n * The object-shape check happens on the schema *after* `rewriteComponentRefs`\n * (which folds `nullable: true` into `type: [\"object\", \"null\"]`), not\n * before — checking beforehand would miss that an inline `{ type: \"object\",\n * nullable: true }` response schema turns into an *array*-valued `type`\n * post-rewrite, which fails that same literal-string protocol requirement\n * just as a bare `$ref` does. When the resolved type is `[\"object\", \"null\"]`\n * (or any array containing `\"object\"`), the advertised type is normalized\n * back down to the literal string `\"object\"` — dropping the `\"null\"`\n * alternative is safe because a genuinely `null` response then simply fails\n * the runtime pre-validation safety net below and falls back to plain text,\n * rather than the *type declaration itself* breaking the whole tool list.\n * `fromOpenAPI.ts`'s pre-validation against the *actual* response is what\n * that safety net is for; getting the static shape right here is purely\n * about protocol validity.\n *\n * Unlike `buildFlatSchema`'s tool input schema, this does **not** set\n * `additionalProperties: false` — an undocumented extra field in a real\n * response is the most common form of spec/API drift, and forcing strict\n * mode here would make that safety net reject constantly.\n *\n * Also skips wiring when the schema transitively references more than\n * `MAX_OUTPUT_SCHEMA_DEFS` definitions. This isn't rare: real \"core\"\n * response objects (Stripe's `Charge`, `Customer`, `PaymentIntent`, ...)\n * routinely embed dozens of other resource types, which themselves embed\n * more — measured directly against Stripe's real spec, the *median*\n * operation's output schema pulled in 868 definitions, and the full\n * tools/list response across all 588 operations would have been ~320MB.\n * A schema this large is also of limited practical use as structured\n * output regardless of size — an LLM isn't better served by a 900-type\n * validation schema than by the same data as text. Skipped operations\n * keep today's plain-text-only behavior; nothing breaks, they just don't\n * get `structuredContent`.\n */\nexport function buildOutputSchema(\n route: HttpRoute,\n sharedDefs: Record<string, OpenApiSchema> | undefined,\n): JsonSchemaObject | undefined {\n const successEntry = Object.entries(route.responses ?? {}).find(([code]) =>\n SUCCESS_STATUS_PATTERN.test(code),\n );\n\n const declaredSchema =\n successEntry?.[1].content?.[\"application/json\"]?.schema;\n\n if (!declaredSchema) {\n return undefined;\n }\n\n const schema = resolveComponentRef(declaredSchema, sharedDefs);\n const rewritten = rewriteComponentRefs(schema, true) as OpenApiSchema;\n const { type } = rewritten;\n\n const isObjectShaped =\n type === \"object\" || (Array.isArray(type) && type.includes(\"object\"));\n\n if (!isObjectShaped) {\n return undefined;\n }\n\n // The protocol requires the literal string \"object\", not an array — see\n // the doc comment above for why dropping \"null\" here is safe.\n const normalized = { ...rewritten, type: \"object\" };\n const usedDefs = sharedDefs && filterReferencedDefs(normalized, sharedDefs);\n\n if (usedDefs && Object.keys(usedDefs).length > MAX_OUTPUT_SCHEMA_DEFS) {\n return undefined;\n }\n\n return {\n ...normalized,\n ...(usedDefs\n ? {\n $defs: rewriteNode(usedDefs, \"schemaMap\", true) as Record<\n string,\n OpenApiSchema\n >,\n }\n : {}),\n } as JsonSchemaObject;\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>(\n value: TValue,\n stripExamples = false,\n): TValue {\n return rewriteNode(value, \"schema\", stripExamples) 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(\n value: unknown,\n mode: WalkMode,\n stripExamples = false,\n): unknown {\n if (mode === \"data\") {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => rewriteNode(item, \"schema\", stripExamples));\n }\n\n if (!value || typeof value !== \"object\") {\n return value;\n }\n\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(\n ([key]) => !stripExamples || mode !== \"schema\" || !STRIP_KEYS.has(key),\n )\n .map(([key, entryValue]): [string, unknown] => {\n if (mode === \"schemaMap\") {\n return [key, rewriteNode(entryValue, \"schema\", stripExamples)];\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), stripExamples)];\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, type ExecuteRequestResult } from \"./requestBuilder.js\";\nimport {\n buildResourceMapping,\n isEligibleForResource,\n} from \"./resourceMapping.js\";\nimport { extractRoutes } from \"./routes.js\";\nimport {\n buildFlatSchema,\n buildOutputSchema,\n buildSharedDefs,\n} 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: route.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 const outputSchemaJson = buildOutputSchema(route, sharedDefs);\n const outputSchema = outputSchemaJson\n ? jsonSchemaAdapter(outputSchemaJson)\n : undefined;\n\n server.addTool({\n description:\n route.summary ?? `${route.method.toUpperCase()} ${route.path}`,\n execute: async (args) => {\n const result = await executeRequest({\n ...execOptions,\n args: args as Record<string, unknown>,\n bodyEncoding,\n wholeBodyKey,\n });\n\n return resolveToolResult(result, outputSchema);\n },\n name,\n parameters: jsonSchemaAdapter(flatSchema),\n ...(outputSchema ? { outputSchema } : {}),\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\n/**\n * Decides whether a tool call returns the parsed response object (letting\n * FastMCP populate `structuredContent` against `outputSchema`) or the plain\n * text fallback that always works.\n *\n * Real API responses commonly drift from their declared OpenAPI schema, and\n * FastMCP treats an `outputSchema` mismatch as a hard tool error (not a\n * silent fallback) — so a successful HTTP call could otherwise turn into a\n * failed MCP tool call purely from schema drift. This pre-validates against\n * the *exact same* schema instance that's attached as `Tool.outputSchema`\n * (AJV compilation is memoized and deterministic, so this agrees with\n * FastMCP's own re-validation), and only returns the object when it passes.\n *\n * The `Array.isArray` guard is required independently of AJV validation: a\n * schema describing array-shaped data can validate successfully, but\n * FastMCP's `structuredContent` is a plain-object field (`z.record(...)`)\n * that rejects an array at the `ContentResultZodSchema.parse` step — a\n * *different* check than AJV's, positioned after our pre-validation would\n * already have said \"fine.\" Without this guard, an array-typed response\n * schema reintroduces exactly the failure mode this function exists to\n * prevent.\n */\nasync function resolveToolResult(\n result: ExecuteRequestResult,\n outputSchema: ReturnType<typeof jsonSchemaAdapter> | undefined,\n): Promise<unknown> {\n const { json, text } = result;\n\n if (\n !outputSchema ||\n json === null ||\n typeof json !== \"object\" ||\n Array.isArray(json)\n ) {\n return text;\n }\n\n const validation = await outputSchema[\"~standard\"].validate(json);\n\n return validation.issues ? text : json;\n}\n\nfunction wrapAsResourceResult({ text }: ExecuteRequestResult): ResourceResult {\n // Content-sniffed rather than relying on executeRequest's header-gated\n // `json` field (which exists for the tool/outputSchema path): a server\n // that returns valid JSON without a matching content-type header should\n // still be recognized here, same as before this field existed.\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
|
@@ -33,9 +33,9 @@ interface BundledOpenApiDocument {
|
|
|
33
33
|
}
|
|
34
34
|
interface FromOpenAPIOptions {
|
|
35
35
|
/**
|
|
36
|
-
* Overrides the
|
|
37
|
-
* `servers` entry, or
|
|
38
|
-
* from an http(s) URL.
|
|
36
|
+
* Overrides the selected server URL for every operation. Required when
|
|
37
|
+
* no applicable `servers` entry exists, or the selected URL is relative
|
|
38
|
+
* and the spec was not loaded from an http(s) URL.
|
|
39
39
|
*/
|
|
40
40
|
baseUrl?: string;
|
|
41
41
|
/**
|
|
@@ -115,6 +115,8 @@ interface HttpRoute {
|
|
|
115
115
|
* equivalent to the GET-never-has-a-body rule for responses.
|
|
116
116
|
*/
|
|
117
117
|
responses?: Record<string, OpenApiResponse>;
|
|
118
|
+
/** The first non-empty server list at operation, path, or document level. */
|
|
119
|
+
servers?: OpenApiServer[];
|
|
118
120
|
summary?: string;
|
|
119
121
|
tags: string[];
|
|
120
122
|
}
|
|
@@ -181,12 +183,14 @@ interface RawOperation {
|
|
|
181
183
|
parameters?: (OpenApiParameter | OpenApiParameterRef)[];
|
|
182
184
|
requestBody?: OpenApiParameterRef | OpenApiRequestBody;
|
|
183
185
|
responses?: Record<string, OpenApiParameterRef | OpenApiResponse>;
|
|
186
|
+
servers?: OpenApiServer[];
|
|
184
187
|
summary?: string;
|
|
185
188
|
tags?: string[];
|
|
186
189
|
}
|
|
187
190
|
type RawPathItem = {
|
|
188
191
|
$ref?: string;
|
|
189
192
|
parameters?: (OpenApiParameter | OpenApiParameterRef)[];
|
|
193
|
+
servers?: OpenApiServer[];
|
|
190
194
|
} & Partial<Record<HttpMethod, RawOperation>>;
|
|
191
195
|
|
|
192
196
|
/**
|
package/dist/openapi/index.d.ts
CHANGED
|
@@ -33,9 +33,9 @@ interface BundledOpenApiDocument {
|
|
|
33
33
|
}
|
|
34
34
|
interface FromOpenAPIOptions {
|
|
35
35
|
/**
|
|
36
|
-
* Overrides the
|
|
37
|
-
* `servers` entry, or
|
|
38
|
-
* from an http(s) URL.
|
|
36
|
+
* Overrides the selected server URL for every operation. Required when
|
|
37
|
+
* no applicable `servers` entry exists, or the selected URL is relative
|
|
38
|
+
* and the spec was not loaded from an http(s) URL.
|
|
39
39
|
*/
|
|
40
40
|
baseUrl?: string;
|
|
41
41
|
/**
|
|
@@ -115,6 +115,8 @@ interface HttpRoute {
|
|
|
115
115
|
* equivalent to the GET-never-has-a-body rule for responses.
|
|
116
116
|
*/
|
|
117
117
|
responses?: Record<string, OpenApiResponse>;
|
|
118
|
+
/** The first non-empty server list at operation, path, or document level. */
|
|
119
|
+
servers?: OpenApiServer[];
|
|
118
120
|
summary?: string;
|
|
119
121
|
tags: string[];
|
|
120
122
|
}
|
|
@@ -181,12 +183,14 @@ interface RawOperation {
|
|
|
181
183
|
parameters?: (OpenApiParameter | OpenApiParameterRef)[];
|
|
182
184
|
requestBody?: OpenApiParameterRef | OpenApiRequestBody;
|
|
183
185
|
responses?: Record<string, OpenApiParameterRef | OpenApiResponse>;
|
|
186
|
+
servers?: OpenApiServer[];
|
|
184
187
|
summary?: string;
|
|
185
188
|
tags?: string[];
|
|
186
189
|
}
|
|
187
190
|
type RawPathItem = {
|
|
188
191
|
$ref?: string;
|
|
189
192
|
parameters?: (OpenApiParameter | OpenApiParameterRef)[];
|
|
193
|
+
servers?: OpenApiServer[];
|
|
190
194
|
} & Partial<Record<HttpMethod, RawOperation>>;
|
|
191
195
|
|
|
192
196
|
/**
|
package/dist/openapi/index.js
CHANGED
|
@@ -257,10 +257,7 @@ var HTTP_METHODS = ["get", "put", "post", "delete", "patch"];
|
|
|
257
257
|
function extractRoutes(document) {
|
|
258
258
|
const routes = [];
|
|
259
259
|
for (const [path, rawPathItem] of Object.entries(document.paths ?? {})) {
|
|
260
|
-
const pathItem =
|
|
261
|
-
...resolveRef(document, rawPathItem),
|
|
262
|
-
...rawPathItem
|
|
263
|
-
};
|
|
260
|
+
const pathItem = resolvePathItem(document, rawPathItem);
|
|
264
261
|
const pathLevelParams = (pathItem.parameters ?? []).map(
|
|
265
262
|
(param) => resolveRef(document, param)
|
|
266
263
|
);
|
|
@@ -280,6 +277,9 @@ function extractRoutes(document) {
|
|
|
280
277
|
path,
|
|
281
278
|
requestBody: operation.requestBody ? resolveRef(document, operation.requestBody) : void 0,
|
|
282
279
|
responses: resolveResponses(document, operation.responses),
|
|
280
|
+
servers: [operation.servers, pathItem.servers, document.servers].find(
|
|
281
|
+
(servers) => servers?.length
|
|
282
|
+
),
|
|
283
283
|
summary: operation.summary,
|
|
284
284
|
tags: operation.tags ?? []
|
|
285
285
|
});
|
|
@@ -298,6 +298,16 @@ function mergeParameters(pathLevel, operationLevel) {
|
|
|
298
298
|
...operationLevel
|
|
299
299
|
];
|
|
300
300
|
}
|
|
301
|
+
function resolvePathItem(document, pathItem) {
|
|
302
|
+
const visited = /* @__PURE__ */ new Set();
|
|
303
|
+
let resolved = pathItem;
|
|
304
|
+
while (resolved.$ref && !visited.has(resolved.$ref)) {
|
|
305
|
+
visited.add(resolved.$ref);
|
|
306
|
+
const { $ref, ...siblings } = resolved;
|
|
307
|
+
resolved = { ...resolveRef(document, { $ref }), ...siblings };
|
|
308
|
+
}
|
|
309
|
+
return resolved;
|
|
310
|
+
}
|
|
301
311
|
function resolveRef(document, value) {
|
|
302
312
|
if (!value || typeof value !== "object" || !("$ref" in value)) {
|
|
303
313
|
return value;
|
|
@@ -657,7 +667,7 @@ async function fromOpenAPI(options) {
|
|
|
657
667
|
origin,
|
|
658
668
|
parameterMap,
|
|
659
669
|
route,
|
|
660
|
-
servers:
|
|
670
|
+
servers: route.servers
|
|
661
671
|
};
|
|
662
672
|
if (options.resources && route.method === "get" && isEligibleForResource(route)) {
|
|
663
673
|
registerResource(
|
|
@@ -1 +1 @@
|
|
|
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 interface ExecuteRequestResult {\n /** The response body, parsed, when the response's content-type indicated JSON and it parsed successfully. */\n json?: unknown;\n /** The response body as text — pretty-printed if `json` is set. */\n text: string;\n}\n\nexport async function executeRequest(\n options: ExecuteRequestOptions,\n): Promise<ExecuteRequestResult> {\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 appendQueryValue(query, mapping.name, mapping.style, value);\n break;\n }\n }\n\n let path = options.route.path;\n\n for (const [name, value] of Object.entries(pathParams)) {\n path = path.replaceAll(`{${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 const json: unknown = JSON.parse(text);\n return { json, text: JSON.stringify(json, 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 * Appends `value` under `key`, expanding nested structure with bracket\n * notation rather than JSON-encoding it:\n *\n * - a plain object → `key[subkey]=...` recursively;\n * - an array of scalars → repeated `key=...` entries (the existing,\n * unchanged convention for both query arrays and form arrays);\n * - an array containing an object → each such item bracket-expands under\n * `key[]` (PHP/Rails-style, and what Stripe's own list-of-objects form\n * fields expect);\n * - anything else (including a scalar where an object/array was expected —\n * e.g. a caller passing a plain value for a `deepObject`-styled query\n * param) → `key=value` directly, rather than assuming a shape that isn't\n * there.\n *\n * Shared between `encodeFormBody` (request bodies) and `deepObject` query\n * parameters (`appendQueryValue`) — both need the same expansion.\n */\nfunction appendBracketPairs(\n params: URLSearchParams,\n key: string,\n value: unknown,\n): void {\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item !== null && typeof item === \"object\") {\n appendBracketPairs(params, `${key}[]`, item);\n } else {\n params.append(key, String(item));\n }\n }\n\n return;\n }\n\n if (value !== null && typeof value === \"object\") {\n for (const [subKey, subValue] of Object.entries(\n value as Record<string, unknown>,\n )) {\n if (subValue !== undefined) {\n appendBracketPairs(params, `${key}[${subKey}]`, subValue);\n }\n }\n\n return;\n }\n\n params.append(key, String(value));\n}\n\n/**\n * Appends a query parameter's value using the serialization its declared\n * `style` requires. `deepObject` and `spaceDelimited`/`pipeDelimited` are\n * real, if less common, OpenAPI styles — Stripe alone uses `deepObject` 354\n * times across its filter/expand-style query params. Anything else (no\n * style, or the OpenAPI default `style: \"form\"`) keeps the existing\n * repeated-key serialization.\n */\nfunction appendQueryValue(\n query: URLSearchParams,\n name: string,\n style: string | undefined,\n value: unknown,\n): void {\n if (style === \"deepObject\") {\n appendBracketPairs(query, name, value);\n return;\n }\n\n if (style === \"spaceDelimited\" || style === \"pipeDelimited\") {\n const items = Array.isArray(value) ? value : [value];\n const separator = style === \"spaceDelimited\" ? \" \" : \"|\";\n query.append(name, items.map(String).join(separator));\n return;\n }\n\n for (const item of Array.isArray(value) ? value : [value]) {\n query.append(name, String(item));\n }\n}\n\n/**\n * Serializes a flattened body payload as `application/x-www-form-urlencoded`,\n * bracket-expanding nested objects/arrays (e.g. Stripe's own\n * `metadata[key]=value` style) via `appendBracketPairs` — the same helper\n * used for `deepObject`-styled query parameters, since both are the same\n * underlying problem: serializing non-scalar values into a position that\n * expects flat key/value pairs, not a JSON blob. `URLSearchParams` handles\n * 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 appendBracketPairs(params, key, value);\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.replaceAll(`{${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 OpenApiResponse,\n RawPathItem,\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 path items, 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, rawPathItem] of Object.entries(document.paths ?? {})) {\n // Bundling can leave shared path items as local refs. Preserve any\n // sibling fields, such as parameters defined alongside the ref.\n const pathItem = {\n ...resolveRef<RawPathItem>(document, rawPathItem),\n ...rawPathItem,\n };\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 responses: resolveResponses(document, operation.responses),\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\n/** A response object can itself be `$ref`'d to `#/components/responses/X`. */\nfunction resolveResponses(\n document: BundledOpenApiDocument,\n rawResponses:\n | Record<string, OpenApiParameterRef | OpenApiResponse>\n | undefined,\n): Record<string, OpenApiResponse> | undefined {\n if (!rawResponses) {\n return undefined;\n }\n\n return Object.fromEntries(\n Object.entries(rawResponses).map(([code, response]) => [\n code,\n resolveRef<OpenApiResponse>(document, response),\n ]),\n );\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 * Keys whose values are dropped entirely — not just left unwalked as\n * `DATA_KEYS` are — when building a schema that gets handed to AJV. Real\n * specs (Box) embed full, realistic sample objects under `example`, which\n * can coincidentally contain fields shaped like JSON Schema keywords (e.g.\n * Box's own `$id` concept on a metadata object, reusing the same example\n * value across multiple schemas). AJV's `$id`-discovery pass doesn't know\n * these are documentation rather than schema, and throws\n * (\"reference ... resolves to more than one schema\") on the collision.\n * These keys carry zero validation meaning, so dropping them removes the\n * only thing AJV could misinterpret this way.\n *\n * Stripping is opt-in (`stripExamples`) and only `buildOutputSchema` opts\n * in. Tool *input* schemas keep their examples: they are useful signal for\n * a model filling in arguments, and the collision has never been observed\n * on that path — Box's input schemas compile fine on `main` today.\n */\nconst STRIP_KEYS = new Set([\"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 /** Only meaningful for `in: \"query\"` — see `OpenApiParameter.style`. */\n style?: 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, style: param.style };\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\nconst SUCCESS_STATUS_PATTERN = /^2\\d\\d$/;\n\nconst MAX_OUTPUT_SCHEMA_DEFS = 50;\n\n/**\n * Builds a tool's `outputSchema` from the route's first declared `2xx`\n * `application/json` response, or `undefined` if there isn't a usable one.\n *\n * Requires the schema to resolve to an explicit `type: \"object\"` — a bare\n * `$ref` (very common; a response schema is often just\n * `{ $ref: \"#/components/schemas/Pet\" }`) is followed via the same\n * `resolveComponentRef` chain-following already used for form-body `$ref`s,\n * so this still covers the common case without needing the schema to spell\n * out `type` inline. This is deliberately **not** \"anything not explicitly\n * non-object\": the MCP SDK's client-side `tools/list` response validation\n * requires an advertised `outputSchema.type` to literally be the *string*\n * `\"object\"` — a bare, unresolved `$ref` (no top-level `type` at all) fails\n * that validation and breaks `tools/list` for *every* tool in the response,\n * not just the one with the bad schema. Confirmed the hard way: an earlier,\n * more permissive version of this function did exactly that against a real\n * spec.\n *\n * The object-shape check happens on the schema *after* `rewriteComponentRefs`\n * (which folds `nullable: true` into `type: [\"object\", \"null\"]`), not\n * before — checking beforehand would miss that an inline `{ type: \"object\",\n * nullable: true }` response schema turns into an *array*-valued `type`\n * post-rewrite, which fails that same literal-string protocol requirement\n * just as a bare `$ref` does. When the resolved type is `[\"object\", \"null\"]`\n * (or any array containing `\"object\"`), the advertised type is normalized\n * back down to the literal string `\"object\"` — dropping the `\"null\"`\n * alternative is safe because a genuinely `null` response then simply fails\n * the runtime pre-validation safety net below and falls back to plain text,\n * rather than the *type declaration itself* breaking the whole tool list.\n * `fromOpenAPI.ts`'s pre-validation against the *actual* response is what\n * that safety net is for; getting the static shape right here is purely\n * about protocol validity.\n *\n * Unlike `buildFlatSchema`'s tool input schema, this does **not** set\n * `additionalProperties: false` — an undocumented extra field in a real\n * response is the most common form of spec/API drift, and forcing strict\n * mode here would make that safety net reject constantly.\n *\n * Also skips wiring when the schema transitively references more than\n * `MAX_OUTPUT_SCHEMA_DEFS` definitions. This isn't rare: real \"core\"\n * response objects (Stripe's `Charge`, `Customer`, `PaymentIntent`, ...)\n * routinely embed dozens of other resource types, which themselves embed\n * more — measured directly against Stripe's real spec, the *median*\n * operation's output schema pulled in 868 definitions, and the full\n * tools/list response across all 588 operations would have been ~320MB.\n * A schema this large is also of limited practical use as structured\n * output regardless of size — an LLM isn't better served by a 900-type\n * validation schema than by the same data as text. Skipped operations\n * keep today's plain-text-only behavior; nothing breaks, they just don't\n * get `structuredContent`.\n */\nexport function buildOutputSchema(\n route: HttpRoute,\n sharedDefs: Record<string, OpenApiSchema> | undefined,\n): JsonSchemaObject | undefined {\n const successEntry = Object.entries(route.responses ?? {}).find(([code]) =>\n SUCCESS_STATUS_PATTERN.test(code),\n );\n\n const declaredSchema =\n successEntry?.[1].content?.[\"application/json\"]?.schema;\n\n if (!declaredSchema) {\n return undefined;\n }\n\n const schema = resolveComponentRef(declaredSchema, sharedDefs);\n const rewritten = rewriteComponentRefs(schema, true) as OpenApiSchema;\n const { type } = rewritten;\n\n const isObjectShaped =\n type === \"object\" || (Array.isArray(type) && type.includes(\"object\"));\n\n if (!isObjectShaped) {\n return undefined;\n }\n\n // The protocol requires the literal string \"object\", not an array — see\n // the doc comment above for why dropping \"null\" here is safe.\n const normalized = { ...rewritten, type: \"object\" };\n const usedDefs = sharedDefs && filterReferencedDefs(normalized, sharedDefs);\n\n if (usedDefs && Object.keys(usedDefs).length > MAX_OUTPUT_SCHEMA_DEFS) {\n return undefined;\n }\n\n return {\n ...normalized,\n ...(usedDefs\n ? {\n $defs: rewriteNode(usedDefs, \"schemaMap\", true) as Record<\n string,\n OpenApiSchema\n >,\n }\n : {}),\n } as JsonSchemaObject;\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>(\n value: TValue,\n stripExamples = false,\n): TValue {\n return rewriteNode(value, \"schema\", stripExamples) 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(\n value: unknown,\n mode: WalkMode,\n stripExamples = false,\n): unknown {\n if (mode === \"data\") {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => rewriteNode(item, \"schema\", stripExamples));\n }\n\n if (!value || typeof value !== \"object\") {\n return value;\n }\n\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(\n ([key]) => !stripExamples || mode !== \"schema\" || !STRIP_KEYS.has(key),\n )\n .map(([key, entryValue]): [string, unknown] => {\n if (mode === \"schemaMap\") {\n return [key, rewriteNode(entryValue, \"schema\", stripExamples)];\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), stripExamples)];\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, type ExecuteRequestResult } from \"./requestBuilder.js\";\nimport {\n buildResourceMapping,\n isEligibleForResource,\n} from \"./resourceMapping.js\";\nimport { extractRoutes } from \"./routes.js\";\nimport {\n buildFlatSchema,\n buildOutputSchema,\n buildSharedDefs,\n} 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 const outputSchemaJson = buildOutputSchema(route, sharedDefs);\n const outputSchema = outputSchemaJson\n ? jsonSchemaAdapter(outputSchemaJson)\n : undefined;\n\n server.addTool({\n description:\n route.summary ?? `${route.method.toUpperCase()} ${route.path}`,\n execute: async (args) => {\n const result = await executeRequest({\n ...execOptions,\n args: args as Record<string, unknown>,\n bodyEncoding,\n wholeBodyKey,\n });\n\n return resolveToolResult(result, outputSchema);\n },\n name,\n parameters: jsonSchemaAdapter(flatSchema),\n ...(outputSchema ? { outputSchema } : {}),\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\n/**\n * Decides whether a tool call returns the parsed response object (letting\n * FastMCP populate `structuredContent` against `outputSchema`) or the plain\n * text fallback that always works.\n *\n * Real API responses commonly drift from their declared OpenAPI schema, and\n * FastMCP treats an `outputSchema` mismatch as a hard tool error (not a\n * silent fallback) — so a successful HTTP call could otherwise turn into a\n * failed MCP tool call purely from schema drift. This pre-validates against\n * the *exact same* schema instance that's attached as `Tool.outputSchema`\n * (AJV compilation is memoized and deterministic, so this agrees with\n * FastMCP's own re-validation), and only returns the object when it passes.\n *\n * The `Array.isArray` guard is required independently of AJV validation: a\n * schema describing array-shaped data can validate successfully, but\n * FastMCP's `structuredContent` is a plain-object field (`z.record(...)`)\n * that rejects an array at the `ContentResultZodSchema.parse` step — a\n * *different* check than AJV's, positioned after our pre-validation would\n * already have said \"fine.\" Without this guard, an array-typed response\n * schema reintroduces exactly the failure mode this function exists to\n * prevent.\n */\nasync function resolveToolResult(\n result: ExecuteRequestResult,\n outputSchema: ReturnType<typeof jsonSchemaAdapter> | undefined,\n): Promise<unknown> {\n const { json, text } = result;\n\n if (\n !outputSchema ||\n json === null ||\n typeof json !== \"object\" ||\n Array.isArray(json)\n ) {\n return text;\n }\n\n const validation = await outputSchema[\"~standard\"].validate(json);\n\n return validation.issues ? text : json;\n}\n\nfunction wrapAsResourceResult({ text }: ExecuteRequestResult): ResourceResult {\n // Content-sniffed rather than relying on executeRequest's header-gated\n // `json` field (which exists for the tool/outputSchema path): a server\n // that returns valid JSON without a matching content-type header should\n // still be recognized here, same as before this field existed.\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":[]}
|
|
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 interface ExecuteRequestResult {\n /** The response body, parsed, when the response's content-type indicated JSON and it parsed successfully. */\n json?: unknown;\n /** The response body as text — pretty-printed if `json` is set. */\n text: string;\n}\n\nexport async function executeRequest(\n options: ExecuteRequestOptions,\n): Promise<ExecuteRequestResult> {\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 appendQueryValue(query, mapping.name, mapping.style, value);\n break;\n }\n }\n\n let path = options.route.path;\n\n for (const [name, value] of Object.entries(pathParams)) {\n path = path.replaceAll(`{${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 const json: unknown = JSON.parse(text);\n return { json, text: JSON.stringify(json, 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 * Appends `value` under `key`, expanding nested structure with bracket\n * notation rather than JSON-encoding it:\n *\n * - a plain object → `key[subkey]=...` recursively;\n * - an array of scalars → repeated `key=...` entries (the existing,\n * unchanged convention for both query arrays and form arrays);\n * - an array containing an object → each such item bracket-expands under\n * `key[]` (PHP/Rails-style, and what Stripe's own list-of-objects form\n * fields expect);\n * - anything else (including a scalar where an object/array was expected —\n * e.g. a caller passing a plain value for a `deepObject`-styled query\n * param) → `key=value` directly, rather than assuming a shape that isn't\n * there.\n *\n * Shared between `encodeFormBody` (request bodies) and `deepObject` query\n * parameters (`appendQueryValue`) — both need the same expansion.\n */\nfunction appendBracketPairs(\n params: URLSearchParams,\n key: string,\n value: unknown,\n): void {\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item !== null && typeof item === \"object\") {\n appendBracketPairs(params, `${key}[]`, item);\n } else {\n params.append(key, String(item));\n }\n }\n\n return;\n }\n\n if (value !== null && typeof value === \"object\") {\n for (const [subKey, subValue] of Object.entries(\n value as Record<string, unknown>,\n )) {\n if (subValue !== undefined) {\n appendBracketPairs(params, `${key}[${subKey}]`, subValue);\n }\n }\n\n return;\n }\n\n params.append(key, String(value));\n}\n\n/**\n * Appends a query parameter's value using the serialization its declared\n * `style` requires. `deepObject` and `spaceDelimited`/`pipeDelimited` are\n * real, if less common, OpenAPI styles — Stripe alone uses `deepObject` 354\n * times across its filter/expand-style query params. Anything else (no\n * style, or the OpenAPI default `style: \"form\"`) keeps the existing\n * repeated-key serialization.\n */\nfunction appendQueryValue(\n query: URLSearchParams,\n name: string,\n style: string | undefined,\n value: unknown,\n): void {\n if (style === \"deepObject\") {\n appendBracketPairs(query, name, value);\n return;\n }\n\n if (style === \"spaceDelimited\" || style === \"pipeDelimited\") {\n const items = Array.isArray(value) ? value : [value];\n const separator = style === \"spaceDelimited\" ? \" \" : \"|\";\n query.append(name, items.map(String).join(separator));\n return;\n }\n\n for (const item of Array.isArray(value) ? value : [value]) {\n query.append(name, String(item));\n }\n}\n\n/**\n * Serializes a flattened body payload as `application/x-www-form-urlencoded`,\n * bracket-expanding nested objects/arrays (e.g. Stripe's own\n * `metadata[key]=value` style) via `appendBracketPairs` — the same helper\n * used for `deepObject`-styled query parameters, since both are the same\n * underlying problem: serializing non-scalar values into a position that\n * expects flat key/value pairs, not a JSON blob. `URLSearchParams` handles\n * 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 appendBracketPairs(params, key, value);\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.replaceAll(`{${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 OpenApiResponse,\n RawPathItem,\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 path items, 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, rawPathItem] of Object.entries(document.paths ?? {})) {\n const pathItem = resolvePathItem(document, rawPathItem);\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 responses: resolveResponses(document, operation.responses),\n servers: [operation.servers, pathItem.servers, document.servers].find(\n (servers) => servers?.length,\n ),\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\n/**\n * Resolves a path item's `$ref`, following chains of them — bundling leaves\n * shared path items as local refs, and doesn't collapse a chain whose\n * intermediate items have sibling fields.\n *\n * A referring item's sibling fields (e.g. `parameters`) replace the\n * referenced item's rather than merging with them: bundling inlines a shared\n * path item at one referrer with that referrer's siblings folded in, so\n * merging would leak them into every other path sharing the item.\n */\nfunction resolvePathItem(\n document: BundledOpenApiDocument,\n pathItem: RawPathItem,\n): RawPathItem {\n const visited = new Set<string>();\n let resolved = pathItem;\n\n while (resolved.$ref && !visited.has(resolved.$ref)) {\n visited.add(resolved.$ref);\n\n const { $ref, ...siblings } = resolved;\n\n resolved = { ...resolveRef<RawPathItem>(document, { $ref }), ...siblings };\n }\n\n return resolved;\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\n/** A response object can itself be `$ref`'d to `#/components/responses/X`. */\nfunction resolveResponses(\n document: BundledOpenApiDocument,\n rawResponses:\n | Record<string, OpenApiParameterRef | OpenApiResponse>\n | undefined,\n): Record<string, OpenApiResponse> | undefined {\n if (!rawResponses) {\n return undefined;\n }\n\n return Object.fromEntries(\n Object.entries(rawResponses).map(([code, response]) => [\n code,\n resolveRef<OpenApiResponse>(document, response),\n ]),\n );\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 * Keys whose values are dropped entirely — not just left unwalked as\n * `DATA_KEYS` are — when building a schema that gets handed to AJV. Real\n * specs (Box) embed full, realistic sample objects under `example`, which\n * can coincidentally contain fields shaped like JSON Schema keywords (e.g.\n * Box's own `$id` concept on a metadata object, reusing the same example\n * value across multiple schemas). AJV's `$id`-discovery pass doesn't know\n * these are documentation rather than schema, and throws\n * (\"reference ... resolves to more than one schema\") on the collision.\n * These keys carry zero validation meaning, so dropping them removes the\n * only thing AJV could misinterpret this way.\n *\n * Stripping is opt-in (`stripExamples`) and only `buildOutputSchema` opts\n * in. Tool *input* schemas keep their examples: they are useful signal for\n * a model filling in arguments, and the collision has never been observed\n * on that path — Box's input schemas compile fine on `main` today.\n */\nconst STRIP_KEYS = new Set([\"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 /** Only meaningful for `in: \"query\"` — see `OpenApiParameter.style`. */\n style?: 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, style: param.style };\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\nconst SUCCESS_STATUS_PATTERN = /^2\\d\\d$/;\n\nconst MAX_OUTPUT_SCHEMA_DEFS = 50;\n\n/**\n * Builds a tool's `outputSchema` from the route's first declared `2xx`\n * `application/json` response, or `undefined` if there isn't a usable one.\n *\n * Requires the schema to resolve to an explicit `type: \"object\"` — a bare\n * `$ref` (very common; a response schema is often just\n * `{ $ref: \"#/components/schemas/Pet\" }`) is followed via the same\n * `resolveComponentRef` chain-following already used for form-body `$ref`s,\n * so this still covers the common case without needing the schema to spell\n * out `type` inline. This is deliberately **not** \"anything not explicitly\n * non-object\": the MCP SDK's client-side `tools/list` response validation\n * requires an advertised `outputSchema.type` to literally be the *string*\n * `\"object\"` — a bare, unresolved `$ref` (no top-level `type` at all) fails\n * that validation and breaks `tools/list` for *every* tool in the response,\n * not just the one with the bad schema. Confirmed the hard way: an earlier,\n * more permissive version of this function did exactly that against a real\n * spec.\n *\n * The object-shape check happens on the schema *after* `rewriteComponentRefs`\n * (which folds `nullable: true` into `type: [\"object\", \"null\"]`), not\n * before — checking beforehand would miss that an inline `{ type: \"object\",\n * nullable: true }` response schema turns into an *array*-valued `type`\n * post-rewrite, which fails that same literal-string protocol requirement\n * just as a bare `$ref` does. When the resolved type is `[\"object\", \"null\"]`\n * (or any array containing `\"object\"`), the advertised type is normalized\n * back down to the literal string `\"object\"` — dropping the `\"null\"`\n * alternative is safe because a genuinely `null` response then simply fails\n * the runtime pre-validation safety net below and falls back to plain text,\n * rather than the *type declaration itself* breaking the whole tool list.\n * `fromOpenAPI.ts`'s pre-validation against the *actual* response is what\n * that safety net is for; getting the static shape right here is purely\n * about protocol validity.\n *\n * Unlike `buildFlatSchema`'s tool input schema, this does **not** set\n * `additionalProperties: false` — an undocumented extra field in a real\n * response is the most common form of spec/API drift, and forcing strict\n * mode here would make that safety net reject constantly.\n *\n * Also skips wiring when the schema transitively references more than\n * `MAX_OUTPUT_SCHEMA_DEFS` definitions. This isn't rare: real \"core\"\n * response objects (Stripe's `Charge`, `Customer`, `PaymentIntent`, ...)\n * routinely embed dozens of other resource types, which themselves embed\n * more — measured directly against Stripe's real spec, the *median*\n * operation's output schema pulled in 868 definitions, and the full\n * tools/list response across all 588 operations would have been ~320MB.\n * A schema this large is also of limited practical use as structured\n * output regardless of size — an LLM isn't better served by a 900-type\n * validation schema than by the same data as text. Skipped operations\n * keep today's plain-text-only behavior; nothing breaks, they just don't\n * get `structuredContent`.\n */\nexport function buildOutputSchema(\n route: HttpRoute,\n sharedDefs: Record<string, OpenApiSchema> | undefined,\n): JsonSchemaObject | undefined {\n const successEntry = Object.entries(route.responses ?? {}).find(([code]) =>\n SUCCESS_STATUS_PATTERN.test(code),\n );\n\n const declaredSchema =\n successEntry?.[1].content?.[\"application/json\"]?.schema;\n\n if (!declaredSchema) {\n return undefined;\n }\n\n const schema = resolveComponentRef(declaredSchema, sharedDefs);\n const rewritten = rewriteComponentRefs(schema, true) as OpenApiSchema;\n const { type } = rewritten;\n\n const isObjectShaped =\n type === \"object\" || (Array.isArray(type) && type.includes(\"object\"));\n\n if (!isObjectShaped) {\n return undefined;\n }\n\n // The protocol requires the literal string \"object\", not an array — see\n // the doc comment above for why dropping \"null\" here is safe.\n const normalized = { ...rewritten, type: \"object\" };\n const usedDefs = sharedDefs && filterReferencedDefs(normalized, sharedDefs);\n\n if (usedDefs && Object.keys(usedDefs).length > MAX_OUTPUT_SCHEMA_DEFS) {\n return undefined;\n }\n\n return {\n ...normalized,\n ...(usedDefs\n ? {\n $defs: rewriteNode(usedDefs, \"schemaMap\", true) as Record<\n string,\n OpenApiSchema\n >,\n }\n : {}),\n } as JsonSchemaObject;\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>(\n value: TValue,\n stripExamples = false,\n): TValue {\n return rewriteNode(value, \"schema\", stripExamples) 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(\n value: unknown,\n mode: WalkMode,\n stripExamples = false,\n): unknown {\n if (mode === \"data\") {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => rewriteNode(item, \"schema\", stripExamples));\n }\n\n if (!value || typeof value !== \"object\") {\n return value;\n }\n\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(\n ([key]) => !stripExamples || mode !== \"schema\" || !STRIP_KEYS.has(key),\n )\n .map(([key, entryValue]): [string, unknown] => {\n if (mode === \"schemaMap\") {\n return [key, rewriteNode(entryValue, \"schema\", stripExamples)];\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), stripExamples)];\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, type ExecuteRequestResult } from \"./requestBuilder.js\";\nimport {\n buildResourceMapping,\n isEligibleForResource,\n} from \"./resourceMapping.js\";\nimport { extractRoutes } from \"./routes.js\";\nimport {\n buildFlatSchema,\n buildOutputSchema,\n buildSharedDefs,\n} 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: route.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 const outputSchemaJson = buildOutputSchema(route, sharedDefs);\n const outputSchema = outputSchemaJson\n ? jsonSchemaAdapter(outputSchemaJson)\n : undefined;\n\n server.addTool({\n description:\n route.summary ?? `${route.method.toUpperCase()} ${route.path}`,\n execute: async (args) => {\n const result = await executeRequest({\n ...execOptions,\n args: args as Record<string, unknown>,\n bodyEncoding,\n wholeBodyKey,\n });\n\n return resolveToolResult(result, outputSchema);\n },\n name,\n parameters: jsonSchemaAdapter(flatSchema),\n ...(outputSchema ? { outputSchema } : {}),\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\n/**\n * Decides whether a tool call returns the parsed response object (letting\n * FastMCP populate `structuredContent` against `outputSchema`) or the plain\n * text fallback that always works.\n *\n * Real API responses commonly drift from their declared OpenAPI schema, and\n * FastMCP treats an `outputSchema` mismatch as a hard tool error (not a\n * silent fallback) — so a successful HTTP call could otherwise turn into a\n * failed MCP tool call purely from schema drift. This pre-validates against\n * the *exact same* schema instance that's attached as `Tool.outputSchema`\n * (AJV compilation is memoized and deterministic, so this agrees with\n * FastMCP's own re-validation), and only returns the object when it passes.\n *\n * The `Array.isArray` guard is required independently of AJV validation: a\n * schema describing array-shaped data can validate successfully, but\n * FastMCP's `structuredContent` is a plain-object field (`z.record(...)`)\n * that rejects an array at the `ContentResultZodSchema.parse` step — a\n * *different* check than AJV's, positioned after our pre-validation would\n * already have said \"fine.\" Without this guard, an array-typed response\n * schema reintroduces exactly the failure mode this function exists to\n * prevent.\n */\nasync function resolveToolResult(\n result: ExecuteRequestResult,\n outputSchema: ReturnType<typeof jsonSchemaAdapter> | undefined,\n): Promise<unknown> {\n const { json, text } = result;\n\n if (\n !outputSchema ||\n json === null ||\n typeof json !== \"object\" ||\n Array.isArray(json)\n ) {\n return text;\n }\n\n const validation = await outputSchema[\"~standard\"].validate(json);\n\n return validation.issues ? text : json;\n}\n\nfunction wrapAsResourceResult({ text }: ExecuteRequestResult): ResourceResult {\n // Content-sniffed rather than relying on executeRequest's header-gated\n // `json` field (which exists for the tool/outputSchema path): a server\n // that returns valid JSON without a matching content-type header should\n // still be recognized here, same as before this field existed.\n try {\n JSON.parse(text);\n return { mimeType: \"application/json\", text };\n } catch {\n return { mimeType: \"text/plain\", text };\n }\n}\n"],"mappings":";;;;;;;;AAAA,OAAO,mBAAmB;AAuB1B,eAAsB,SACpB,MACqB;AACrB,QAAM,WAAY,MAAM,cAAc;AAAA,IACpC;AAAA,EACF;AAEA,MAAI,CAAC,SAAS,SAAS,WAAW,IAAI,GAAG;AACvC,UAAM,IAAI;AAAA,MACR,0DACE,SAAS,WAAW,SAAS,WAAW,yBAC1C;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,QAAQ,OAAO,SAAS,YAAY,UAAU,IAAI,IAAI,OAAO;AAAA,EAC/D;AACF;AAEA,SAAS,UAAU,OAAwB;AACzC,SAAO,MAAM,WAAW,SAAS,KAAK,MAAM,WAAW,UAAU;AACnE;;;AC5CA,IAAM,kBAAkB;AAGxB,IAAM,kBAAkB,kBAAkB;AAmBnC,SAAS,cACd,QACA,UACwB;AACxB,QAAM,QAAQ,oBAAI,IAAuB;AACzC,QAAM,OAAO,oBAAI,IAAY;AAE7B,aAAW,SAAS,QAAQ;AAC1B,UAAM,OAAO,QAAQ,YAAY,OAAO,QAAQ,CAAC;AACjD,QAAI,YAAY;AAChB,QAAI,SAAS;AAEb,WAAO,KAAK,IAAI,SAAS,GAAG;AAC1B,gBAAU;AACV,kBAAY,GAAG,IAAI,IAAI,MAAM;AAAA,IAC/B;AAEA,SAAK,IAAI,SAAS;AAClB,UAAM,IAAI,OAAO,SAAS;AAAA,EAC5B;AAEA,SAAO;AACT;AAEA,SAAS,YACP,OACA,UACQ;AACR,MAAI,MAAM,aAAa;AACrB,WAAO,WAAW,MAAM,WAAW,KAAK,MAAM,YAAY,MAAM,IAAI,EAAE,CAAC;AAAA,EACzE;AAEA,SAAO,MAAM,WAAW,GAAG,MAAM,MAAM,IAAI,MAAM,IAAI;AACvD;AAEA,SAAS,QAAQ,OAAuB;AACtC,QAAM,OAAO,MACV,QAAQ,mBAAmB,GAAG,EAC9B,QAAQ,OAAO,GAAG,EAClB,QAAQ,UAAU,EAAE,EACpB,MAAM,GAAG,eAAe;AAE3B,SAAO,QAAQ;AACjB;;;ACzCA,eAAsB,eACpB,SAC+B;AAC/B,QAAM,UAAU;AAAA,IACd,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV;AAEA,QAAM,aAAqC,CAAC;AAC5C,QAAM,QAAQ,IAAI,gBAAgB;AAMlC,QAAM,UAAU,IAAI,QAAQ,MAAM,eAAe,QAAQ,OAAO,CAAC;AACjE,QAAM,YAAqC,CAAC;AAE5C,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAQ,IAAI,GAAG;AACvD,UAAM,UAAU,QAAQ,aAAa,GAAG;AAExC,QAAI,CAAC,WAAW,UAAU,QAAW;AACnC;AAAA,IACF;AAEA,YAAQ,QAAQ,IAAI;AAAA,MAClB,KAAK;AACH,kBAAU,QAAQ,IAAI,IAAI;AAC1B;AAAA,MACF,KAAK,UAAU;AACb,cAAM,WAAW,QAAQ,IAAI,QAAQ;AACrC,gBAAQ;AAAA,UACN;AAAA,UACA,WACI,GAAG,QAAQ,KAAK,QAAQ,IAAI,IAAI,OAAO,KAAK,CAAC,KAC7C,GAAG,QAAQ,IAAI,IAAI,OAAO,KAAK,CAAC;AAAA,QACtC;AACA;AAAA,MACF;AAAA,MACA,KAAK;AACH,gBAAQ,IAAI,QAAQ,MAAM,OAAO,KAAK,CAAC;AACvC;AAAA,MACF,KAAK;AACH,mBAAW,QAAQ,IAAI,IAAI,OAAO,KAAK;AACvC;AAAA,MACF,KAAK;AACH,yBAAiB,OAAO,QAAQ,MAAM,QAAQ,OAAO,KAAK;AAC1D;AAAA,IACJ;AAAA,EACF;AAEA,MAAI,OAAO,QAAQ,MAAM;AAEzB,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,UAAU,GAAG;AACtD,WAAO,KAAK,WAAW,IAAI,IAAI,KAAK,mBAAmB,KAAK,CAAC;AAAA,EAC/D;AAEA,QAAM,MAAM,IAAI,IAAI,QAAQ,QAAQ,OAAO,EAAE,IAAI,IAAI;AACrD,MAAI,SAAS,MAAM,SAAS;AAE5B,MAAI;AAEJ,MAAI,OAAO,KAAK,SAAS,EAAE,SAAS,GAAG;AACrC,UAAM,UAAU,QAAQ,eACpB,UAAU,QAAQ,YAAY,IAC9B;AAEJ,QAAI,QAAQ,iBAAiB,QAAQ;AACnC,UAAI,CAAC,QAAQ,IAAI,cAAc,GAAG;AAChC,gBAAQ,IAAI,gBAAgB,mCAAmC;AAAA,MACjE;AAEA,aAAO,eAAe,OAAO;AAAA,IAC/B,OAAO;AACL,UAAI,CAAC,QAAQ,IAAI,cAAc,GAAG;AAChC,gBAAQ,IAAI,gBAAgB,kBAAkB;AAAA,MAChD;AAEA,aAAO,KAAK,UAAU,OAAO;AAAA,IAC/B;AAAA,EACF;AAEA,QAAM,WAAW,MAAM,QAAQ,UAAU,IAAI,SAAS,GAAG;AAAA,IACvD;AAAA,IACA;AAAA,IACA,QAAQ,QAAQ,MAAM,OAAO,YAAY;AAAA,EAC3C,CAAC;AAED,QAAM,OAAO,MAAM,SAAS,KAAK;AAEjC,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI;AAAA,MACR,GAAG,QAAQ,MAAM,OAAO,YAAY,CAAC,IAAI,IAAI,gBAAgB,SAAS,MAAM,KAAK,KAAK,MAAM,GAAG,GAAI,CAAC;AAAA,IACtG;AAAA,EACF;AAEA,MAAI,SAAS,QAAQ,IAAI,cAAc,GAAG,SAAS,MAAM,GAAG;AAC1D,QAAI;AACF,YAAM,OAAgB,KAAK,MAAM,IAAI;AACrC,aAAO,EAAE,MAAM,MAAM,KAAK,UAAU,MAAM,MAAM,CAAC,EAAE;AAAA,IACrD,QAAQ;AACN,aAAO,EAAE,KAAK;AAAA,IAChB;AAAA,EACF;AAEA,SAAO,EAAE,KAAK;AAChB;AAQO,SAAS,eACd,SACA,QACA,aACQ;AACR,MAAI,aAAa;AACf,WAAO,YAAY,QAAQ,OAAO,EAAE;AAAA,EACtC;AAEA,QAAM,SAAS,UAAU,CAAC;AAE1B,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MAAI,MAAM,OAAO;AAEjB,aAAW,CAAC,MAAM,QAAQ,KAAK,OAAO,QAAQ,OAAO,aAAa,CAAC,CAAC,GAAG;AACrE,UAAM,IAAI,WAAW,IAAI,IAAI,KAAK,SAAS,OAAO;AAAA,EACpD;AAEA,MAAI;AACF,WAAO,IAAI,IAAI,GAAG,EAAE,SAAS,EAAE,QAAQ,OAAO,EAAE;AAAA,EAClD,QAAQ;AACN,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI;AAAA,QACR,2CAA2C,GAAG;AAAA,MAChD;AAAA,IACF;AAEA,WAAO,IAAI,IAAI,KAAK,MAAM,EAAE,SAAS,EAAE,QAAQ,OAAO,EAAE;AAAA,EAC1D;AACF;AAoBA,SAAS,mBACP,QACA,KACA,OACM;AACN,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,eAAW,QAAQ,OAAO;AACxB,UAAI,SAAS,QAAQ,OAAO,SAAS,UAAU;AAC7C,2BAAmB,QAAQ,GAAG,GAAG,MAAM,IAAI;AAAA,MAC7C,OAAO;AACL,eAAO,OAAO,KAAK,OAAO,IAAI,CAAC;AAAA,MACjC;AAAA,IACF;AAEA;AAAA,EACF;AAEA,MAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;AAC/C,eAAW,CAAC,QAAQ,QAAQ,KAAK,OAAO;AAAA,MACtC;AAAA,IACF,GAAG;AACD,UAAI,aAAa,QAAW;AAC1B,2BAAmB,QAAQ,GAAG,GAAG,IAAI,MAAM,KAAK,QAAQ;AAAA,MAC1D;AAAA,IACF;AAEA;AAAA,EACF;AAEA,SAAO,OAAO,KAAK,OAAO,KAAK,CAAC;AAClC;AAUA,SAAS,iBACP,OACA,MACA,OACA,OACM;AACN,MAAI,UAAU,cAAc;AAC1B,uBAAmB,OAAO,MAAM,KAAK;AACrC;AAAA,EACF;AAEA,MAAI,UAAU,oBAAoB,UAAU,iBAAiB;AAC3D,UAAM,QAAQ,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;AACnD,UAAM,YAAY,UAAU,mBAAmB,MAAM;AACrD,UAAM,OAAO,MAAM,MAAM,IAAI,MAAM,EAAE,KAAK,SAAS,CAAC;AACpD;AAAA,EACF;AAEA,aAAW,QAAQ,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK,GAAG;AACzD,UAAM,OAAO,MAAM,OAAO,IAAI,CAAC;AAAA,EACjC;AACF;AAWA,SAAS,eAAe,SAA0B;AAChD,QAAM,SAAS,IAAI,gBAAgB;AAEnC,MAAI,WAAW,OAAO,YAAY,YAAY,CAAC,MAAM,QAAQ,OAAO,GAAG;AACrE,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO;AAAA,MAChC;AAAA,IACF,GAAG;AACD,UAAI,UAAU,QAAW;AACvB;AAAA,MACF;AAEA,yBAAmB,QAAQ,KAAK,KAAK;AAAA,IACvC;AAAA,EACF;AAEA,SAAO,OAAO,SAAS;AACzB;AAEA,eAAe,eACb,SACiC;AACjC,MAAI,CAAC,SAAS;AACZ,WAAO,CAAC;AAAA,EACV;AAEA,SAAO,OAAO,YAAY,aAAa,MAAM,QAAQ,IAAI,EAAE,GAAG,QAAQ;AACxE;;;AC5QO,SAAS,qBACd,OACA,MACA,cACA,cACiB;AACjB,QAAM,UAAU,OAAO,QAAQ,YAAY;AAE3C,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO,EAAE,MAAM,YAAY,KAAK,aAAa,IAAI,GAAG,MAAM,IAAI,GAAG;AAAA,EACnE;AAEA,QAAM,WAAW,IAAI,IAAI,gBAAgB,CAAC,CAAC;AAC3C,QAAM,OAA8C,CAAC;AACrD,QAAM,YAAsB,CAAC;AAC7B,MAAI,OAAO,MAAM;AAEjB,aAAW,CAAC,SAAS,OAAO,KAAK,SAAS;AACxC,QAAI,QAAQ,OAAO,QAAQ;AACzB,UAAI,YAAY,QAAQ,MAAM;AAC5B,eAAO,KAAK,WAAW,IAAI,QAAQ,IAAI,KAAK,IAAI,OAAO,GAAG;AAAA,MAC5D;AAAA,IACF,OAAO;AACL,gBAAU,KAAK,OAAO;AAAA,IACxB;AAEA,SAAK,KAAK,EAAE,MAAM,SAAS,UAAU,SAAS,IAAI,OAAO,EAAE,CAAC;AAAA,EAC9D;AAEA,QAAM,cACJ,aAAa,IAAI,GAAG,IAAI,MACvB,UAAU,SAAS,IAAI,KAAK,UAAU,KAAK,GAAG,CAAC,MAAM;AAExD,SAAO,EAAE,MAAM,MAAM,YAAY,YAAY;AAC/C;AAiBO,SAAS,sBAAsB,OAA2B;AAC/D,SAAO,MAAM,WAAW,MAAM,CAAC,UAAU;AACvC,QAAI,MAAM,OAAO,YAAY,MAAM,OAAO,UAAU;AAClD,aAAO;AAAA,IACT;AAEA,WAAO,MAAM,QAAQ,SAAS;AAAA,EAChC,CAAC;AACH;;;ACzEA,IAAM,eAA6B,CAAC,OAAO,OAAO,QAAQ,UAAU,OAAO;AAUpE,SAAS,cAAc,UAA+C;AAC3E,QAAM,SAAsB,CAAC;AAE7B,aAAW,CAAC,MAAM,WAAW,KAAK,OAAO,QAAQ,SAAS,SAAS,CAAC,CAAC,GAAG;AACtE,UAAM,WAAW,gBAAgB,UAAU,WAAW;AACtD,UAAM,mBAAmB,SAAS,cAAc,CAAC,GAAG;AAAA,MAAI,CAAC,UACvD,WAA6B,UAAU,KAAK;AAAA,IAC9C;AAEA,eAAW,UAAU,cAAc;AACjC,YAAM,YAAY,SAAS,MAAM;AAEjC,UAAI,CAAC,WAAW;AACd;AAAA,MACF;AAEA,YAAM,mBAAmB,UAAU,cAAc,CAAC,GAAG;AAAA,QAAI,CAAC,UACxD,WAA6B,UAAU,KAAK;AAAA,MAC9C;AAEA,aAAO,KAAK;AAAA,QACV,YAAY,UAAU,cAAc;AAAA,QACpC;AAAA,QACA,aAAa,UAAU;AAAA,QACvB,YAAY,gBAAgB,iBAAiB,eAAe;AAAA,QAC5D;AAAA,QACA,aAAa,UAAU,cACnB,WAA+B,UAAU,UAAU,WAAW,IAC9D;AAAA,QACJ,WAAW,iBAAiB,UAAU,UAAU,SAAS;AAAA,QACzD,SAAS,CAAC,UAAU,SAAS,SAAS,SAAS,SAAS,OAAO,EAAE;AAAA,UAC/D,CAAC,YAAY,SAAS;AAAA,QACxB;AAAA,QACA,SAAS,UAAU;AAAA,QACnB,MAAM,UAAU,QAAQ,CAAC;AAAA,MAC3B,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,gBACP,WACA,gBACoB;AACpB,QAAM,aAAa,IAAI;AAAA,IACrB,eAAe,IAAI,CAAC,UAAU,GAAG,MAAM,EAAE,IAAI,MAAM,IAAI,EAAE;AAAA,EAC3D;AAEA,SAAO;AAAA,IACL,GAAG,UAAU;AAAA,MACX,CAAC,UAAU,CAAC,WAAW,IAAI,GAAG,MAAM,EAAE,IAAI,MAAM,IAAI,EAAE;AAAA,IACxD;AAAA,IACA,GAAG;AAAA,EACL;AACF;AAYA,SAAS,gBACP,UACA,UACa;AACb,QAAM,UAAU,oBAAI,IAAY;AAChC,MAAI,WAAW;AAEf,SAAO,SAAS,QAAQ,CAAC,QAAQ,IAAI,SAAS,IAAI,GAAG;AACnD,YAAQ,IAAI,SAAS,IAAI;AAEzB,UAAM,EAAE,MAAM,GAAG,SAAS,IAAI;AAE9B,eAAW,EAAE,GAAG,WAAwB,UAAU,EAAE,KAAK,CAAC,GAAG,GAAG,SAAS;AAAA,EAC3E;AAEA,SAAO;AACT;AAEA,SAAS,WACP,UACA,OACQ;AACR,MAAI,CAAC,SAAS,OAAO,UAAU,YAAY,EAAE,UAAU,QAAQ;AAC7D,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,MAAM;AAEtB,MAAI,CAAC,QAAQ,WAAW,IAAI,GAAG;AAG7B,UAAM,IAAI,MAAM,4CAA4C,OAAO,EAAE;AAAA,EACvE;AAKA,QAAM,WAAW,QACd,MAAM,CAAC,EACP,MAAM,GAAG,EACT;AAAA,IAAI,CAAC,YACJ,mBAAmB,QAAQ,WAAW,MAAM,GAAG,EAAE,WAAW,MAAM,GAAG,CAAC;AAAA,EACxE;AAEF,MAAI,OAAgB;AAEpB,aAAW,WAAW,UAAU;AAC9B,WAAQ,OAA+C,OAAO;AAAA,EAChE;AAEA,SAAO;AACT;AAGA,SAAS,iBACP,UACA,cAG6C;AAC7C,MAAI,CAAC,cAAc;AACjB,WAAO;AAAA,EACT;AAEA,SAAO,OAAO;AAAA,IACZ,OAAO,QAAQ,YAAY,EAAE,IAAI,CAAC,CAAC,MAAM,QAAQ,MAAM;AAAA,MACrD;AAAA,MACA,WAA4B,UAAU,QAAQ;AAAA,IAChD,CAAC;AAAA,EACH;AACF;;;ACjJA,IAAM,kBAAkB,oBAAI,IAAI;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAMD,IAAM,YAAY,oBAAI,IAAI,CAAC,SAAS,WAAW,QAAQ,WAAW,UAAU,CAAC;AAmB7E,IAAM,aAAa,oBAAI,IAAI,CAAC,WAAW,UAAU,CAAC;AA6D3C,SAAS,gBACd,OACA,YACkB;AAClB,QAAM,SAAS,oBAAI,IAAgC;AAEnD,aAAW,SAAS,MAAM,YAAY;AACpC,UAAM,OAAO,OAAO,IAAI,MAAM,IAAI,KAAK,CAAC;AACxC,SAAK,KAAK,KAAK;AACf,WAAO,IAAI,MAAM,MAAM,IAAI;AAAA,EAC7B;AAEA,QAAM;AAAA,IACJ;AAAA,IACA,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,EACF,IAAI;AAAA,IACF,MAAM,WAAW,QAAQ,SAAY,MAAM;AAAA,IAC3C;AAAA,EACF;AAEA,QAAM,aAA4C,CAAC;AACnD,QAAM,WAAqB,CAAC;AAC5B,QAAM,eAAiD,CAAC;AAExD,aAAW,CAAC,MAAM,WAAW,KAAK,QAAQ;AACxC,UAAM,WAAW,YAAY,SAAS,KAAK,eAAe,IAAI,IAAI;AAElE,eAAW,SAAS,aAAa;AAC/B,YAAM,MAAM,WAAW,GAAG,IAAI,KAAK,MAAM,EAAE,KAAK;AAEhD,iBAAW,GAAG,IAAI;AAAA,QAChB,MAAM,UAAU,EAAE,MAAM,SAAS;AAAA,MACnC;AACA,mBAAa,GAAG,IAAI,EAAE,IAAI,MAAM,IAAI,MAAM,OAAO,MAAM,MAAM;AAE7D,UAAI,MAAM,OAAO,UAAU,MAAM,UAAU;AACzC,iBAAS,KAAK,GAAG;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AAEA,aAAW,CAAC,MAAM,EAAE,UAAU,YAAY,OAAO,CAAC,KAAK,gBAAgB;AACrE,eAAW,IAAI,IAAI,qBAAqB,MAAM;AAC9C,iBAAa,IAAI,IAAI,EAAE,IAAI,QAAQ,KAAK;AAExC,QAAI,YAAY;AACd,eAAS,KAAK,IAAI;AAAA,IACpB;AAAA,EACF;AAEA,QAAM,aAA+B;AAAA,IACnC,sBAAsB;AAAA,IACtB;AAAA,IACA,MAAM;AAAA,IACN,GAAI,SAAS,SAAS,IAAI,EAAE,SAAS,IAAI,CAAC;AAAA,EAC5C;AAMA,QAAM,WAAW,cAAc,qBAAqB,YAAY,UAAU;AAE1E,MAAI,UAAU;AACZ,eAAW,QAAQ;AAAA,EACrB;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEO,SAAS,gBACd,UAC2C;AAC3C,QAAM,UAAU,SAAS,YAAY;AAErC,MAAI,CAAC,WAAW,OAAO,KAAK,OAAO,EAAE,WAAW,GAAG;AACjD,WAAO;AAAA,EACT;AAEA,SAAO,YAAY,SAAS,WAAW;AACzC;AAEA,IAAM,yBAAyB;AAE/B,IAAM,yBAAyB;AAqDxB,SAAS,kBACd,OACA,YAC8B;AAC9B,QAAM,eAAe,OAAO,QAAQ,MAAM,aAAa,CAAC,CAAC,EAAE;AAAA,IAAK,CAAC,CAAC,IAAI,MACpE,uBAAuB,KAAK,IAAI;AAAA,EAClC;AAEA,QAAM,iBACJ,eAAe,CAAC,EAAE,UAAU,kBAAkB,GAAG;AAEnD,MAAI,CAAC,gBAAgB;AACnB,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,oBAAoB,gBAAgB,UAAU;AAC7D,QAAM,YAAY,qBAAqB,QAAQ,IAAI;AACnD,QAAM,EAAE,KAAK,IAAI;AAEjB,QAAM,iBACJ,SAAS,YAAa,MAAM,QAAQ,IAAI,KAAK,KAAK,SAAS,QAAQ;AAErE,MAAI,CAAC,gBAAgB;AACnB,WAAO;AAAA,EACT;AAIA,QAAM,aAAa,EAAE,GAAG,WAAW,MAAM,SAAS;AAClD,QAAM,WAAW,cAAc,qBAAqB,YAAY,UAAU;AAE1E,MAAI,YAAY,OAAO,KAAK,QAAQ,EAAE,SAAS,wBAAwB;AACrE,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAI,WACA;AAAA,MACE,OAAO,YAAY,UAAU,aAAa,IAAI;AAAA,IAIhD,IACA,CAAC;AAAA,EACP;AACF;AAYO,SAAS,qBACd,OACA,gBAAgB,OACR;AACR,SAAO,YAAY,OAAO,UAAU,aAAa;AACnD;AAEA,SAAS,UAAU,KAAuB;AACxC,MAAI,UAAU,IAAI,GAAG,GAAG;AACtB,WAAO;AAAA,EACT;AAEA,SAAO,gBAAgB,IAAI,GAAG,IAAI,cAAc;AAClD;AAEA,SAAS,oBAAoB,KAAiC;AAC5D,aAAW,UAAU,CAAC,yBAAyB,UAAU,GAAG;AAC1D,QAAI,IAAI,WAAW,MAAM,GAAG;AAC1B,aAAO,IAAI,MAAM,OAAO,MAAM;AAAA,IAChC;AAAA,EACF;AAEA,SAAO;AACT;AAmCA,SAAS,sBACP,aACA,YAMA;AACA,QAAM,aAAa,oBAAI,IAGrB;AAEF,QAAM,UAAU,aAAa;AAE7B,MAAI,CAAC,SAAS;AACZ,WAAO,EAAE,WAAW;AAAA,EACtB;AAEA,QAAM,UAAU,sBAAsB;AACtC,QAAM,UAAU,uCAAuC;AAEvD,MAAI,CAAC,WAAW,CAAC,SAAS;AACxB,UAAM,eAAe,OAAO,KAAK,OAAO;AAExC,WAAO,aAAa,SAAS,IACzB,EAAE,YAAY,4BAA4B,aAAa,CAAC,EAAE,IAC1D,EAAE,WAAW;AAAA,EACnB;AAEA,QAAM,eAAgC,UAAU,SAAS;AACzD,QAAM,iBAAiB,UACnB,QAAQ,kBAAkB,GAAG,SAC7B,QAAQ,mCAAmC,GAAG;AAElD,MAAI,CAAC,gBAAgB;AAGnB,WAAO,EAAE,cAAc,WAAW;AAAA,EACpC;AAEA,QAAM,SACJ,iBAAiB,SACb,oBAAoB,gBAAgB,UAAU,IAC9C;AAEN,QAAM,mBAAmB,OAAO;AAIhC,MAAI,OAAO,SAAS,YAAY,kBAAkB;AAChD,UAAM,gBAAgB,IAAI;AAAA,MACvB,OAAO,YAAqC,CAAC;AAAA,IAChD;AAEA,eAAW,CAAC,MAAM,cAAc,KAAK,OAAO,QAAQ,gBAAgB,GAAG;AACrE,iBAAW,IAAI,MAAM;AAAA,QACnB,UAAU,cAAc,IAAI,IAAI;AAAA,QAChC,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAEA,WAAO,EAAE,cAAc,WAAW;AAAA,EACpC;AAEA,MAAI,iBAAiB,QAAQ;AAC3B,WAAO;AAAA,MACL;AAAA,MACA,4BAA4B;AAAA,IAC9B;AAAA,EACF;AAIA,aAAW,IAAI,QAAQ;AAAA,IACrB,UAAU,aAAa,YAAY;AAAA,IACnC;AAAA,EACF,CAAC;AAED,SAAO,EAAE,cAAc,YAAY,cAAc,OAAO;AAC1D;AAOA,SAAS,qBACP,MACA,SAC2C;AAC3C,QAAM,aAAa,oBAAI,IAAY;AACnC,QAAM,QAAmB,CAAC,IAAI;AAE9B,SAAO,MAAM,SAAS,GAAG;AACvB,UAAM,UAAU,MAAM,IAAI;AAE1B,QAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,YAAM,KAAK,GAAG,OAAO;AACrB;AAAA,IACF;AAEA,QAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C;AAAA,IACF;AAEA,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO;AAAA,MAChC;AAAA,IACF,GAAG;AACD,UACE,QAAQ,UACR,OAAO,UAAU,YACjB,MAAM,WAAW,UAAU,GAC3B;AACA,cAAM,OAAO,MAAM,MAAM,WAAW,MAAM;AAE1C,YAAI,QAAQ,IAAI,KAAK,CAAC,WAAW,IAAI,IAAI,GAAG;AAC1C,qBAAW,IAAI,IAAI;AACnB,gBAAM,KAAK,QAAQ,IAAI,CAAC;AAAA,QAC1B;AAEA;AAAA,MACF;AAEA,YAAM,KAAK,KAAK;AAAA,IAClB;AAAA,EACF;AAEA,MAAI,WAAW,SAAS,GAAG;AACzB,WAAO;AAAA,EACT;AAEA,SAAO,OAAO;AAAA,IACZ,CAAC,GAAG,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,QAAQ,IAAI,CAAC,CAAC;AAAA,EACrD;AACF;AAYA,SAAS,kBACP,QACyB;AACzB,MAAI,EAAE,cAAc,SAAS;AAC3B,WAAO;AAAA,EACT;AAEA,QAAM,EAAE,UAAU,MAAM,GAAG,KAAK,IAAI;AAEpC,MAAI,aAAa,MAAM;AACrB,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,SAAS,UAAU;AAC5B,WAAO,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,MAAM,EAAE;AAAA,EACzC;AAEA,MAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,WAAO,EAAE,GAAG,MAAM,MAAM,CAAC,GAAG,oBAAI,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC,EAAE;AAAA,EAC1D;AAEA,SAAO;AACT;AAQA,SAAS,oBACP,QACA,YACe;AACf,QAAM,OAAO,oBAAI,IAAY;AAC7B,MAAI,UAAU;AACd,MAAI,MAAM,QAAQ;AAElB,SAAO,OAAO,QAAQ,UAAU;AAC9B,UAAM,OAAO,oBAAoB,GAAG;AACpC,UAAM,SAAS,SAAS,SAAY,SAAY,aAAa,IAAI;AAEjE,QAAI,SAAS,UAAa,WAAW,UAAa,KAAK,IAAI,IAAI,GAAG;AAChE;AAAA,IACF;AAEA,SAAK,IAAI,IAAI;AACb,cAAU;AACV,UAAM,QAAQ;AAAA,EAChB;AAEA,SAAO;AACT;AAkBA,SAAS,YACP,OACA,MACA,gBAAgB,OACP;AACT,MAAI,SAAS,QAAQ;AACnB,WAAO;AAAA,EACT;AAEA,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO,MAAM,IAAI,CAAC,SAAS,YAAY,MAAM,UAAU,aAAa,CAAC;AAAA,EACvE;AAEA,MAAI,CAAC,SAAS,OAAO,UAAU,UAAU;AACvC,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,OAAO,QAAQ,KAAgC,EAC5D;AAAA,IACC,CAAC,CAAC,GAAG,MAAM,CAAC,iBAAiB,SAAS,YAAY,CAAC,WAAW,IAAI,GAAG;AAAA,EACvE,EACC,IAAI,CAAC,CAAC,KAAK,UAAU,MAAyB;AAC7C,QAAI,SAAS,aAAa;AACxB,aAAO,CAAC,KAAK,YAAY,YAAY,UAAU,aAAa,CAAC;AAAA,IAC/D;AAEA,QACE,QAAQ,UACR,OAAO,eAAe,YACtB,WAAW,WAAW,uBAAuB,GAC7C;AACA,aAAO,CAAC,KAAK,WAAW,QAAQ,yBAAyB,UAAU,CAAC;AAAA,IACtE;AAEA,WAAO,CAAC,KAAK,YAAY,YAAY,UAAU,GAAG,GAAG,aAAa,CAAC;AAAA,EACrE,CAAC;AAEH,QAAM,YAAY,OAAO,YAAY,OAAO;AAE5C,SAAO,SAAS,cAAc,YAAY,kBAAkB,SAAS;AACvE;;;ACrmBO,IAAM,yBAAyB;AAEtC,IAAM,kBAA8C;AAAA,EAClD,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,OAAO;AAAA,EACP,MAAM;AAAA,EACN,KAAK;AACP;AASO,SAAS,aACd,QACA,SACa;AACb,MAAI,WAAW,OAAO,OAAO,CAAC,UAAU,CAAC,MAAM,UAAU;AAEzD,MAAI,QAAQ,SAAS;AACnB,UAAM,UAAU,QAAQ;AACxB,eAAW,SAAS,OAAO,CAAC,UAAU,QAAQ,UAAU,KAAK,CAAC,CAAC;AAAA,EACjE;AAEA,MAAI,QAAQ,SAAS;AACnB,UAAM,UAAU,QAAQ;AACxB,eAAW,SAAS,OAAO,CAAC,UAAU,CAAC,QAAQ,UAAU,KAAK,CAAC,CAAC;AAAA,EAClE;AAEA,aAAW,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,GAAG,MAAM;AACtC,UAAM,WAAW,gBAAgB,EAAE,MAAM,IAAI,gBAAgB,EAAE,MAAM;AACrE,WAAO,aAAa,IAAI,WAAW,EAAE,KAAK,cAAc,EAAE,IAAI;AAAA,EAChE,CAAC;AAED,QAAM,mBAAmB,CAAC,QAAQ,WAAW,CAAC,QAAQ;AAEtD,MACE,oBACA,QAAQ,aAAa,UACrB,SAAS,SAAS,wBAClB;AACA,UAAM,IAAI;AAAA,MACR,qBAAqB,SAAS,MAAM,mDAAmD,sBAAsB;AAAA,IAG/G;AAAA,EACF;AAEA,MAAI,QAAQ,aAAa,UAAa,SAAS,SAAS,QAAQ,UAAU;AACxE,UAAM,IAAI;AAAA,MACR,qBAAqB,SAAS,MAAM,wCAAwC,QAAQ,QAAQ;AAAA,IAE9F;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,UAAU,OAAoC;AACrD,SAAO;AAAA,IACL,YAAY,MAAM;AAAA,IAClB,QAAQ,MAAM;AAAA,IACd,aAAa,MAAM;AAAA,IACnB,MAAM,MAAM;AAAA,IACZ,MAAM,MAAM;AAAA,EACd;AACF;;;ACrDA,eAAsB,YACpB,SACkB;AAClB,QAAM,EAAE,UAAU,OAAO,IAAI,MAAM,SAAS,QAAQ,IAAI;AACxD,QAAM,SAAS,cAAc,QAAQ;AACrC,QAAM,WAAW,aAAa,QAAQ,OAAO;AAC7C,QAAM,QAAQ,cAAc,UAAU,QAAQ,QAAQ;AACtD,QAAM,aAAa,gBAAgB,QAAQ;AAE3C,QAAM,SACJ,QAAQ,UACR,IAAI,QAAQ;AAAA,IACV,MAAM,QAAQ,QAAQ,SAAS,MAAM,SAAS;AAAA,IAC9C,SAAS,QAAQ,WAAW;AAAA,EAC9B,CAAC;AAEH,QAAM,oBAIA,CAAC;AAEP,aAAW,SAAS,UAAU;AAC5B,UAAM,OAAO,MAAM,IAAI,KAAK;AAE5B,QAAI,CAAC,MAAM;AACT;AAAA,IACF;AAEA,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,IAAI,gBAAgB,OAAO,UAAU;AAErC,UAAM,cAAc;AAAA,MAClB,iBAAiB,QAAQ;AAAA,MACzB,WAAW,QAAQ,SAAS;AAAA,MAC5B,SAAS,QAAQ;AAAA,MACjB;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS,MAAM;AAAA,IACjB;AAEA,QACE,QAAQ,aACR,MAAM,WAAW,SACjB,sBAAsB,KAAK,GAC3B;AACA;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,WAAW;AAAA,QACX;AAAA,MACF;AACA;AAAA,IACF;AAEA,QAAI,4BAA4B;AAC9B,wBAAkB,KAAK;AAAA,QACrB,aAAa;AAAA,QACb,QAAQ,MAAM;AAAA,QACd,MAAM,MAAM;AAAA,MACd,CAAC;AACD;AAAA,IACF;AAEA,UAAM,mBAAmB,kBAAkB,OAAO,UAAU;AAC5D,UAAM,eAAe,mBACjB,kBAAkB,gBAAgB,IAClC;AAEJ,WAAO,QAAQ;AAAA,MACb,aACE,MAAM,WAAW,GAAG,MAAM,OAAO,YAAY,CAAC,IAAI,MAAM,IAAI;AAAA,MAC9D,SAAS,OAAO,SAAS;AACvB,cAAM,SAAS,MAAM,eAAe;AAAA,UAClC,GAAG;AAAA,UACH;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAED,eAAO,kBAAkB,QAAQ,YAAY;AAAA,MAC/C;AAAA,MACA;AAAA,MACA,YAAY,kBAAkB,UAAU;AAAA,MACxC,GAAI,eAAe,EAAE,aAAa,IAAI,CAAC;AAAA,IACzC,CAAC;AAAA,EACH;AAEA,MAAI,kBAAkB,SAAS,GAAG;AAChC,YAAQ;AAAA,MACN,wBACK,kBAAkB,MAAM,0KAE3B,kBACG;AAAA,QACC,CAAC,OAAO,GAAG,GAAG,OAAO,YAAY,CAAC,IAAI,GAAG,IAAI,KAAK,GAAG,WAAW;AAAA,MAClE,EACC,KAAK,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,iBACP,QACA,OACA,MACA,cACA,cACA,aAIM;AACN,QAAM,UAAU,qBAAqB,OAAO,MAAM,cAAc,YAAY;AAC5E,QAAM,cAAc,MAAM,WAAW,OAAO,MAAM,IAAI;AAEtD,MAAI,QAAQ,SAAS,YAAY;AAC/B,WAAO,YAAY;AAAA,MACjB;AAAA,MACA,MAAM,YACJ;AAAA,QACE,MAAM,eAAe,EAAE,GAAG,aAAa,MAAM,CAAC,EAAE,CAAC;AAAA,MACnD;AAAA,MACF;AAAA,MACA,KAAK,QAAQ;AAAA,IACf,CAAC;AAED;AAAA,EACF;AAEA,SAAO,oBAAoB;AAAA,IACzB,WAAW,QAAQ;AAAA,IACnB;AAAA,IACA,MAAM,OAAO,SACX;AAAA,MACE,MAAM,eAAe;AAAA,QACnB,GAAG;AAAA,QACH;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IACF;AAAA,IACA,aAAa,QAAQ;AAAA,EACvB,CAAC;AACH;AAwBA,eAAe,kBACb,QACA,cACkB;AAClB,QAAM,EAAE,MAAM,KAAK,IAAI;AAEvB,MACE,CAAC,gBACD,SAAS,QACT,OAAO,SAAS,YAChB,MAAM,QAAQ,IAAI,GAClB;AACA,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,MAAM,aAAa,WAAW,EAAE,SAAS,IAAI;AAEhE,SAAO,WAAW,SAAS,OAAO;AACpC;AAEA,SAAS,qBAAqB,EAAE,KAAK,GAAyC;AAK5E,MAAI;AACF,SAAK,MAAM,IAAI;AACf,WAAO,EAAE,UAAU,oBAAoB,KAAK;AAAA,EAC9C,QAAQ;AACN,WAAO,EAAE,UAAU,cAAc,KAAK;AAAA,EACxC;AACF;","names":[]}
|