@prefecthq/fastmcp-ts 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -8,6 +8,9 @@ import { ProtocolError as ProtocolError3, ProtocolErrorCode as ProtocolErrorCode
8
8
  import { randomUUID as randomUUID2 } from "crypto";
9
9
 
10
10
  // src/server/auth/types.ts
11
+ function isRequestVerifier(v) {
12
+ return "verifyRequest" in v;
13
+ }
11
14
  var AuthorizationError = class extends Error {
12
15
  constructor(message) {
13
16
  super(message);
@@ -57,12 +60,94 @@ var BoundedEventStore = class {
57
60
 
58
61
  // src/server/context.ts
59
62
  import { AsyncLocalStorage } from "async_hooks";
63
+
64
+ // src/server/httpContext.ts
65
+ var DEFAULT_SENSITIVE_HEADERS = [
66
+ "authorization",
67
+ "cookie",
68
+ "proxy-authorization",
69
+ "mcp-session-id"
70
+ ];
71
+ function resolveSensitiveHeaders(options) {
72
+ const set = new Set(DEFAULT_SENSITIVE_HEADERS);
73
+ for (const h of options?.redactHeaders ?? []) set.add(h.toLowerCase());
74
+ for (const h of options?.exposeHeaders ?? []) set.delete(h.toLowerCase());
75
+ return set;
76
+ }
77
+ function buildHttpRequestContext(req, sensitiveHeaders) {
78
+ const headers = new Headers();
79
+ const redacted = [];
80
+ req.headers.forEach((value, name) => {
81
+ if (sensitiveHeaders.has(name)) {
82
+ redacted.push(name);
83
+ return;
84
+ }
85
+ headers.append(name, value);
86
+ });
87
+ redacted.sort();
88
+ let url;
89
+ try {
90
+ const u = new URL(req.url);
91
+ url = u.pathname + u.search;
92
+ } catch {
93
+ url = req.url;
94
+ }
95
+ return { headers, redactedHeaderNames: redacted, method: req.method, url };
96
+ }
97
+ function nodeRequestToHttpContext(req) {
98
+ const headers = new Headers();
99
+ for (const [name, value] of Object.entries(req.headers)) {
100
+ if (value === void 0) continue;
101
+ if (Array.isArray(value)) {
102
+ for (const v of value) headers.append(name, v);
103
+ } else {
104
+ headers.append(name, value);
105
+ }
106
+ }
107
+ return { headers, redactedHeaderNames: [], method: req.method ?? "GET", url: req.url ?? "/" };
108
+ }
109
+ var UNFORWARDABLE = /* @__PURE__ */ new Set([
110
+ // credentials (see also DEFAULT_SENSITIVE_HEADERS; kept here too so the
111
+ // helper is safe on Headers that did not come from a redacted ctx.http)
112
+ "authorization",
113
+ "cookie",
114
+ "proxy-authorization",
115
+ "proxy-authenticate",
116
+ // hop-by-hop (RFC 9110 §7.6.1) and connection management
117
+ "connection",
118
+ "keep-alive",
119
+ "te",
120
+ "trailer",
121
+ "transfer-encoding",
122
+ "upgrade",
123
+ "proxy-connection",
124
+ // message framing / target-specific
125
+ "host",
126
+ "content-length",
127
+ "content-type",
128
+ "expect"
129
+ ]);
130
+ function forwardableHeaders(headers, options) {
131
+ const include = new Set((options?.include ?? []).map((h) => h.toLowerCase()));
132
+ const out = new Headers();
133
+ headers.forEach((value, name) => {
134
+ if (include.has(name)) {
135
+ out.append(name, value);
136
+ return;
137
+ }
138
+ if (UNFORWARDABLE.has(name) || name.startsWith("mcp-")) return;
139
+ out.append(name, value);
140
+ });
141
+ return out;
142
+ }
143
+
144
+ // src/server/context.ts
60
145
  var contextStore = new AsyncLocalStorage();
61
146
  var SESSION_CLOSE_CALLBACKS_KEY = "__fastmcp_session_close_callbacks";
62
147
  var SESSION_STATE_MODERN_HTTP_ERROR = "[fastmcp] Session state is not available on modern HTTP requests (protocol revision 2026-07-28). Each request runs statelessly with no shared session store. Use ctx.requestState() to read per-request state. Use ctx.mintRequestState() to carry state across a multi-round-trip flow.";
63
148
  var SESSION_STATE_STATELESS_HTTP_ERROR = "[fastmcp] Session state is not available on a stateless HTTP server (RunOptions.stateless or FASTMCP_STATELESS_HTTP). Each request runs against a fresh store that is discarded when the request ends. Use ctx.requestState() to read per-request state. Use ctx.mintRequestState() to carry state across a multi-round-trip flow. Turn stateless off to use session state, or keep state in your own external store.";
64
149
  var SERVER_INITIATED_STATELESS_HTTP_ERROR = "[fastmcp] Server-initiated requests (ctx.elicit, ctx.sample, ctx.listRoots) are not available on a stateless HTTP server (RunOptions.stateless or FASTMCP_STATELESS_HTTP). They need a session: client capabilities are negotiated once at initialize, and the client replies on a separate request. Return inputRequired({ requestState }) to carry a multi-round-trip flow without a session; the embedded-request form of inputRequired needs a session too. Turn stateless off to use server-initiated requests.";
65
- function createContext(server, sdkCtx, auth, sessionState, requestStateCodec, stateless) {
150
+ function createContext(server, sdkCtx, auth, sessionState, requestStateCodec, stateless, sensitiveHeaders) {
66
151
  const requestId = String(sdkCtx.mcpReq.id);
67
152
  const progressToken = sdkCtx.mcpReq._meta?.progressToken;
68
153
  const isModernEra = sdkCtx.mcpReq.envelope !== void 0;
@@ -75,9 +160,12 @@ function createContext(server, sdkCtx, auth, sessionState, requestStateCodec, st
75
160
  async function log(level, message, loggerName) {
76
161
  await sdkCtx.mcpReq.log(level, message, loggerName);
77
162
  }
163
+ const httpReq = sdkCtx.http?.req;
164
+ const sensitive = sensitiveHeaders ?? resolveSensitiveHeaders();
78
165
  return {
79
166
  auth,
80
167
  requestId,
168
+ http: httpReq ? buildHttpRequestContext(httpReq, sensitive) : void 0,
81
169
  log,
82
170
  debug: (msg, logger) => log("debug", msg, logger),
83
171
  info: (msg, logger) => log("info", msg, logger),
@@ -683,6 +771,12 @@ function convertResourceResult(value, uri, mimeType, stateless) {
683
771
  }
684
772
  return { contents: [{ uri, mimeType: mimeType ?? "text/plain", text: String(value) }] };
685
773
  }
774
+ function attachResourceUiMeta(result, ui, uiCapable) {
775
+ if (!ui || !uiCapable || isInputRequiredResult(result)) return result;
776
+ return {
777
+ contents: result.contents.map((c) => ({ ...c, _meta: { ui, ...c._meta } }))
778
+ };
779
+ }
686
780
  function isUriTemplate(uri) {
687
781
  return uri.includes("{");
688
782
  }
@@ -820,7 +914,7 @@ function toAccessToken(authInfo) {
820
914
  }
821
915
  var _cliEnvToken;
822
916
  async function resolveCliEnvToken(verifier) {
823
- if (!verifier) return void 0;
917
+ if (!verifier || !("verify" in verifier)) return void 0;
824
918
  if (_cliEnvToken !== void 0) return _cliEnvToken ?? void 0;
825
919
  const raw = process.env["FASTMCP_CLI_AUTH_TOKEN"];
826
920
  if (!raw) {
@@ -851,6 +945,7 @@ var FastMCP = class {
851
945
  version;
852
946
  _auth;
853
947
  _oauth;
948
+ _sensitiveHeaders;
854
949
  _toolsPageSize;
855
950
  _resourcesPageSize;
856
951
  _tools = /* @__PURE__ */ new Map();
@@ -900,6 +995,7 @@ var FastMCP = class {
900
995
  this.version = options.version ?? "0.0.1";
901
996
  this._auth = options.auth;
902
997
  this._oauth = options.oauth;
998
+ this._sensitiveHeaders = resolveSensitiveHeaders(options.http);
903
999
  this._toolsPageSize = options.toolsPageSize ?? 50;
904
1000
  this._resourcesPageSize = options.resourcesPageSize ?? 50;
905
1001
  this._promptsPageSize = options.promptsPageSize ?? 50;
@@ -976,7 +1072,7 @@ var FastMCP = class {
976
1072
  _setupHandlers(server, sessionState, opts) {
977
1073
  server.setRequestHandler("tools/list", async (req, sdkCtx) => {
978
1074
  const token = await this._resolveToken(sdkCtx.http?.authInfo);
979
- const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless);
1075
+ const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders);
980
1076
  return contextStore.run(
981
1077
  ctx,
982
1078
  () => runMiddlewareChain(this._middleware, "tools/list", req.params, ctx, async () => {
@@ -1055,7 +1151,7 @@ var FastMCP = class {
1055
1151
  const synthTool = synthesizedList.find((s) => s.name === requestedName);
1056
1152
  if (synthTool) {
1057
1153
  if (synthTool.auth) await runAuthCheck(synthTool.auth, token);
1058
- const ctx2 = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless);
1154
+ const ctx2 = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders);
1059
1155
  try {
1060
1156
  return await contextStore.run(
1061
1157
  ctx2,
@@ -1108,7 +1204,7 @@ var FastMCP = class {
1108
1204
  if (tool.config.auth) await runAuthCheck(tool.config.auth, token);
1109
1205
  const resolvedTool = tool;
1110
1206
  const rawArgs = req.params.arguments ?? {};
1111
- const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless);
1207
+ const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders);
1112
1208
  try {
1113
1209
  return await contextStore.run(
1114
1210
  ctx,
@@ -1150,7 +1246,7 @@ var FastMCP = class {
1150
1246
  });
1151
1247
  server.setRequestHandler("resources/list", async (req, sdkCtx) => {
1152
1248
  const token = await this._resolveToken(sdkCtx.http?.authInfo);
1153
- const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless);
1249
+ const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders);
1154
1250
  return contextStore.run(
1155
1251
  ctx,
1156
1252
  () => runMiddlewareChain(this._middleware, "resources/list", req.params, ctx, async () => {
@@ -1197,7 +1293,7 @@ var FastMCP = class {
1197
1293
  });
1198
1294
  server.setRequestHandler("resources/templates/list", async (req, sdkCtx) => {
1199
1295
  const token = await this._resolveToken(sdkCtx.http?.authInfo);
1200
- const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless);
1296
+ const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders);
1201
1297
  return contextStore.run(
1202
1298
  ctx,
1203
1299
  () => runMiddlewareChain(this._middleware, "resources/templates/list", req.params, ctx, async () => {
@@ -1249,7 +1345,7 @@ var FastMCP = class {
1249
1345
  const resource = resolved.resource;
1250
1346
  const templateParams = resolved.templateParams;
1251
1347
  if (resource.config.auth) await runAuthCheck(resource.config.auth, token);
1252
- const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless);
1348
+ const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders);
1253
1349
  return contextStore.run(
1254
1350
  ctx,
1255
1351
  () => runMiddlewareChain(this._middleware, "resources/read", req.params, ctx, async () => {
@@ -1268,7 +1364,8 @@ var FastMCP = class {
1268
1364
  );
1269
1365
  }
1270
1366
  const result = await executePromise;
1271
- return convertResourceResult(result, requestedUri, resource.config.mimeType, opts?.stateless);
1367
+ const converted = convertResourceResult(result, requestedUri, resource.config.mimeType, opts?.stateless);
1368
+ return attachResourceUiMeta(converted, resource.config.ui, isUiCapable(server.getClientCapabilities()));
1272
1369
  })
1273
1370
  );
1274
1371
  });
@@ -1282,7 +1379,7 @@ var FastMCP = class {
1282
1379
  }
1283
1380
  const resource = resolved.resource;
1284
1381
  if (resource.config.auth) await runAuthCheck(resource.config.auth, token);
1285
- const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless);
1382
+ const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders);
1286
1383
  return contextStore.run(
1287
1384
  ctx,
1288
1385
  () => runMiddlewareChain(this._middleware, "resources/subscribe", req.params, ctx, async () => {
@@ -1300,7 +1397,7 @@ var FastMCP = class {
1300
1397
  if (opts?.stateless) throw new ProtocolError3(ProtocolErrorCode3.MethodNotFound, STATELESS_SUBSCRIBE_ERROR);
1301
1398
  const uri = req.params.uri;
1302
1399
  const token = await this._resolveToken(sdkCtx.http?.authInfo);
1303
- const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless);
1400
+ const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders);
1304
1401
  return contextStore.run(
1305
1402
  ctx,
1306
1403
  () => runMiddlewareChain(this._middleware, "resources/unsubscribe", req.params, ctx, async () => {
@@ -1312,7 +1409,7 @@ var FastMCP = class {
1312
1409
  });
1313
1410
  server.setRequestHandler("prompts/list", async (req, sdkCtx) => {
1314
1411
  const token = await this._resolveToken(sdkCtx.http?.authInfo);
1315
- const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless);
1412
+ const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders);
1316
1413
  return contextStore.run(
1317
1414
  ctx,
1318
1415
  () => runMiddlewareChain(this._middleware, "prompts/list", req.params, ctx, async () => {
@@ -1392,7 +1489,7 @@ var FastMCP = class {
1392
1489
  );
1393
1490
  }
1394
1491
  }
1395
- const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless);
1492
+ const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders);
1396
1493
  return contextStore.run(
1397
1494
  ctx,
1398
1495
  () => runMiddlewareChain(this._middleware, "prompts/get", req.params, ctx, async () => {
@@ -1416,7 +1513,7 @@ var FastMCP = class {
1416
1513
  });
1417
1514
  server.setRequestHandler("completion/complete", async (req, sdkCtx) => {
1418
1515
  const token = await this._resolveToken(sdkCtx.http?.authInfo);
1419
- const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless);
1516
+ const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless, this._sensitiveHeaders);
1420
1517
  return contextStore.run(
1421
1518
  ctx,
1422
1519
  () => runMiddlewareChain(this._middleware, "completion/complete", req.params, ctx, async () => {
@@ -2237,14 +2334,26 @@ var FastMCP = class {
2237
2334
  }
2238
2335
  for (const [k, v] of Object.entries(corsHeaders)) res.setHeader(k, v);
2239
2336
  if (auth) {
2240
- const authHeader = req.headers.authorization;
2241
- const bearer = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
2242
- if (!bearer) {
2243
- res.writeHead(401, { "Content-Type": "application/json", "WWW-Authenticate": 'Bearer realm="mcp"' }).end(JSON.stringify({ error: "Missing bearer token" }));
2244
- return;
2245
- }
2246
- try {
2247
- const accessToken = await auth.verify(bearer);
2337
+ if (isRequestVerifier(auth)) {
2338
+ let accessToken;
2339
+ try {
2340
+ accessToken = await auth.verifyRequest(nodeRequestToHttpContext(req));
2341
+ } catch (err) {
2342
+ if (err instanceof AuthorizationError) {
2343
+ res.writeHead(403, { "Content-Type": "application/json" }).end(JSON.stringify({ error: err.message }));
2344
+ } else {
2345
+ res.writeHead(401, { "Content-Type": "application/json" }).end(JSON.stringify({ error: err instanceof Error ? err.message : "Authentication failed" }));
2346
+ }
2347
+ return;
2348
+ }
2349
+ if (!accessToken.token) {
2350
+ console.error(
2351
+ "[fastmcp] RequestVerifier.verifyRequest must set AccessToken.token to a stable, non-empty per-identity value. It keys response-cache partitioning and downstream identity."
2352
+ );
2353
+ res.writeHead(500, { "Content-Type": "application/json" }).end(JSON.stringify({ error: "verifyRequest returned an AccessToken with an empty token" }));
2354
+ return;
2355
+ }
2356
+ ;
2248
2357
  req.auth = {
2249
2358
  token: accessToken.token,
2250
2359
  clientId: accessToken.clientId ?? "",
@@ -2252,13 +2361,30 @@ var FastMCP = class {
2252
2361
  expiresAt: accessToken.expiresAt,
2253
2362
  extra: accessToken.claims
2254
2363
  };
2255
- } catch (err) {
2256
- if (err instanceof AuthorizationError) {
2257
- res.writeHead(403, { "Content-Type": "application/json" }).end(JSON.stringify({ error: err.message }));
2258
- } else {
2259
- res.writeHead(401, { "Content-Type": "application/json", "WWW-Authenticate": 'Bearer realm="mcp"' }).end(JSON.stringify({ error: err instanceof Error ? err.message : "Authentication failed" }));
2364
+ } else {
2365
+ const authHeader = req.headers.authorization;
2366
+ const bearer = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
2367
+ if (!bearer) {
2368
+ res.writeHead(401, { "Content-Type": "application/json", "WWW-Authenticate": 'Bearer realm="mcp"' }).end(JSON.stringify({ error: "Missing bearer token" }));
2369
+ return;
2370
+ }
2371
+ try {
2372
+ const accessToken = await auth.verify(bearer);
2373
+ req.auth = {
2374
+ token: accessToken.token,
2375
+ clientId: accessToken.clientId ?? "",
2376
+ scopes: accessToken.scopes,
2377
+ expiresAt: accessToken.expiresAt,
2378
+ extra: accessToken.claims
2379
+ };
2380
+ } catch (err) {
2381
+ if (err instanceof AuthorizationError) {
2382
+ res.writeHead(403, { "Content-Type": "application/json" }).end(JSON.stringify({ error: err.message }));
2383
+ } else {
2384
+ res.writeHead(401, { "Content-Type": "application/json", "WWW-Authenticate": 'Bearer realm="mcp"' }).end(JSON.stringify({ error: err instanceof Error ? err.message : "Authentication failed" }));
2385
+ }
2386
+ return;
2260
2387
  }
2261
- return;
2262
2388
  }
2263
2389
  }
2264
2390
  await this._dispatchHttp(req, res);
@@ -2816,6 +2942,1472 @@ async function createProxy(config, name) {
2816
2942
  return buildProxyFromClient(client, { cacheTtl: config.cacheTtl, name });
2817
2943
  }
2818
2944
 
2945
+ // src/server/openapi/internal.ts
2946
+ function truthy(value) {
2947
+ if (value === null || value === void 0 || value === false) return false;
2948
+ if (typeof value === "number") return value !== 0;
2949
+ if (typeof value === "string") return value.length > 0;
2950
+ if (Array.isArray(value)) return value.length > 0;
2951
+ if (typeof value === "object") return Object.keys(value).length > 0;
2952
+ return true;
2953
+ }
2954
+ function isPlainObject(value) {
2955
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2956
+ }
2957
+ function deepEqual(a, b) {
2958
+ if (a === b) return true;
2959
+ if (Array.isArray(a) && Array.isArray(b)) {
2960
+ return a.length === b.length && a.every((item, i) => deepEqual(item, b[i]));
2961
+ }
2962
+ if (isPlainObject(a) && isPlainObject(b)) {
2963
+ const aKeys = Object.keys(a);
2964
+ const bKeys = Object.keys(b);
2965
+ return aKeys.length === bKeys.length && aKeys.every((k) => Object.prototype.hasOwnProperty.call(b, k) && deepEqual(a[k], b[k]));
2966
+ }
2967
+ return false;
2968
+ }
2969
+ function warn(message) {
2970
+ console.warn(`[fastmcp] ${message}`);
2971
+ }
2972
+
2973
+ // src/server/openapi/parser.ts
2974
+ import { parse as parseYaml } from "yaml";
2975
+
2976
+ // src/server/openapi/jsonSchema.ts
2977
+ var OPENAPI_SPECIFIC_FIELDS = [
2978
+ "nullable",
2979
+ "discriminator",
2980
+ "readOnly",
2981
+ "writeOnly",
2982
+ "xml",
2983
+ "externalDocs",
2984
+ "deprecated"
2985
+ ];
2986
+ var RECURSIVE_MAP_FIELDS = ["properties", "$defs", "$definitions"];
2987
+ var RECURSIVE_SINGLE_FIELDS = ["items", "additionalProperties", "not"];
2988
+ var RECURSIVE_LIST_FIELDS = ["allOf", "anyOf", "oneOf"];
2989
+ function convertOpenAPISchemaToJsonSchema(schema, openapiVersion) {
2990
+ if (!isPlainObject(schema)) return schema;
2991
+ let result = { ...schema };
2992
+ if (openapiVersion?.startsWith("3.0")) {
2993
+ result = convertNullableField(result);
2994
+ }
2995
+ if ("oneOf" in result) {
2996
+ result.anyOf = result.oneOf;
2997
+ delete result.oneOf;
2998
+ }
2999
+ result = requireDiscriminatorProperty(result);
3000
+ for (const field of OPENAPI_SPECIFIC_FIELDS) {
3001
+ delete result[field];
3002
+ }
3003
+ for (const field of RECURSIVE_MAP_FIELDS) {
3004
+ const map = result[field];
3005
+ if (isPlainObject(map)) {
3006
+ result[field] = Object.fromEntries(
3007
+ Object.entries(map).map(([name, sub]) => [
3008
+ name,
3009
+ isPlainObject(sub) ? convertOpenAPISchemaToJsonSchema(sub, openapiVersion) : sub
3010
+ ])
3011
+ );
3012
+ }
3013
+ }
3014
+ for (const field of RECURSIVE_SINGLE_FIELDS) {
3015
+ const sub = result[field];
3016
+ if (isPlainObject(sub)) {
3017
+ result[field] = convertOpenAPISchemaToJsonSchema(sub, openapiVersion);
3018
+ }
3019
+ }
3020
+ for (const field of RECURSIVE_LIST_FIELDS) {
3021
+ const list = result[field];
3022
+ if (Array.isArray(list)) {
3023
+ result[field] = list.map(
3024
+ (item) => isPlainObject(item) ? convertOpenAPISchemaToJsonSchema(item, openapiVersion) : item
3025
+ );
3026
+ }
3027
+ }
3028
+ return result;
3029
+ }
3030
+ function convertNullableField(schema) {
3031
+ if (!("nullable" in schema)) return schema;
3032
+ const result = { ...schema };
3033
+ const nullableValue = result.nullable;
3034
+ delete result.nullable;
3035
+ if (!nullableValue) return result;
3036
+ const currentType = result.type;
3037
+ if ("type" in result) {
3038
+ if (typeof currentType === "string") {
3039
+ result.type = [currentType, "null"];
3040
+ } else if (Array.isArray(currentType) && !currentType.includes("null")) {
3041
+ result.type = [...currentType, "null"];
3042
+ }
3043
+ } else if ("oneOf" in result) {
3044
+ result.anyOf = [...result.oneOf, { type: "null" }];
3045
+ delete result.oneOf;
3046
+ } else if ("anyOf" in result) {
3047
+ const anyOf = result.anyOf;
3048
+ if (!anyOf.some((item) => isPlainObject(item) && item.type === "null")) {
3049
+ result.anyOf = [...anyOf, { type: "null" }];
3050
+ }
3051
+ } else if ("allOf" in result) {
3052
+ result.anyOf = [{ allOf: result.allOf }, { type: "null" }];
3053
+ delete result.allOf;
3054
+ }
3055
+ if (Array.isArray(result.enum) && !result.enum.includes(null)) {
3056
+ result.enum = [...result.enum, null];
3057
+ }
3058
+ return result;
3059
+ }
3060
+ function requireProperty(schema, propertyName) {
3061
+ const required = schema.required;
3062
+ if (required === void 0 || required === null) {
3063
+ return { ...schema, required: [propertyName] };
3064
+ }
3065
+ if (Array.isArray(required) && !required.includes(propertyName)) {
3066
+ return { ...schema, required: [...required, propertyName] };
3067
+ }
3068
+ return schema;
3069
+ }
3070
+ function requireDiscriminatorProperty(schema) {
3071
+ const discriminator = schema.discriminator;
3072
+ if (!isPlainObject(discriminator)) return schema;
3073
+ const propertyName = discriminator.propertyName;
3074
+ if (typeof propertyName !== "string") return schema;
3075
+ const result = { ...schema };
3076
+ for (const key of ["anyOf", "oneOf"]) {
3077
+ const variants = result[key];
3078
+ if (!Array.isArray(variants)) continue;
3079
+ result[key] = variants.map(
3080
+ (variant) => isPlainObject(variant) ? requireProperty(variant, propertyName) : variant
3081
+ );
3082
+ }
3083
+ return result;
3084
+ }
3085
+
3086
+ // src/server/openapi/schemas.ts
3087
+ var EXTERNAL_REF_MESSAGE = "External or non-local reference not supported";
3088
+ function isExternalRefError(error) {
3089
+ return error instanceof Error && error.message.includes(EXTERNAL_REF_MESSAGE);
3090
+ }
3091
+ function replaceRefWithDefs(info, description) {
3092
+ const schema = { ...info };
3093
+ const refPath = schema.$ref;
3094
+ if (truthy(refPath)) {
3095
+ if (typeof refPath === "string") {
3096
+ if (refPath.startsWith("#/components/schemas/")) {
3097
+ schema.$ref = `#/$defs/${refPath.split("/").pop()}`;
3098
+ } else if (!refPath.startsWith("#/")) {
3099
+ throw new Error(
3100
+ `${EXTERNAL_REF_MESSAGE}: ${refPath}. FastMCP only supports local schema references starting with '#/'. Please include all schema definitions within the OpenAPI document.`
3101
+ );
3102
+ }
3103
+ }
3104
+ } else if (truthy(schema.properties)) {
3105
+ const properties = schema.properties;
3106
+ if ("$ref" in properties) {
3107
+ schema.properties = replaceRefWithDefs(properties);
3108
+ } else {
3109
+ schema.properties = Object.fromEntries(
3110
+ Object.entries(properties).map(([name, prop]) => [
3111
+ name,
3112
+ isPlainObject(prop) ? replaceRefWithDefs(prop) : prop
3113
+ ])
3114
+ );
3115
+ }
3116
+ } else if (truthy(schema.items)) {
3117
+ if (isPlainObject(schema.items)) {
3118
+ schema.items = replaceRefWithDefs(schema.items);
3119
+ }
3120
+ }
3121
+ for (const section of ["anyOf", "allOf", "oneOf"]) {
3122
+ const list = schema[section];
3123
+ if (Array.isArray(list)) {
3124
+ schema[section] = list.map((item) => isPlainObject(item) ? replaceRefWithDefs(item) : item);
3125
+ }
3126
+ }
3127
+ const additionalProperties = schema.additionalProperties;
3128
+ if (truthy(additionalProperties) && isPlainObject(additionalProperties)) {
3129
+ schema.additionalProperties = replaceRefWithDefs(additionalProperties);
3130
+ }
3131
+ const propertyNames = schema.propertyNames;
3132
+ if (truthy(propertyNames) && isPlainObject(propertyNames)) {
3133
+ schema.propertyNames = replaceRefWithDefs(propertyNames);
3134
+ }
3135
+ const patternProperties = schema.patternProperties;
3136
+ if (truthy(patternProperties) && isPlainObject(patternProperties)) {
3137
+ schema.patternProperties = Object.fromEntries(
3138
+ Object.entries(patternProperties).map(([pattern, sub]) => [
3139
+ pattern,
3140
+ isPlainObject(sub) ? replaceRefWithDefs(sub) : sub
3141
+ ])
3142
+ );
3143
+ }
3144
+ if (info.description === void 0 && truthy(description) && !truthy(schema.description)) {
3145
+ schema.description = description;
3146
+ }
3147
+ return schema;
3148
+ }
3149
+ function allofMembers(schema, schemaDefs, resolving = /* @__PURE__ */ new Set()) {
3150
+ const ref = schema.$ref;
3151
+ if (typeof ref === "string") {
3152
+ for (const prefix of ["#/$defs/", "#/components/schemas/"]) {
3153
+ if (ref.startsWith(prefix)) {
3154
+ const name = ref.slice(prefix.length);
3155
+ const referenced = schemaDefs[name];
3156
+ if (isPlainObject(referenced) && !resolving.has(name)) {
3157
+ const siblings = Object.fromEntries(
3158
+ Object.entries(schema).filter(([key]) => key !== "$ref")
3159
+ );
3160
+ const members = allofMembers(referenced, schemaDefs, /* @__PURE__ */ new Set([...resolving, name]));
3161
+ return truthy(siblings) ? [...members, siblings] : members;
3162
+ }
3163
+ break;
3164
+ }
3165
+ }
3166
+ }
3167
+ const allOf = schema.allOf;
3168
+ if (Array.isArray(allOf)) {
3169
+ const members = [];
3170
+ for (const member of allOf) {
3171
+ if (isPlainObject(member)) {
3172
+ members.push(...allofMembers(member, schemaDefs, resolving));
3173
+ }
3174
+ }
3175
+ const siblings = Object.fromEntries(Object.entries(schema).filter(([key]) => key !== "allOf"));
3176
+ return truthy(siblings) ? [...members, siblings] : members;
3177
+ }
3178
+ return [schema];
3179
+ }
3180
+ function discriminatorTargetName(target) {
3181
+ for (const prefix of ["#/$defs/", "#/components/schemas/"]) {
3182
+ if (target.startsWith(prefix)) {
3183
+ return target.slice(prefix.length) || null;
3184
+ }
3185
+ }
3186
+ if (target.startsWith("#") || target.includes("/")) return null;
3187
+ return target || null;
3188
+ }
3189
+ function flattenDiscriminatorSubtypes(schema, schemaDefs) {
3190
+ const discriminator = schema.discriminator;
3191
+ if (!isPlainObject(discriminator)) return null;
3192
+ const propertyName = discriminator.propertyName;
3193
+ const mapping = discriminator.mapping;
3194
+ if (typeof propertyName !== "string" || !isPlainObject(mapping)) return null;
3195
+ const ownProps = isPlainObject(schema.properties) ? schema.properties : {};
3196
+ const alternatives = /* @__PURE__ */ new Map();
3197
+ const values = [];
3198
+ const variants = [];
3199
+ for (const [value, target] of Object.entries(mapping)) {
3200
+ if (typeof target !== "string") continue;
3201
+ const name = discriminatorTargetName(target);
3202
+ const subtype = name !== null ? schemaDefs[name] : void 0;
3203
+ if (!isPlainObject(subtype)) continue;
3204
+ const variantFields = [];
3205
+ for (const member of allofMembers(subtype, schemaDefs)) {
3206
+ const memberProps = isPlainObject(member.properties) ? member.properties : {};
3207
+ for (const [propName, propSchema] of Object.entries(memberProps)) {
3208
+ if (propName in ownProps) continue;
3209
+ if (!variantFields.includes(propName)) variantFields.push(propName);
3210
+ const seen = alternatives.get(propName) ?? [];
3211
+ if (!seen.some((existing2) => deepEqual(existing2, propSchema))) seen.push(propSchema);
3212
+ alternatives.set(propName, seen);
3213
+ }
3214
+ }
3215
+ values.push(`'${value}'`);
3216
+ if (variantFields.length > 0) {
3217
+ variants.push(`'${value}' uses ${variantFields.join(", ")}`);
3218
+ }
3219
+ }
3220
+ if (values.length === 0) return null;
3221
+ const subtypeProps = Object.fromEntries(
3222
+ [...alternatives.entries()].map(([propName, schemas]) => [
3223
+ propName,
3224
+ schemas.length === 1 ? schemas[0] : { anyOf: schemas }
3225
+ ])
3226
+ );
3227
+ const properties = { ...ownProps, ...subtypeProps };
3228
+ let note = `Selects the variant. Accepted values: ${values.join(", ")}.`;
3229
+ if (variants.length > 0) {
3230
+ note += ` ${variants.join("; ")}. Send only the fields belonging to the selected variant.`;
3231
+ }
3232
+ const existing = properties[propertyName];
3233
+ const tagSchema = isPlainObject(existing) ? existing : { type: "string" };
3234
+ const existingDesc = tagSchema.description;
3235
+ properties[propertyName] = {
3236
+ ...tagSchema,
3237
+ description: truthy(existingDesc) ? `${existingDesc} ${note}` : note
3238
+ };
3239
+ return properties;
3240
+ }
3241
+ function capitalize(text) {
3242
+ return text.charAt(0).toUpperCase() + text.slice(1);
3243
+ }
3244
+ function combineSchemasAndMapParams(route) {
3245
+ const properties = {};
3246
+ const required = [];
3247
+ const parameterMap = {};
3248
+ const paramNamesByLocation = {
3249
+ path: /* @__PURE__ */ new Set(),
3250
+ query: /* @__PURE__ */ new Set(),
3251
+ header: /* @__PURE__ */ new Set(),
3252
+ cookie: /* @__PURE__ */ new Set()
3253
+ };
3254
+ let bodySchema = {};
3255
+ let bodyProps = {};
3256
+ for (const param of route.parameters) {
3257
+ paramNamesByLocation[param.location].add(param.name);
3258
+ }
3259
+ const requestBody = route.requestBody;
3260
+ if (requestBody && truthy(requestBody.contentSchema)) {
3261
+ const contentType = Object.keys(requestBody.contentSchema)[0];
3262
+ bodySchema = requestBody.contentSchema[contentType];
3263
+ if (truthy(requestBody.description) && !truthy(bodySchema.description)) {
3264
+ bodySchema.description = requestBody.description;
3265
+ }
3266
+ if ("allOf" in bodySchema && Array.isArray(bodySchema.allOf)) {
3267
+ const mergedProps = {};
3268
+ const mergedRequired = [];
3269
+ for (const subSchema of allofMembers(bodySchema, route.requestSchemas)) {
3270
+ if ("properties" in subSchema && isPlainObject(subSchema.properties)) {
3271
+ Object.assign(mergedProps, subSchema.properties);
3272
+ }
3273
+ if ("required" in subSchema && Array.isArray(subSchema.required)) {
3274
+ mergedRequired.push(...subSchema.required);
3275
+ }
3276
+ }
3277
+ bodySchema.properties = mergedProps;
3278
+ if (mergedRequired.length > 0) {
3279
+ bodySchema.required = [...new Set(mergedRequired)];
3280
+ }
3281
+ delete bodySchema.allOf;
3282
+ }
3283
+ const flattenedProps = flattenDiscriminatorSubtypes(bodySchema, route.requestSchemas);
3284
+ if (flattenedProps !== null) {
3285
+ bodySchema.properties = flattenedProps;
3286
+ delete bodySchema.discriminator;
3287
+ }
3288
+ bodyProps = isPlainObject(bodySchema.properties) ? bodySchema.properties : {};
3289
+ }
3290
+ const allNonBodyParams = /* @__PURE__ */ new Set();
3291
+ for (const names of Object.values(paramNamesByLocation)) {
3292
+ for (const name of names) allNonBodyParams.add(name);
3293
+ }
3294
+ const bodyParamNames = new Set(Object.keys(bodyProps));
3295
+ const paramCounts = /* @__PURE__ */ new Map();
3296
+ for (const param of route.parameters) {
3297
+ paramCounts.set(param.name, (paramCounts.get(param.name) ?? 0) + 1);
3298
+ }
3299
+ const collidingParams = /* @__PURE__ */ new Set();
3300
+ for (const name of allNonBodyParams) {
3301
+ if (bodyParamNames.has(name)) collidingParams.add(name);
3302
+ }
3303
+ for (const [name, count] of paramCounts) {
3304
+ if (count > 1) collidingParams.add(name);
3305
+ }
3306
+ for (const param of route.parameters) {
3307
+ const paramSchema = { ...param.schema };
3308
+ if (truthy(param.description) && !truthy(paramSchema.description)) {
3309
+ paramSchema.description = param.description;
3310
+ }
3311
+ if (collidingParams.has(param.name)) {
3312
+ const suffixedName = `${param.name}__${param.location}`;
3313
+ if (param.required) required.push(suffixedName);
3314
+ parameterMap[suffixedName] = { location: param.location, openapiName: param.name };
3315
+ const originalDesc = truthy(paramSchema.description) ? paramSchema.description : "";
3316
+ const locationDesc = `(${capitalize(param.location)} parameter)`;
3317
+ paramSchema.description = originalDesc ? `${originalDesc} ${locationDesc}` : locationDesc;
3318
+ properties[suffixedName] = paramSchema;
3319
+ } else {
3320
+ if (param.required) required.push(param.name);
3321
+ parameterMap[param.name] = { location: param.location, openapiName: param.name };
3322
+ properties[param.name] = paramSchema;
3323
+ }
3324
+ }
3325
+ if (requestBody && truthy(requestBody.contentSchema)) {
3326
+ if ("$ref" in bodySchema && !truthy(bodyProps)) {
3327
+ properties.body = bodySchema;
3328
+ if (requestBody.required) required.push("body");
3329
+ parameterMap.body = { location: "body", openapiName: "body" };
3330
+ } else if (truthy(bodyProps)) {
3331
+ for (const [propName, propSchema] of Object.entries(bodyProps)) {
3332
+ properties[propName] = propSchema;
3333
+ parameterMap[propName] = { location: "body", openapiName: propName };
3334
+ }
3335
+ if (requestBody.required) {
3336
+ required.push(...bodySchema.required ?? []);
3337
+ }
3338
+ } else {
3339
+ let paramName = String(
3340
+ typeof bodySchema.title === "string" ? bodySchema.title : "body"
3341
+ ).toLowerCase();
3342
+ paramName = paramName.replace(/[^a-zA-Z0-9_]/g, "_");
3343
+ if (!paramName || /^\d/.test(paramName)) paramName = "body_data";
3344
+ properties[paramName] = bodySchema;
3345
+ if (requestBody.required) required.push(paramName);
3346
+ parameterMap[paramName] = { location: "body", openapiName: paramName };
3347
+ }
3348
+ }
3349
+ let result = { type: "object", properties, required };
3350
+ if (truthy(route.requestSchemas)) {
3351
+ result.$defs = route.requestSchemas;
3352
+ }
3353
+ if (route.openapiVersion?.startsWith("3")) {
3354
+ result = convertOpenAPISchemaToJsonSchema(result, route.openapiVersion);
3355
+ }
3356
+ return [result, parameterMap];
3357
+ }
3358
+ var SUCCESS_CODE_PRIORITY = ["200", "201", "202", "204"];
3359
+ var JSON_COMPATIBLE_TYPES = [
3360
+ "application/json",
3361
+ "application/vnd.api+json",
3362
+ "application/hal+json",
3363
+ "application/ld+json",
3364
+ "text/json"
3365
+ ];
3366
+ function extractOutputSchemaFromResponses(responses, schemaDefinitions, openapiVersion) {
3367
+ if (!truthy(responses)) return null;
3368
+ let responseInfo;
3369
+ for (const statusCode of SUCCESS_CODE_PRIORITY) {
3370
+ if (statusCode in responses) {
3371
+ responseInfo = responses[statusCode];
3372
+ break;
3373
+ }
3374
+ }
3375
+ if (responseInfo === void 0) {
3376
+ for (const [statusCode, respInfo] of Object.entries(responses)) {
3377
+ if (statusCode.startsWith("2")) {
3378
+ responseInfo = respInfo;
3379
+ break;
3380
+ }
3381
+ }
3382
+ }
3383
+ if (responseInfo === void 0 || !truthy(responseInfo.contentSchema)) return null;
3384
+ let schema;
3385
+ for (const contentType of JSON_COMPATIBLE_TYPES) {
3386
+ if (contentType in responseInfo.contentSchema) {
3387
+ schema = responseInfo.contentSchema[contentType];
3388
+ break;
3389
+ }
3390
+ }
3391
+ if (schema === void 0) {
3392
+ const firstContentType = Object.keys(responseInfo.contentSchema)[0];
3393
+ schema = responseInfo.contentSchema[firstContentType];
3394
+ }
3395
+ if (!truthy(schema) || !isPlainObject(schema)) return null;
3396
+ let outputSchema = replaceRefWithDefs(schema);
3397
+ if (typeof outputSchema.$ref === "string" && schemaDefinitions && truthy(schemaDefinitions)) {
3398
+ const refPath = outputSchema.$ref;
3399
+ if (refPath.startsWith("#/$defs/")) {
3400
+ const schemaName = refPath.split("/").pop();
3401
+ if (schemaName in schemaDefinitions) {
3402
+ outputSchema = replaceRefWithDefs(schemaDefinitions[schemaName]);
3403
+ }
3404
+ }
3405
+ }
3406
+ if (openapiVersion?.startsWith("3")) {
3407
+ outputSchema = convertOpenAPISchemaToJsonSchema(outputSchema, openapiVersion);
3408
+ }
3409
+ if (outputSchema.type !== "object") {
3410
+ outputSchema = {
3411
+ type: "object",
3412
+ properties: { result: outputSchema },
3413
+ required: ["result"],
3414
+ "x-fastmcp-wrap-result": true
3415
+ };
3416
+ }
3417
+ if (schemaDefinitions && truthy(schemaDefinitions)) {
3418
+ const processedDefs = { ...schemaDefinitions };
3419
+ for (const [name, defSchema] of Object.entries(processedDefs)) {
3420
+ if (isPlainObject(defSchema)) {
3421
+ processedDefs[name] = replaceRefWithDefs(defSchema);
3422
+ }
3423
+ }
3424
+ if (openapiVersion?.startsWith("3")) {
3425
+ for (const name of Object.keys(processedDefs)) {
3426
+ processedDefs[name] = convertOpenAPISchemaToJsonSchema(processedDefs[name], openapiVersion);
3427
+ }
3428
+ }
3429
+ outputSchema.$defs = processedDefs;
3430
+ }
3431
+ return outputSchema;
3432
+ }
3433
+
3434
+ // src/server/openapi/parser.ts
3435
+ function loadSpec(spec) {
3436
+ if (typeof spec !== "string") return spec;
3437
+ const parsed = parseYaml(spec);
3438
+ if (!isPlainObject(parsed)) {
3439
+ throw new Error("OpenAPI spec text must parse to an object");
3440
+ }
3441
+ return parsed;
3442
+ }
3443
+ var HTTP_METHODS_LOWER = [
3444
+ "get",
3445
+ "put",
3446
+ "post",
3447
+ "delete",
3448
+ "options",
3449
+ "head",
3450
+ "patch",
3451
+ "trace"
3452
+ ];
3453
+ var PRIMARY_SUCCESS_PRIORITY = ["200", "201", "202", "204", "207"];
3454
+ var PARAMETER_LOCATIONS = ["path", "query", "header", "cookie"];
3455
+ function isSuccessStatusCode(statusCode) {
3456
+ if (/^\s*[+-]?\d+\s*$/.test(statusCode)) {
3457
+ const code = parseInt(statusCode, 10);
3458
+ return code >= 200 && code < 300;
3459
+ }
3460
+ return ["default", "2xx"].includes(statusCode.toLowerCase());
3461
+ }
3462
+ function parseOpenAPIToHttpRoutes(spec) {
3463
+ const openapiVersion = typeof spec.openapi === "string" ? spec.openapi : "";
3464
+ return new SpecParser(spec, openapiVersion).parse();
3465
+ }
3466
+ var SpecParser = class {
3467
+ constructor(spec, openapiVersion) {
3468
+ this.spec = spec;
3469
+ this.openapiVersion = openapiVersion;
3470
+ }
3471
+ spec;
3472
+ openapiVersion;
3473
+ toParameterLocation(paramIn) {
3474
+ if (PARAMETER_LOCATIONS.includes(paramIn)) {
3475
+ return paramIn;
3476
+ }
3477
+ warn(`Unknown parameter location: ${paramIn}, defaulting to 'query'`);
3478
+ return "query";
3479
+ }
3480
+ /** Resolve a `{$ref}` object to its target within the spec (local only). */
3481
+ resolveRef(item) {
3482
+ if (!isPlainObject(item) || typeof item.$ref !== "string") return item;
3483
+ const ref = item.$ref;
3484
+ try {
3485
+ if (!ref.startsWith("#/")) {
3486
+ throw new Error(`External or non-local reference not supported: ${ref}`);
3487
+ }
3488
+ const parts = ref.replace(/^[#/]+/, "").replace(/[#/]+$/, "").split("/");
3489
+ let target = this.spec;
3490
+ for (const part of parts) {
3491
+ if (/^\d+$/.test(part) && Array.isArray(target)) {
3492
+ target = target[Number(part)];
3493
+ } else if (isPlainObject(target)) {
3494
+ target = target[part];
3495
+ } else {
3496
+ throw new Error(`Cannot traverse part '${part}' in reference '${ref}'`);
3497
+ }
3498
+ if (target === null || target === void 0) {
3499
+ throw new Error(`Reference part '${part}' not found in path '${ref}'`);
3500
+ }
3501
+ }
3502
+ if (isPlainObject(target) && typeof target.$ref === "string") {
3503
+ return this.resolveRef(target);
3504
+ }
3505
+ return target;
3506
+ } catch (error) {
3507
+ throw new Error(`Failed to resolve reference '${ref}': ${error.message}`);
3508
+ }
3509
+ }
3510
+ /** Resolve a schema (or ref to one) and return it as a ref-converted copy. */
3511
+ extractSchemaAsDict(schemaObj) {
3512
+ try {
3513
+ const resolved = this.resolveRef(schemaObj);
3514
+ let result;
3515
+ if (isPlainObject(resolved)) {
3516
+ result = structuredClone(resolved);
3517
+ } else {
3518
+ warn(`Expected a schema object after resolving, got ${typeof resolved}. Using empty schema.`);
3519
+ result = {};
3520
+ }
3521
+ return replaceRefWithDefs(result);
3522
+ } catch (error) {
3523
+ if (isExternalRefError(error)) throw error;
3524
+ warn(`Failed to extract schema: ${error.message}`);
3525
+ return {};
3526
+ }
3527
+ }
3528
+ extractParameters(operationParams, pathItemParams) {
3529
+ const extracted = [];
3530
+ const seen = /* @__PURE__ */ new Set();
3531
+ const all = [
3532
+ ...Array.isArray(operationParams) ? operationParams : [],
3533
+ ...Array.isArray(pathItemParams) ? pathItemParams : []
3534
+ ];
3535
+ for (const paramOrRef of all) {
3536
+ try {
3537
+ const parameter = this.resolveRef(paramOrRef);
3538
+ if (!isPlainObject(parameter) || typeof parameter.name !== "string" || typeof parameter.in !== "string") {
3539
+ warn(`Expected a parameter object after resolving. Skipping.`);
3540
+ continue;
3541
+ }
3542
+ const location = this.toParameterLocation(parameter.in);
3543
+ const key = `${parameter.name}\0${parameter.in}`;
3544
+ if (seen.has(key)) continue;
3545
+ seen.add(key);
3546
+ let paramSchema = {};
3547
+ if (truthy(parameter.schema)) {
3548
+ paramSchema = this.extractSchemaAsDict(parameter.schema);
3549
+ } else if (isPlainObject(parameter.content) && truthy(parameter.content)) {
3550
+ const firstMediaType = Object.values(parameter.content)[0];
3551
+ if (isPlainObject(firstMediaType) && truthy(firstMediaType.schema)) {
3552
+ paramSchema = this.extractSchemaAsDict(firstMediaType.schema);
3553
+ }
3554
+ }
3555
+ extracted.push({
3556
+ name: parameter.name,
3557
+ location,
3558
+ required: Boolean(parameter.required),
3559
+ schema: paramSchema,
3560
+ ...typeof parameter.description === "string" ? { description: parameter.description } : {},
3561
+ ...typeof parameter.explode === "boolean" ? { explode: parameter.explode } : {},
3562
+ ...typeof parameter.style === "string" ? { style: parameter.style } : {}
3563
+ });
3564
+ } catch (error) {
3565
+ warn(`Failed to extract parameter: ${error.message}`);
3566
+ }
3567
+ }
3568
+ return extracted;
3569
+ }
3570
+ extractRequestBody(requestBodyOrRef) {
3571
+ if (!truthy(requestBodyOrRef)) return void 0;
3572
+ try {
3573
+ const requestBody = this.resolveRef(requestBodyOrRef);
3574
+ if (!isPlainObject(requestBody)) {
3575
+ warn(`Expected a request body object after resolving. Skipping request body.`);
3576
+ return void 0;
3577
+ }
3578
+ const info = {
3579
+ required: Boolean(requestBody.required),
3580
+ contentSchema: {},
3581
+ ...typeof requestBody.description === "string" ? { description: requestBody.description } : {}
3582
+ };
3583
+ if (isPlainObject(requestBody.content)) {
3584
+ for (const [mediaType, mediaTypeObj] of Object.entries(requestBody.content)) {
3585
+ if (isPlainObject(mediaTypeObj) && truthy(mediaTypeObj.schema)) {
3586
+ try {
3587
+ info.contentSchema[mediaType] = this.extractSchemaAsDict(mediaTypeObj.schema);
3588
+ } catch (error) {
3589
+ if (isExternalRefError(error)) throw error;
3590
+ warn(`Failed to extract schema for media type '${mediaType}': ${error.message}`);
3591
+ }
3592
+ }
3593
+ }
3594
+ }
3595
+ return info;
3596
+ } catch (error) {
3597
+ if (isExternalRefError(error)) throw error;
3598
+ warn(`Failed to extract request body: ${error.message}`);
3599
+ return void 0;
3600
+ }
3601
+ }
3602
+ getPrimarySuccessResponse(operationResponses) {
3603
+ for (const code of PRIMARY_SUCCESS_PRIORITY) {
3604
+ if (code in operationResponses) return [code, operationResponses[code]];
3605
+ }
3606
+ for (const [statusCode, respOrRef] of Object.entries(operationResponses)) {
3607
+ if (isSuccessStatusCode(statusCode)) return [statusCode, respOrRef];
3608
+ }
3609
+ return null;
3610
+ }
3611
+ /** Extract only the primary success response for the MCP component. */
3612
+ extractResponses(operationResponses) {
3613
+ const extracted = {};
3614
+ if (!isPlainObject(operationResponses) || !truthy(operationResponses)) return extracted;
3615
+ const primary = this.getPrimarySuccessResponse(operationResponses);
3616
+ if (primary === null) return extracted;
3617
+ const [statusCode, respOrRef] = primary;
3618
+ try {
3619
+ const response = this.resolveRef(respOrRef);
3620
+ if (!isPlainObject(response)) {
3621
+ warn(`Expected a response object after resolving for status code ${statusCode}. Skipping.`);
3622
+ return extracted;
3623
+ }
3624
+ const respInfo = {
3625
+ contentSchema: {},
3626
+ ...typeof response.description === "string" ? { description: response.description } : {}
3627
+ };
3628
+ if (isPlainObject(response.content)) {
3629
+ for (const [mediaType, mediaTypeObj] of Object.entries(response.content)) {
3630
+ if (isPlainObject(mediaTypeObj) && truthy(mediaTypeObj.schema)) {
3631
+ try {
3632
+ let topLevelSchemaName = null;
3633
+ const mediaSchema = mediaTypeObj.schema;
3634
+ if (isPlainObject(mediaSchema) && typeof mediaSchema.$ref === "string") {
3635
+ if (mediaSchema.$ref.startsWith("#/components/schemas/")) {
3636
+ topLevelSchemaName = mediaSchema.$ref.split("/").pop();
3637
+ }
3638
+ }
3639
+ const schemaDict = this.extractSchemaAsDict(mediaSchema);
3640
+ if (topLevelSchemaName !== null) {
3641
+ schemaDict["x-fastmcp-top-level-schema"] = topLevelSchemaName;
3642
+ }
3643
+ respInfo.contentSchema[mediaType] = schemaDict;
3644
+ } catch (error) {
3645
+ if (isExternalRefError(error)) throw error;
3646
+ warn(
3647
+ `Failed to extract schema for media type '${mediaType}' in response ${statusCode}: ${error.message}`
3648
+ );
3649
+ }
3650
+ } else {
3651
+ respInfo.contentSchema[mediaType] ??= {};
3652
+ }
3653
+ }
3654
+ }
3655
+ extracted[statusCode] = respInfo;
3656
+ } catch (error) {
3657
+ if (isExternalRefError(error)) throw error;
3658
+ warn(`Failed to extract response for status code ${statusCode}: ${error.message}`);
3659
+ }
3660
+ return extracted;
3661
+ }
3662
+ /** Collect schema names referenced by `schema`, transitively. */
3663
+ extractSchemaDependencies(schema, allSchemas, collected = /* @__PURE__ */ new Set(), followDiscriminator = false) {
3664
+ const collect = (schemaName) => {
3665
+ if (!collected.has(schemaName) && schemaName in allSchemas) {
3666
+ collected.add(schemaName);
3667
+ findRefs(allSchemas[schemaName]);
3668
+ }
3669
+ };
3670
+ const findRefs = (obj) => {
3671
+ if (isPlainObject(obj)) {
3672
+ if (typeof obj.$ref === "string") {
3673
+ const ref = obj.$ref;
3674
+ if (ref.startsWith("#/$defs/") || ref.startsWith("#/components/schemas/")) {
3675
+ collect(ref.split("/").pop());
3676
+ }
3677
+ }
3678
+ if (followDiscriminator) {
3679
+ const discriminator = obj.discriminator;
3680
+ if (isPlainObject(discriminator) && isPlainObject(discriminator.mapping)) {
3681
+ for (const target of Object.values(discriminator.mapping)) {
3682
+ if (typeof target !== "string") continue;
3683
+ const name = discriminatorTargetName(target);
3684
+ if (name !== null) collect(name);
3685
+ }
3686
+ }
3687
+ }
3688
+ for (const value of Object.values(obj)) findRefs(value);
3689
+ } else if (Array.isArray(obj)) {
3690
+ for (const item of obj) findRefs(item);
3691
+ }
3692
+ };
3693
+ findRefs(schema);
3694
+ return collected;
3695
+ }
3696
+ extractInputSchemaDependencies(parameters, requestBody, allSchemas) {
3697
+ const needed = /* @__PURE__ */ new Set();
3698
+ for (const param of parameters) {
3699
+ if (truthy(param.schema)) {
3700
+ this.extractSchemaDependencies(param.schema, allSchemas, needed);
3701
+ }
3702
+ }
3703
+ if (requestBody && truthy(requestBody.contentSchema)) {
3704
+ for (const contentSchema of Object.values(requestBody.contentSchema)) {
3705
+ this.extractSchemaDependencies(contentSchema, allSchemas, needed, true);
3706
+ }
3707
+ }
3708
+ return Object.fromEntries([...needed].filter((n) => n in allSchemas).map((n) => [n, allSchemas[n]]));
3709
+ }
3710
+ extractOutputSchemaDependencies(responses, allSchemas) {
3711
+ if (!truthy(responses) || !truthy(allSchemas)) return {};
3712
+ const needed = /* @__PURE__ */ new Set();
3713
+ for (const response of Object.values(responses)) {
3714
+ if (!truthy(response.contentSchema)) continue;
3715
+ for (const contentSchema of Object.values(response.contentSchema)) {
3716
+ this.extractSchemaDependencies(contentSchema, allSchemas, needed);
3717
+ const schemaName = contentSchema["x-fastmcp-top-level-schema"];
3718
+ if (typeof schemaName === "string" && schemaName in allSchemas) {
3719
+ needed.add(schemaName);
3720
+ this.extractSchemaDependencies(allSchemas[schemaName], allSchemas, needed);
3721
+ }
3722
+ }
3723
+ }
3724
+ return Object.fromEntries([...needed].filter((n) => n in allSchemas).map((n) => [n, allSchemas[n]]));
3725
+ }
3726
+ parse() {
3727
+ const routes = [];
3728
+ const paths = this.spec.paths;
3729
+ if (!isPlainObject(paths) || !truthy(paths)) {
3730
+ warn("OpenAPI schema has no paths defined.");
3731
+ return [];
3732
+ }
3733
+ const schemaDefinitions = {};
3734
+ const components = this.spec.components;
3735
+ if (isPlainObject(components) && isPlainObject(components.schemas)) {
3736
+ for (const [name, schema] of Object.entries(components.schemas)) {
3737
+ try {
3738
+ schemaDefinitions[name] = this.extractSchemaAsDict(this.resolveRef(schema));
3739
+ } catch (error) {
3740
+ warn(`Failed to extract schema definition '${name}': ${error.message}`);
3741
+ }
3742
+ }
3743
+ }
3744
+ for (const [name, schema] of Object.entries(schemaDefinitions)) {
3745
+ if (isPlainObject(schema)) {
3746
+ schemaDefinitions[name] = replaceRefWithDefs(schema);
3747
+ }
3748
+ }
3749
+ for (const [pathStr, pathItemObj] of Object.entries(paths)) {
3750
+ if (!isPlainObject(pathItemObj)) {
3751
+ warn(`Skipping invalid path item for path '${pathStr}'`);
3752
+ continue;
3753
+ }
3754
+ const pathLevelParams = pathItemObj.parameters;
3755
+ for (const methodLower of HTTP_METHODS_LOWER) {
3756
+ const operation = pathItemObj[methodLower];
3757
+ if (!isPlainObject(operation)) continue;
3758
+ const method = methodLower.toUpperCase();
3759
+ try {
3760
+ const parameters = this.extractParameters(operation.parameters, pathLevelParams);
3761
+ const requestBody = this.extractRequestBody(operation.requestBody);
3762
+ const responses = this.extractResponses(operation.responses);
3763
+ const extensions = Object.fromEntries(
3764
+ Object.entries(operation).filter(([key]) => key.startsWith("x-"))
3765
+ );
3766
+ const requestSchemas = this.extractInputSchemaDependencies(
3767
+ parameters,
3768
+ requestBody,
3769
+ schemaDefinitions
3770
+ );
3771
+ const responseSchemas = this.extractOutputSchemaDependencies(
3772
+ responses,
3773
+ schemaDefinitions
3774
+ );
3775
+ const route = {
3776
+ path: pathStr,
3777
+ method,
3778
+ ...typeof operation.operationId === "string" ? { operationId: operation.operationId } : {},
3779
+ ...typeof operation.summary === "string" ? { summary: operation.summary } : {},
3780
+ ...typeof operation.description === "string" ? { description: operation.description } : {},
3781
+ tags: Array.isArray(operation.tags) ? [...operation.tags] : [],
3782
+ parameters,
3783
+ ...requestBody !== void 0 ? { requestBody } : {},
3784
+ responses,
3785
+ requestSchemas,
3786
+ responseSchemas,
3787
+ extensions,
3788
+ openapiVersion: this.openapiVersion,
3789
+ flatParamSchema: {},
3790
+ parameterMap: {}
3791
+ };
3792
+ try {
3793
+ const [flatSchema, parameterMap] = combineSchemasAndMapParams(route);
3794
+ route.flatParamSchema = flatSchema;
3795
+ route.parameterMap = parameterMap;
3796
+ } catch (error) {
3797
+ warn(
3798
+ `Failed to pre-calculate schema for route ${method} ${pathStr}: ${error.message}`
3799
+ );
3800
+ route.flatParamSchema = { type: "object", properties: {} };
3801
+ route.parameterMap = {};
3802
+ }
3803
+ routes.push(route);
3804
+ } catch (error) {
3805
+ if (isExternalRefError(error)) throw error;
3806
+ const opId = typeof operation.operationId === "string" ? operation.operationId : "unknown";
3807
+ warn(
3808
+ `Failed to process operation ${method} ${pathStr} (ID: ${opId}): ${error.message}`
3809
+ );
3810
+ }
3811
+ }
3812
+ }
3813
+ return routes;
3814
+ }
3815
+ };
3816
+
3817
+ // src/server/openapi/routing.ts
3818
+ var DEFAULT_ROUTE_MAPPINGS = [{ mcpType: "tool" }];
3819
+ function patternMatches(pattern, path) {
3820
+ if (pattern === void 0) return true;
3821
+ if (typeof pattern === "string") return new RegExp(pattern).test(path);
3822
+ return path.search(pattern) !== -1;
3823
+ }
3824
+ function determineRouteType(route, mappings) {
3825
+ for (const routeMap of mappings) {
3826
+ const methods = routeMap.methods ?? "*";
3827
+ if (methods !== "*" && !methods.includes(route.method)) continue;
3828
+ if (!patternMatches(routeMap.pattern, route.path)) continue;
3829
+ if (routeMap.tags && routeMap.tags.length > 0) {
3830
+ const routeTags = new Set(route.tags ?? []);
3831
+ if (!routeMap.tags.every((tag) => routeTags.has(tag))) continue;
3832
+ }
3833
+ return routeMap;
3834
+ }
3835
+ return { mcpType: "tool" };
3836
+ }
3837
+
3838
+ // src/server/openapi/naming.ts
3839
+ function slugify(text) {
3840
+ if (!text) return "";
3841
+ return text.replace(/[\s\-.]+/g, "_").replace(/[^a-zA-Z0-9_]/g, "").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
3842
+ }
3843
+ function generateDefaultName(route, names) {
3844
+ let name;
3845
+ if (route.operationId) {
3846
+ name = names?.[route.operationId] ?? route.operationId.split("__")[0];
3847
+ } else {
3848
+ name = route.summary || `${route.method}_${route.path}`;
3849
+ }
3850
+ name = slugify(name);
3851
+ if (name.length > 56) name = name.slice(0, 56);
3852
+ return name;
3853
+ }
3854
+ var NameRegistry = class {
3855
+ counters = {
3856
+ tool: /* @__PURE__ */ new Map(),
3857
+ resource: /* @__PURE__ */ new Map(),
3858
+ resourceTemplate: /* @__PURE__ */ new Map(),
3859
+ prompt: /* @__PURE__ */ new Map()
3860
+ };
3861
+ getUniqueName(name, componentType) {
3862
+ const counter = this.counters[componentType];
3863
+ const count = (counter.get(name) ?? 0) + 1;
3864
+ counter.set(name, count);
3865
+ if (count === 1) return name;
3866
+ return `${name}_${count}`;
3867
+ }
3868
+ };
3869
+
3870
+ // src/server/openapi/director.ts
3871
+ function queryScalarToStr(value) {
3872
+ if (typeof value === "boolean") return value ? "true" : "false";
3873
+ return String(value);
3874
+ }
3875
+ var STYLE_DELIMITERS = {
3876
+ form: ",",
3877
+ spaceDelimited: " ",
3878
+ pipeDelimited: "|"
3879
+ };
3880
+ function unflattenArguments(route, flatArgs) {
3881
+ const pathParams = {};
3882
+ const queryParams = {};
3883
+ const headerParams = {};
3884
+ const cookieParams = {};
3885
+ const bodyProps = {};
3886
+ const buckets = {
3887
+ path: pathParams,
3888
+ query: queryParams,
3889
+ header: headerParams,
3890
+ cookie: cookieParams,
3891
+ body: bodyProps
3892
+ };
3893
+ if (Object.keys(route.parameterMap).length > 0) {
3894
+ for (const [argName, value] of Object.entries(flatArgs)) {
3895
+ if (value === null || value === void 0) continue;
3896
+ const mapping = route.parameterMap[argName];
3897
+ if (mapping === void 0) {
3898
+ warn(`Argument '${argName}' not found in parameter map for ${route.operationId ?? route.path}`);
3899
+ continue;
3900
+ }
3901
+ buckets[mapping.location][mapping.openapiName] = value;
3902
+ }
3903
+ } else {
3904
+ const paramLocations = new Map(route.parameters.map((p) => [p.name, p.location]));
3905
+ for (const [argName, value] of Object.entries(flatArgs)) {
3906
+ if (value === null || value === void 0) continue;
3907
+ const suffixIdx = argName.lastIndexOf("__");
3908
+ if (suffixIdx !== -1) {
3909
+ const baseName = argName.slice(0, suffixIdx);
3910
+ const location2 = argName.slice(suffixIdx + 2);
3911
+ if (location2 in buckets && location2 !== "body") {
3912
+ buckets[location2][baseName] = value;
3913
+ continue;
3914
+ }
3915
+ }
3916
+ const location = paramLocations.get(argName);
3917
+ if (location !== void 0) {
3918
+ buckets[location][argName] = value;
3919
+ } else {
3920
+ bodyProps[argName] = value;
3921
+ }
3922
+ }
3923
+ }
3924
+ let body;
3925
+ if (Object.keys(bodyProps).length > 0) {
3926
+ const contentSchema = route.requestBody?.contentSchema;
3927
+ if (contentSchema && Object.keys(contentSchema).length > 0) {
3928
+ const contentType = Object.keys(contentSchema)[0];
3929
+ const bodySchema = contentSchema[contentType];
3930
+ if (isPlainObject(bodySchema) && bodySchema.type === "object") {
3931
+ body = bodyProps;
3932
+ } else if (Object.keys(bodyProps).length === 1) {
3933
+ body = Object.values(bodyProps)[0];
3934
+ } else {
3935
+ body = bodyProps;
3936
+ }
3937
+ } else {
3938
+ body = bodyProps;
3939
+ }
3940
+ }
3941
+ return { pathParams, queryParams, headerParams, cookieParams, body };
3942
+ }
3943
+ function serializeQueryParams(route, queryParams) {
3944
+ if (Object.keys(queryParams).length === 0) return queryParams;
3945
+ const paramLookup = new Map(
3946
+ route.parameters.filter((p) => p.location === "query").map((p) => [p.name, p])
3947
+ );
3948
+ const serialized = {};
3949
+ for (const [key, value] of Object.entries(queryParams)) {
3950
+ const paramInfo = paramLookup.get(key);
3951
+ if (paramInfo !== void 0) {
3952
+ const explode = paramInfo.explode ?? true;
3953
+ if (isPlainObject(value)) {
3954
+ if (Object.keys(value).length === 0) continue;
3955
+ if (explode) {
3956
+ for (const [propKey, propValue] of Object.entries(value)) {
3957
+ const propertyName = paramInfo.style === "deepObject" ? `${key}[${queryScalarToStr(propKey)}]` : queryScalarToStr(propKey);
3958
+ serialized[propertyName] = queryScalarToStr(propValue);
3959
+ }
3960
+ } else {
3961
+ const delimiter = STYLE_DELIMITERS[paramInfo.style ?? "form"] ?? ",";
3962
+ const parts = [];
3963
+ for (const [propKey, propValue] of Object.entries(value)) {
3964
+ parts.push(queryScalarToStr(propKey), queryScalarToStr(propValue));
3965
+ }
3966
+ serialized[key] = parts.join(delimiter);
3967
+ }
3968
+ continue;
3969
+ }
3970
+ if (!explode && Array.isArray(value)) {
3971
+ if (value.length === 0) continue;
3972
+ const delimiter = STYLE_DELIMITERS[paramInfo.style ?? "form"] ?? ",";
3973
+ serialized[key] = value.map(queryScalarToStr).join(delimiter);
3974
+ continue;
3975
+ }
3976
+ }
3977
+ serialized[key] = value;
3978
+ }
3979
+ return serialized;
3980
+ }
3981
+ function quotePathValue(value) {
3982
+ return encodeURIComponent(value).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`).replace(/\./g, "%2E");
3983
+ }
3984
+ function buildUrl(pathTemplate, pathParams, baseUrl) {
3985
+ let urlPath = pathTemplate;
3986
+ for (const [paramName, paramValue] of Object.entries(pathParams)) {
3987
+ const placeholder = `{${paramName}}`;
3988
+ if (urlPath.includes(placeholder)) {
3989
+ urlPath = urlPath.split(placeholder).join(quotePathValue(String(paramValue)));
3990
+ }
3991
+ }
3992
+ const base = `${baseUrl.replace(/\/+$/, "")}/`;
3993
+ return new URL(urlPath.replace(/^\/+/, ""), base).toString();
3994
+ }
3995
+ function buildRequest(route, flatArgs, baseUrl) {
3996
+ const { pathParams, queryParams, headerParams, cookieParams, body } = unflattenArguments(
3997
+ route,
3998
+ flatArgs
3999
+ );
4000
+ const serializedQuery = serializeQueryParams(route, queryParams);
4001
+ const url = new URL(buildUrl(route.path, pathParams, baseUrl));
4002
+ for (const [key, value] of Object.entries(serializedQuery)) {
4003
+ if (Array.isArray(value)) {
4004
+ for (const item of value) url.searchParams.append(key, queryScalarToStr(item));
4005
+ } else {
4006
+ url.searchParams.append(key, queryScalarToStr(value));
4007
+ }
4008
+ }
4009
+ const headers = {};
4010
+ for (const [key, value] of Object.entries(headerParams)) {
4011
+ headers[key] = queryScalarToStr(value);
4012
+ }
4013
+ const cookiePairs = Object.entries(cookieParams).map(
4014
+ ([key, value]) => `${key}=${queryScalarToStr(value)}`
4015
+ );
4016
+ if (cookiePairs.length > 0) {
4017
+ headers.cookie = cookiePairs.join("; ");
4018
+ }
4019
+ let rawContentType;
4020
+ let declaredContentType;
4021
+ const contentSchema = route.requestBody?.contentSchema;
4022
+ if (contentSchema && Object.keys(contentSchema).length > 0) {
4023
+ rawContentType = Object.keys(contentSchema)[0];
4024
+ declaredContentType = rawContentType.split(";")[0].trim().toLowerCase();
4025
+ }
4026
+ let requestBody;
4027
+ if (body !== null && body !== void 0) {
4028
+ if (declaredContentType === "multipart/form-data" && isPlainObject(body)) {
4029
+ const form = new FormData();
4030
+ for (const [key, value] of Object.entries(body)) {
4031
+ if (value instanceof Uint8Array) {
4032
+ form.append(key, new Blob([value]));
4033
+ } else {
4034
+ form.append(key, queryScalarToStr(value));
4035
+ }
4036
+ }
4037
+ requestBody = form;
4038
+ } else if (declaredContentType === "application/x-www-form-urlencoded" && isPlainObject(body)) {
4039
+ const params = new URLSearchParams();
4040
+ for (const [key, value] of Object.entries(body)) {
4041
+ params.append(key, queryScalarToStr(value));
4042
+ }
4043
+ requestBody = params;
4044
+ } else if (isPlainObject(body) || Array.isArray(body)) {
4045
+ requestBody = JSON.stringify(body);
4046
+ headers["content-type"] = declaredContentType !== void 0 && declaredContentType !== "application/json" && declaredContentType.includes("json") ? rawContentType : "application/json";
4047
+ } else {
4048
+ requestBody = String(body);
4049
+ if (rawContentType !== void 0) {
4050
+ headers["content-type"] = rawContentType;
4051
+ }
4052
+ }
4053
+ }
4054
+ return {
4055
+ method: route.method.toUpperCase(),
4056
+ url: url.toString(),
4057
+ headers,
4058
+ ...requestBody !== void 0 ? { body: requestBody } : {}
4059
+ };
4060
+ }
4061
+ function pathArgumentName(route, parameterName) {
4062
+ for (const [argumentName, mapping] of Object.entries(route.parameterMap)) {
4063
+ if (mapping.location === "path" && mapping.openapiName === parameterName) {
4064
+ return argumentName;
4065
+ }
4066
+ }
4067
+ return parameterName;
4068
+ }
4069
+
4070
+ // src/server/openapi/execute.ts
4071
+ var DEFAULT_TIMEOUT_MS = 3e4;
4072
+ function fillServerVariables(url, variables) {
4073
+ if (!isPlainObject(variables)) return url;
4074
+ let filled = url;
4075
+ for (const [name, variable] of Object.entries(variables)) {
4076
+ const fallback = isPlainObject(variable) ? variable.default : void 0;
4077
+ filled = filled.split(`{${name}}`).join(typeof fallback === "string" ? fallback : "");
4078
+ }
4079
+ return filled;
4080
+ }
4081
+ function resolveClient(options, spec) {
4082
+ let baseUrl = options.baseUrl;
4083
+ if (baseUrl === void 0) {
4084
+ const servers = spec.servers;
4085
+ const first = Array.isArray(servers) ? servers[0] : void 0;
4086
+ const serverUrl = isPlainObject(first) && typeof first.url === "string" ? first.url : void 0;
4087
+ if (serverUrl === void 0) {
4088
+ throw new Error(
4089
+ "No server URL found in OpenAPI spec. Either add a 'servers' entry to the spec or provide client.baseUrl explicitly."
4090
+ );
4091
+ }
4092
+ baseUrl = fillServerVariables(serverUrl, first?.variables);
4093
+ }
4094
+ try {
4095
+ new URL(baseUrl);
4096
+ } catch {
4097
+ throw new Error(
4098
+ `OpenAPI base URL must be absolute, got '${baseUrl}'. Provide client.baseUrl explicitly.`
4099
+ );
4100
+ }
4101
+ return {
4102
+ baseUrl,
4103
+ headers: options.headers ?? {},
4104
+ ...options.auth !== void 0 ? { auth: options.auth } : {},
4105
+ fetch: options.fetch ?? globalThis.fetch,
4106
+ timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS
4107
+ };
4108
+ }
4109
+ function isTimeoutError(error) {
4110
+ return error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError");
4111
+ }
4112
+ async function sendRequest(client, route, args) {
4113
+ let url;
4114
+ let init;
4115
+ try {
4116
+ const directed = buildRequest(route, args, client.baseUrl);
4117
+ const authHeaders = client.auth ? await client.auth.getHeaders() : {};
4118
+ const headers = {};
4119
+ for (const source of [client.headers, authHeaders, directed.headers]) {
4120
+ for (const [name, value] of Object.entries(source)) {
4121
+ headers[name.toLowerCase()] = value;
4122
+ }
4123
+ }
4124
+ url = directed.url;
4125
+ init = {
4126
+ method: directed.method,
4127
+ headers,
4128
+ ...directed.body !== void 0 ? { body: directed.body } : {},
4129
+ signal: AbortSignal.timeout(client.timeoutMs)
4130
+ };
4131
+ } catch (error) {
4132
+ const e = error;
4133
+ throw new Error(
4134
+ `Error building request for ${route.method.toUpperCase()} ${route.path}: ${e.name}: ${e.message}`
4135
+ );
4136
+ }
4137
+ let response;
4138
+ try {
4139
+ response = await client.fetch(url, init);
4140
+ } catch (error) {
4141
+ if (isTimeoutError(error)) {
4142
+ throw new Error(`HTTP request timed out (${error.name})`);
4143
+ }
4144
+ const e = error;
4145
+ throw new Error(`Request error (${e.name}): ${e.message}`);
4146
+ }
4147
+ if (!response.ok) {
4148
+ let message = `HTTP error ${response.status}: ${response.statusText}`;
4149
+ const text = await response.text().catch(() => "");
4150
+ if (text) message += ` - ${text}`;
4151
+ throw new Error(message);
4152
+ }
4153
+ return response;
4154
+ }
4155
+ async function runToolRequest(client, route, args, outputSchema) {
4156
+ const response = await sendRequest(client, route, args);
4157
+ const text = await response.text();
4158
+ let result;
4159
+ try {
4160
+ result = JSON.parse(text);
4161
+ } catch {
4162
+ return new ToolResult({ content: [{ type: "text", text }] });
4163
+ }
4164
+ let structured;
4165
+ if (outputSchema !== null && outputSchema !== void 0) {
4166
+ structured = truthy(outputSchema["x-fastmcp-wrap-result"]) ? { result } : result;
4167
+ } else if (!isPlainObject(result)) {
4168
+ structured = { result };
4169
+ } else {
4170
+ structured = result;
4171
+ }
4172
+ if (!isPlainObject(structured)) {
4173
+ structured = { result: structured };
4174
+ }
4175
+ return new ToolResult({
4176
+ content: [{ type: "text", text: JSON.stringify(structured) }],
4177
+ structuredContent: structured
4178
+ });
4179
+ }
4180
+ async function readResourceRequest(client, route, args, uri, mimeType) {
4181
+ const response = await sendRequest(client, route, args);
4182
+ const contentType = (response.headers.get("content-type") ?? "").toLowerCase();
4183
+ if (contentType.includes("application/json")) {
4184
+ const result = JSON.parse(await response.text());
4185
+ return new ResourceResult([
4186
+ { uri, mimeType: "application/json", text: JSON.stringify(result) }
4187
+ ]);
4188
+ }
4189
+ if (contentType.includes("text/") || contentType.includes("application/xml")) {
4190
+ return new ResourceResult([{ uri, mimeType, text: await response.text() }]);
4191
+ }
4192
+ const bytes = Buffer.from(await response.arrayBuffer());
4193
+ return new ResourceResult([{ uri, mimeType, blob: bytes.toString("base64") }]);
4194
+ }
4195
+
4196
+ // src/server/openapi/index.ts
4197
+ var DEFAULT_MIME_TYPE = "application/json";
4198
+ var JSON_COMPATIBLE_TYPES2 = [
4199
+ "application/json",
4200
+ "application/vnd.api+json",
4201
+ "application/hal+json",
4202
+ "application/ld+json",
4203
+ "text/json"
4204
+ ];
4205
+ function extractMimeTypeFromRoute(route) {
4206
+ if (!truthy(route.responses)) return DEFAULT_MIME_TYPE;
4207
+ let responseInfo = void 0;
4208
+ for (const statusCode of ["200", "201", "202", "204"]) {
4209
+ if (statusCode in route.responses) {
4210
+ responseInfo = route.responses[statusCode];
4211
+ break;
4212
+ }
4213
+ }
4214
+ if (responseInfo === void 0) {
4215
+ for (const [statusCode, respInfo] of Object.entries(route.responses)) {
4216
+ if (statusCode.startsWith("2")) {
4217
+ responseInfo = respInfo;
4218
+ break;
4219
+ }
4220
+ }
4221
+ }
4222
+ if (responseInfo === void 0 || !truthy(responseInfo.contentSchema)) return DEFAULT_MIME_TYPE;
4223
+ const contentTypes = Object.keys(responseInfo.contentSchema);
4224
+ if (contentTypes.length === 1) return contentTypes[0];
4225
+ for (const contentType of JSON_COMPATIBLE_TYPES2) {
4226
+ if (contentType in responseInfo.contentSchema) return contentType;
4227
+ }
4228
+ return contentTypes[0];
4229
+ }
4230
+ function expandTemplateUri(uriTemplate, params) {
4231
+ let uri = uriTemplate;
4232
+ for (const [name, value] of Object.entries(params)) {
4233
+ uri = uri.split(`{${name}}`).join(encodeURIComponent(value));
4234
+ }
4235
+ return uri;
4236
+ }
4237
+ function templateArguments(route, params) {
4238
+ const args = {};
4239
+ for (const parameter of route.parameters) {
4240
+ if (parameter.location !== "path") continue;
4241
+ const argumentName = pathArgumentName(route, parameter.name);
4242
+ if (parameter.name in params) {
4243
+ args[argumentName] = params[parameter.name];
4244
+ continue;
4245
+ }
4246
+ const normalizedName = parameter.name.replace(/-/g, "_");
4247
+ if (normalizedName in params) {
4248
+ args[argumentName] = params[normalizedName];
4249
+ }
4250
+ }
4251
+ return args;
4252
+ }
4253
+ function createOpenAPIServer(options) {
4254
+ const spec = loadSpec(options.spec);
4255
+ const client = resolveClient(options.client ?? {}, spec);
4256
+ const routes = parseOpenAPIToHttpRoutes(spec);
4257
+ const server = new FastMCP({
4258
+ name: options.name ?? "OpenAPI Server",
4259
+ ...options.version !== void 0 ? { version: options.version } : {},
4260
+ ...options.serverOptions
4261
+ });
4262
+ const routeMaps = [...options.routeMaps ?? [], ...DEFAULT_ROUTE_MAPPINGS];
4263
+ const registry = new NameRegistry();
4264
+ const validateOutput = options.validateOutput ?? true;
4265
+ for (const route of routes) {
4266
+ const routeMap = determineRouteType(route, routeMaps);
4267
+ let routeType = routeMap.mcpType;
4268
+ if (options.routeMapFn !== void 0) {
4269
+ try {
4270
+ const result = options.routeMapFn(route, routeType);
4271
+ if (result !== null && result !== void 0) {
4272
+ routeType = result;
4273
+ }
4274
+ } catch (error) {
4275
+ warn(
4276
+ `Error in routeMapFn for ${route.method} ${route.path}: ${error.message}. Using mapped type.`
4277
+ );
4278
+ }
4279
+ }
4280
+ const componentName = generateDefaultName(route, options.names);
4281
+ const componentTags = [
4282
+ .../* @__PURE__ */ new Set([...route.tags, ...routeMap.mcpTags ?? [], ...options.tags ?? []])
4283
+ ].sort();
4284
+ if (routeType === "tool") {
4285
+ createTool(server, client, route, componentName, componentTags, registry, validateOutput, options.componentFn);
4286
+ } else if (routeType === "resource") {
4287
+ createResource(server, client, route, componentName, componentTags, registry, options.componentFn);
4288
+ } else if (routeType === "resourceTemplate") {
4289
+ createTemplate(server, client, route, componentName, componentTags, registry, options.componentFn);
4290
+ }
4291
+ }
4292
+ return server;
4293
+ }
4294
+ function applyComponentFn(componentFn, route, component) {
4295
+ if (componentFn === void 0) return;
4296
+ try {
4297
+ componentFn(route, component);
4298
+ } catch (error) {
4299
+ warn(
4300
+ `Error in componentFn for ${component.kind} '${component.name}': ${error.message}`
4301
+ );
4302
+ }
4303
+ }
4304
+ function createTool(server, client, route, name, tags, registry, validateOutput, componentFn) {
4305
+ let outputSchema = extractOutputSchemaFromResponses(
4306
+ route.responses,
4307
+ route.responseSchemas,
4308
+ route.openapiVersion
4309
+ );
4310
+ if (!validateOutput && outputSchema !== null) {
4311
+ const permissive = { type: "object", additionalProperties: true };
4312
+ if (truthy(outputSchema["x-fastmcp-wrap-result"])) {
4313
+ permissive["x-fastmcp-wrap-result"] = true;
4314
+ }
4315
+ outputSchema = permissive;
4316
+ }
4317
+ const component = {
4318
+ kind: "tool",
4319
+ name: registry.getUniqueName(name, "tool"),
4320
+ description: route.description || route.summary || `Executes ${route.method} ${route.path}`,
4321
+ inputSchema: route.flatParamSchema,
4322
+ ...outputSchema !== null ? { outputSchema } : {},
4323
+ tags
4324
+ };
4325
+ applyComponentFn(componentFn, route, component);
4326
+ server.tool(
4327
+ {
4328
+ name: component.name,
4329
+ description: component.description,
4330
+ inputSchema: component.inputSchema,
4331
+ ...component.outputSchema !== void 0 ? { outputSchema: component.outputSchema } : {},
4332
+ tags: component.tags
4333
+ },
4334
+ async (args) => runToolRequest(client, route, isPlainObject(args) ? args : {}, component.outputSchema)
4335
+ );
4336
+ }
4337
+ function createResource(server, client, route, name, tags, registry, componentFn) {
4338
+ const uniqueName = registry.getUniqueName(name, "resource");
4339
+ const component = {
4340
+ kind: "resource",
4341
+ uri: `resource://${uniqueName}`,
4342
+ name: uniqueName,
4343
+ description: route.description || route.summary || `Represents ${route.path}`,
4344
+ mimeType: extractMimeTypeFromRoute(route),
4345
+ tags
4346
+ };
4347
+ applyComponentFn(componentFn, route, component);
4348
+ server.resource(
4349
+ {
4350
+ uri: component.uri,
4351
+ name: component.name,
4352
+ description: component.description,
4353
+ mimeType: component.mimeType,
4354
+ tags: component.tags
4355
+ },
4356
+ async () => readResourceRequest(client, route, {}, component.uri, component.mimeType)
4357
+ );
4358
+ }
4359
+ function createTemplate(server, client, route, name, tags, registry, componentFn) {
4360
+ const uniqueName = registry.getUniqueName(name, "resourceTemplate");
4361
+ const pathParams = route.parameters.filter((p) => p.location === "path").map((p) => p.name).sort();
4362
+ let uriTemplate = `resource://${uniqueName}`;
4363
+ if (pathParams.length > 0) {
4364
+ uriTemplate += "/" + pathParams.map((p) => `{${p}}`).join("/");
4365
+ }
4366
+ const parametersSchema = {
4367
+ type: "object",
4368
+ properties: Object.fromEntries(
4369
+ route.parameters.filter((p) => p.location === "path").map((p) => [
4370
+ p.name,
4371
+ {
4372
+ ...isPlainObject(p.schema) ? p.schema : {},
4373
+ ...truthy(p.description) && !(isPlainObject(p.schema) && "description" in p.schema) ? { description: p.description } : {}
4374
+ }
4375
+ ])
4376
+ ),
4377
+ required: route.parameters.filter((p) => p.location === "path" && p.required).map((p) => p.name)
4378
+ };
4379
+ const component = {
4380
+ kind: "resourceTemplate",
4381
+ uriTemplate,
4382
+ name: uniqueName,
4383
+ description: route.description || route.summary || `Template for ${route.path}`,
4384
+ mimeType: extractMimeTypeFromRoute(route),
4385
+ parameters: parametersSchema,
4386
+ tags
4387
+ };
4388
+ applyComponentFn(componentFn, route, component);
4389
+ server.resource(
4390
+ {
4391
+ uri: component.uriTemplate,
4392
+ name: component.name,
4393
+ description: component.description,
4394
+ mimeType: component.mimeType,
4395
+ tags: component.tags
4396
+ },
4397
+ async (params) => {
4398
+ const matched = params ?? {};
4399
+ const concreteUri = expandTemplateUri(component.uriTemplate, matched);
4400
+ return readResourceRequest(
4401
+ client,
4402
+ route,
4403
+ templateArguments(route, matched),
4404
+ concreteUri,
4405
+ component.mimeType
4406
+ );
4407
+ }
4408
+ );
4409
+ }
4410
+
2819
4411
  // src/server/apps/FastMCPApp.ts
2820
4412
  var FastMCPApp = class {
2821
4413
  server;
@@ -3353,8 +4945,10 @@ export {
3353
4945
  VersionFilter,
3354
4946
  acceptedContent,
3355
4947
  actionRef,
4948
+ createOpenAPIServer,
3356
4949
  createProxy,
3357
4950
  debugTokenVerifier,
4951
+ forwardableHeaders,
3358
4952
  inputRequired,
3359
4953
  inputResponse,
3360
4954
  introspectionVerifier,