@prefecthq/fastmcp-ts 1.0.0-rc.0 → 1.0.0-rc.1

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.
@@ -1172,7 +1172,7 @@ var init_version = __esm({
1172
1172
  async run({ args }) {
1173
1173
  if (args.json) setJsonMode(true);
1174
1174
  const data = {
1175
- fastmcp: "1.0.0-rc.0",
1175
+ fastmcp: "1.0.0-rc.1",
1176
1176
  "mcp-sdk": "2.0.0-beta.5",
1177
1177
  node: process.version,
1178
1178
  platform: `${process.platform} ${process.arch}`
@@ -46213,7 +46213,7 @@ init_format();
46213
46213
  var main = defineCommand({
46214
46214
  meta: {
46215
46215
  name: "fastmcp",
46216
- version: "1.0.0-rc.0",
46216
+ version: "1.0.0-rc.1",
46217
46217
  description: "FastMCP CLI \u2014 build, run, and manage MCP servers"
46218
46218
  },
46219
46219
  args: {
package/dist/server.d.ts CHANGED
@@ -796,6 +796,18 @@ interface RunOptions {
796
796
  port?: number;
797
797
  host?: string;
798
798
  path?: string;
799
+ /**
800
+ * Serve the legacy (2025-era) HTTP transport statelessly: a fresh server and
801
+ * transport per request, no session registry, incoming `mcp-session-id`
802
+ * ignored, and no session id issued. Use this behind a load balancer or on
803
+ * serverless compute where consecutive requests reach different instances.
804
+ *
805
+ * Falls back to the `FASTMCP_STATELESS_HTTP` environment variable, then to
806
+ * `false`. A valid value is ignored for the stdio transport, but a malformed
807
+ * one still aborts stdio startup, deliberately. The modern (2026-07-28) era
808
+ * is already stateless and is unaffected.
809
+ */
810
+ stateless?: boolean;
799
811
  /** Custom stdin stream for the stdio transport. Defaults to process.stdin. */
800
812
  stdin?: Readable;
801
813
  /** Custom stdout stream for the stdio transport. Defaults to process.stdout. */
@@ -862,11 +874,13 @@ declare class FastMCP {
862
874
  private _address;
863
875
  private _isRunning;
864
876
  private _sessions;
877
+ private _stateless;
865
878
  private _primaryServer;
866
879
  private _stdioServer;
867
880
  private _stdioState;
868
881
  private _stdioHandle;
869
882
  private _modernHandler;
883
+ private _statelessLegacyHandler;
870
884
  private _toolRegisteredCallbacks;
871
885
  private _resourceRegisteredCallbacks;
872
886
  private _promptRegisteredCallbacks;
@@ -898,6 +912,11 @@ declare class FastMCP {
898
912
  * client here, so nothing needs the era fork the way `subscribe` does. The
899
913
  * SDK also requires this capability to be present to register the
900
914
  * `completion/complete` handler (`assertRequestHandlerCapability`).
915
+ *
916
+ * `stateless` withdraws the same capability for a different reason. A
917
+ * stateless server has no session for a subscription to live in and no
918
+ * channel to deliver `notifications/resources/updated` on, so it must not
919
+ * claim the capability.
901
920
  */
902
921
  private _makeServer;
903
922
  private _resolveToken;
@@ -961,7 +980,7 @@ declare class FastMCP {
961
980
  * Dispatch a tool call through this server's middleware chain using an inherited context.
962
981
  * Used by parent servers to honour child-level middleware when routing mounted tool calls.
963
982
  */
964
- _dispatchTool(name: string, rawArgs: unknown, ctx: McpContext): Promise<{
983
+ _dispatchTool(name: string, rawArgs: unknown, ctx: McpContext): Promise<_modelcontextprotocol_server.InputRequiredResult | {
965
984
  [x: string]: unknown;
966
985
  content: ({
967
986
  type: "text";
@@ -1064,7 +1083,7 @@ declare class FastMCP {
1064
1083
  } | undefined;
1065
1084
  structuredContent?: unknown;
1066
1085
  isError?: boolean | undefined;
1067
- } | _modelcontextprotocol_server.InputRequiredResult>;
1086
+ }>;
1068
1087
  /**
1069
1088
  * Run a resource read through this server's middleware chain using an inherited context.
1070
1089
  * Returns the raw handler result so the parent can apply convertResourceResult with the
@@ -1139,6 +1158,16 @@ declare class FastMCP {
1139
1158
  * of createMcpHandler's own stateless legacy fallback, so session state and the
1140
1159
  * legacy server-initiated-request shim keep working for 2025-era clients. */
1141
1160
  private _getModernHandler;
1161
+ /** Lazily builds the stateless legacy (2025-era) HTTP handler. One per FastMCP
1162
+ * instance, mirroring _getModernHandler's per-instance, per-request-factory model.
1163
+ *
1164
+ * The SDK serves each POST from a fresh instance of the factory over a transport
1165
+ * built with `sessionIdGenerator: undefined`, and answers GET and DELETE with 405
1166
+ * because both are session operations that mean nothing per request.
1167
+ *
1168
+ * Only reachable when `_stateless` is on. The sessionful transport in
1169
+ * _dispatchLegacyHttp is untouched and still serves every other deployment. */
1170
+ private _getStatelessLegacyHandler;
1142
1171
  /**
1143
1172
  * Dual-era HTTP dispatch shared by the OAuth and non-OAuth serve paths. Assumes CORS
1144
1173
  * and auth have already been handled by the caller (req.auth already set, if any —
package/dist/server.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
 
5
5
  // src/server/FastMCP.ts
6
6
  import { hostHeaderValidation, originValidation } from "@modelcontextprotocol/node";
7
- import { ProtocolError as ProtocolError3, ProtocolErrorCode as ProtocolErrorCode3, ResourceNotFoundError, Server as Server2, createMcpHandler, isLegacyRequest, isJsonContentType, createRequestStateCodec, localhostAllowedHostnames, localhostAllowedOrigins, assertCompleteRequestPrompt, assertCompleteRequestResourceTemplate } from "@modelcontextprotocol/server";
7
+ import { ProtocolError as ProtocolError3, ProtocolErrorCode as ProtocolErrorCode3, ResourceNotFoundError, Server as Server2, createMcpHandler, legacyStatelessFallback, isLegacyRequest, isJsonContentType, createRequestStateCodec, localhostAllowedHostnames, localhostAllowedOrigins, assertCompleteRequestPrompt, assertCompleteRequestResourceTemplate } from "@modelcontextprotocol/server";
8
8
  import { randomUUID as randomUUID2 } from "crypto";
9
9
 
10
10
  // src/server/auth/types.ts
@@ -60,11 +60,17 @@ import { AsyncLocalStorage } from "async_hooks";
60
60
  var contextStore = new AsyncLocalStorage();
61
61
  var SESSION_CLOSE_CALLBACKS_KEY = "__fastmcp_session_close_callbacks";
62
62
  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
- function createContext(server, sdkCtx, auth, sessionState, requestStateCodec) {
63
+ 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
+ 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) {
64
66
  const requestId = String(sdkCtx.mcpReq.id);
65
67
  const progressToken = sdkCtx.mcpReq._meta?.progressToken;
66
68
  const isModernEra = sdkCtx.mcpReq.envelope !== void 0;
67
69
  const isModernHttpRequest = isModernEra && sdkCtx.http !== void 0;
70
+ const sessionlessReason = isModernHttpRequest ? "modern-http" : stateless && sdkCtx.http !== void 0 ? "stateless-http" : null;
71
+ const sessionStateError = () => new Error(
72
+ sessionlessReason === "modern-http" ? SESSION_STATE_MODERN_HTTP_ERROR : SESSION_STATE_STATELESS_HTTP_ERROR
73
+ );
68
74
  const serverRequestOptions = { relatedRequestId: sdkCtx.mcpReq.id };
69
75
  async function log(level, message, loggerName) {
70
76
  await sdkCtx.mcpReq.log(level, message, loggerName);
@@ -94,6 +100,7 @@ function createContext(server, sdkCtx, auth, sessionState, requestStateCodec) {
94
100
  });
95
101
  },
96
102
  async sample(params) {
103
+ if (sessionlessReason === "stateless-http") throw new Error(SERVER_INITIATED_STATELESS_HTTP_ERROR);
97
104
  if (!isModernEra) {
98
105
  const caps = server.getClientCapabilities();
99
106
  if (!caps?.sampling) {
@@ -121,6 +128,7 @@ function createContext(server, sdkCtx, auth, sessionState, requestStateCodec) {
121
128
  };
122
129
  },
123
130
  async elicit(message, schema) {
131
+ if (sessionlessReason === "stateless-http") throw new Error(SERVER_INITIATED_STATELESS_HTTP_ERROR);
124
132
  if (!isModernEra) {
125
133
  const caps = server.getClientCapabilities();
126
134
  if (!caps?.elicitation) {
@@ -144,6 +152,7 @@ function createContext(server, sdkCtx, auth, sessionState, requestStateCodec) {
144
152
  };
145
153
  },
146
154
  async listRoots() {
155
+ if (sessionlessReason === "stateless-http") throw new Error(SERVER_INITIATED_STATELESS_HTTP_ERROR);
147
156
  if (!isModernEra) {
148
157
  const caps = server.getClientCapabilities();
149
158
  if (!caps?.roots) {
@@ -165,15 +174,15 @@ function createContext(server, sdkCtx, auth, sessionState, requestStateCodec) {
165
174
  return JSON.stringify(payload);
166
175
  },
167
176
  getState: (key) => {
168
- if (isModernHttpRequest) throw new Error(SESSION_STATE_MODERN_HTTP_ERROR);
177
+ if (sessionlessReason) throw sessionStateError();
169
178
  return sessionState.get(key);
170
179
  },
171
180
  setState: (key, value) => {
172
- if (isModernHttpRequest) throw new Error(SESSION_STATE_MODERN_HTTP_ERROR);
181
+ if (sessionlessReason) throw sessionStateError();
173
182
  sessionState.set(key, value);
174
183
  },
175
184
  deleteState: (key) => {
176
- if (isModernHttpRequest) throw new Error(SESSION_STATE_MODERN_HTTP_ERROR);
185
+ if (sessionlessReason) throw sessionStateError();
177
186
  sessionState.delete(key);
178
187
  },
179
188
  resolveToolName: (name) => name,
@@ -184,6 +193,20 @@ function createContext(server, sdkCtx, auth, sessionState, requestStateCodec) {
184
193
  };
185
194
  }
186
195
 
196
+ // src/server/env.ts
197
+ var TRUE_VALUES = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
198
+ var FALSE_VALUES = /* @__PURE__ */ new Set(["0", "false", "no", "off"]);
199
+ function envBool(name) {
200
+ const raw = process.env[name];
201
+ if (raw === void 0 || raw.trim() === "") return void 0;
202
+ const normalized = raw.trim().toLowerCase();
203
+ if (TRUE_VALUES.has(normalized)) return true;
204
+ if (FALSE_VALUES.has(normalized)) return false;
205
+ throw new Error(
206
+ `[fastmcp] ${name} must be one of 1/true/yes/on or 0/false/no/off (case-insensitive). Received: ${JSON.stringify(raw)}`
207
+ );
208
+ }
209
+
187
210
  // src/server/middleware.ts
188
211
  import { ProtocolError, ProtocolErrorCode } from "@modelcontextprotocol/server";
189
212
  import { createHash } from "crypto";
@@ -500,6 +523,13 @@ import { ProtocolError as ProtocolError2, ProtocolErrorCode as ProtocolErrorCode
500
523
 
501
524
  // src/server/mrtr.ts
502
525
  import { inputRequired, acceptedContent, inputResponse, isInputRequiredResult } from "@modelcontextprotocol/server";
526
+ var INPUT_REQUESTS_STATELESS_HTTP_ERROR = "[fastmcp] inputRequired({ inputRequests }) is not available on a stateless HTTP server (RunOptions.stateless or FASTMCP_STATELESS_HTTP). Fulfilling inputRequests needs a session: the client's reply to each embedded request arrives on a separate request, and a stateless server discards its per-request Server before that reply could arrive \u2014 the same reason ctx.elicit/ctx.sample/ctx.listRoots are unavailable here. inputRequired({ requestState }), with no inputRequests, remains available \u2014 it carries state across rounds without needing a session. Turn stateless off to use inputRequests.";
527
+ function assertInputRequestsAllowedStateless(value, stateless) {
528
+ const hasInputRequests = value.inputRequests !== void 0 && Object.keys(value.inputRequests).length > 0;
529
+ if (stateless && hasInputRequests) {
530
+ throw new Error(INPUT_REQUESTS_STATELESS_HTTP_ERROR);
531
+ }
532
+ }
503
533
 
504
534
  // src/server/tool.ts
505
535
  var Image = class {
@@ -526,8 +556,9 @@ var ToolResult = class {
526
556
  }
527
557
  result;
528
558
  };
529
- function convertResult(value) {
559
+ function convertResult(value, stateless) {
530
560
  if (isInputRequiredResult(value)) {
561
+ assertInputRequestsAllowedStateless(value, stateless);
531
562
  return value;
532
563
  }
533
564
  if (value instanceof ToolResult) {
@@ -623,8 +654,9 @@ var ResourceResult = class {
623
654
  }
624
655
  contents;
625
656
  };
626
- function convertResourceResult(value, uri, mimeType) {
657
+ function convertResourceResult(value, uri, mimeType, stateless) {
627
658
  if (isInputRequiredResult(value)) {
659
+ assertInputRequestsAllowedStateless(value, stateless);
628
660
  return value;
629
661
  }
630
662
  if (value instanceof ResourceResult) {
@@ -721,8 +753,9 @@ function isPromptMessage(value) {
721
753
  if (!v.content || typeof v.content !== "object") return false;
722
754
  return typeof v.content.type === "string";
723
755
  }
724
- function convertPromptResult(value) {
756
+ function convertPromptResult(value, stateless) {
725
757
  if (isInputRequiredResult(value)) {
758
+ assertInputRequestsAllowedStateless(value, stateless);
726
759
  return value;
727
760
  }
728
761
  if (value instanceof PromptResult) {
@@ -766,6 +799,7 @@ function prefixResourceUri(uri, prefix) {
766
799
  return `${uri.slice(0, idx + 3)}${prefix}/${uri.slice(idx + 3)}`;
767
800
  }
768
801
  var RESOURCE_SUBSCRIPTIONS_KEY = "__fastmcp_resource_subscriptions";
802
+ var STATELESS_SUBSCRIBE_ERROR = "[fastmcp] resources/subscribe and resources/unsubscribe are unavailable on a stateless HTTP server. A stateless server keeps no session, so a subscription has nowhere to live and no channel to deliver on. The server does not advertise the resources.subscribe capability; a compliant client will not call this.";
769
803
  function inferDescription(name) {
770
804
  return name.replace(/([A-Z])/g, " $1").replace(/[-_]+/g, " ").trim().toLowerCase();
771
805
  }
@@ -841,6 +875,7 @@ var FastMCP = class {
841
875
  _address = null;
842
876
  _isRunning = false;
843
877
  _sessions = /* @__PURE__ */ new Map();
878
+ _stateless = false;
844
879
  // Primary server used by connect() (in-process transports — always 2025-era)
845
880
  _primaryServer;
846
881
  // The pinned Server instance for a run({transport:'stdio'}) connection (2025- or
@@ -854,6 +889,7 @@ var FastMCP = class {
854
889
  // Modern (2026-07-28) HTTP handler — one per FastMCP instance, lazily created.
855
890
  // Builds a fresh Server (via _makeServer) per request; see createMcpHandler.
856
891
  _modernHandler = null;
892
+ _statelessLegacyHandler = null;
857
893
  _toolRegisteredCallbacks = [];
858
894
  _resourceRegisteredCallbacks = [];
859
895
  _promptRegisteredCallbacks = [];
@@ -912,6 +948,11 @@ var FastMCP = class {
912
948
  * client here, so nothing needs the era fork the way `subscribe` does. The
913
949
  * SDK also requires this capability to be present to register the
914
950
  * `completion/complete` handler (`assertRequestHandlerCapability`).
951
+ *
952
+ * `stateless` withdraws the same capability for a different reason. A
953
+ * stateless server has no session for a subscription to live in and no
954
+ * channel to deliver `notifications/resources/updated` on, so it must not
955
+ * claim the capability.
915
956
  */
916
957
  _makeServer(sessionState, opts) {
917
958
  const state = sessionState ?? this._primaryState;
@@ -919,23 +960,23 @@ var FastMCP = class {
919
960
  const server = new Server2(
920
961
  { name: this.name, version: this.version },
921
962
  {
922
- capabilities: { tools: { listChanged: true }, resources: { listChanged: true, ...opts?.modern ? {} : { subscribe: true } }, prompts: { listChanged: true }, logging: {}, completions: {}, ...extensions ? { extensions } : {} },
963
+ capabilities: { tools: { listChanged: true }, resources: { listChanged: true, ...opts?.modern || opts?.stateless ? {} : { subscribe: true } }, prompts: { listChanged: true }, logging: {}, completions: {}, ...extensions ? { extensions } : {} },
923
964
  ...this._cacheHints ? { cacheHints: this._cacheHints } : {},
924
965
  ...this._requestStateCodec ? { requestState: { verify: this._requestStateCodec.verify.bind(this._requestStateCodec) } } : {},
925
966
  ...this._inputRequiredOptions ? { inputRequired: this._inputRequiredOptions } : {}
926
967
  }
927
968
  );
928
969
  for (const mw of this._middleware) mw.setup?.(server);
929
- this._setupHandlers(server, state);
970
+ this._setupHandlers(server, state, opts);
930
971
  return server;
931
972
  }
932
973
  async _resolveToken(authInfo) {
933
974
  return toAccessToken(authInfo) ?? await resolveCliEnvToken(this._auth);
934
975
  }
935
- _setupHandlers(server, sessionState) {
976
+ _setupHandlers(server, sessionState, opts) {
936
977
  server.setRequestHandler("tools/list", async (req, sdkCtx) => {
937
978
  const token = await this._resolveToken(sdkCtx.http?.authInfo);
938
- const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec);
979
+ const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless);
939
980
  return contextStore.run(
940
981
  ctx,
941
982
  () => runMiddlewareChain(this._middleware, "tools/list", req.params, ctx, async () => {
@@ -1014,7 +1055,7 @@ var FastMCP = class {
1014
1055
  const synthTool = synthesizedList.find((s) => s.name === requestedName);
1015
1056
  if (synthTool) {
1016
1057
  if (synthTool.auth) await runAuthCheck(synthTool.auth, token);
1017
- const ctx2 = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec);
1058
+ const ctx2 = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless);
1018
1059
  try {
1019
1060
  return await contextStore.run(
1020
1061
  ctx2,
@@ -1035,7 +1076,7 @@ var FastMCP = class {
1035
1076
  () => clearTimeout(timer)
1036
1077
  );
1037
1078
  }
1038
- return convertResult(await executePromise);
1079
+ return convertResult(await executePromise, opts?.stateless);
1039
1080
  })
1040
1081
  );
1041
1082
  } catch (err) {
@@ -1067,7 +1108,7 @@ var FastMCP = class {
1067
1108
  if (tool.config.auth) await runAuthCheck(tool.config.auth, token);
1068
1109
  const resolvedTool = tool;
1069
1110
  const rawArgs = req.params.arguments ?? {};
1070
- const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec);
1111
+ const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless);
1071
1112
  try {
1072
1113
  return await contextStore.run(
1073
1114
  ctx,
@@ -1089,7 +1130,7 @@ var FastMCP = class {
1089
1130
  }
1090
1131
  let resultValue = await executePromise;
1091
1132
  if (resolvedTool.config.output) resultValue = await validateInput(resolvedTool.config.output, resultValue);
1092
- const callResult = convertResult(resultValue);
1133
+ const callResult = convertResult(resultValue, opts?.stateless);
1093
1134
  if (resolvedTool.config.ui) {
1094
1135
  const clientIsUi = isUiCapable(server.getClientCapabilities());
1095
1136
  if (!clientIsUi && callResult.structuredContent !== void 0) {
@@ -1109,7 +1150,7 @@ var FastMCP = class {
1109
1150
  });
1110
1151
  server.setRequestHandler("resources/list", async (req, sdkCtx) => {
1111
1152
  const token = await this._resolveToken(sdkCtx.http?.authInfo);
1112
- const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec);
1153
+ const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless);
1113
1154
  return contextStore.run(
1114
1155
  ctx,
1115
1156
  () => runMiddlewareChain(this._middleware, "resources/list", req.params, ctx, async () => {
@@ -1156,7 +1197,7 @@ var FastMCP = class {
1156
1197
  });
1157
1198
  server.setRequestHandler("resources/templates/list", async (req, sdkCtx) => {
1158
1199
  const token = await this._resolveToken(sdkCtx.http?.authInfo);
1159
- const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec);
1200
+ const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless);
1160
1201
  return contextStore.run(
1161
1202
  ctx,
1162
1203
  () => runMiddlewareChain(this._middleware, "resources/templates/list", req.params, ctx, async () => {
@@ -1208,7 +1249,7 @@ var FastMCP = class {
1208
1249
  const resource = resolved.resource;
1209
1250
  const templateParams = resolved.templateParams;
1210
1251
  if (resource.config.auth) await runAuthCheck(resource.config.auth, token);
1211
- const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec);
1252
+ const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless);
1212
1253
  return contextStore.run(
1213
1254
  ctx,
1214
1255
  () => runMiddlewareChain(this._middleware, "resources/read", req.params, ctx, async () => {
@@ -1227,11 +1268,12 @@ var FastMCP = class {
1227
1268
  );
1228
1269
  }
1229
1270
  const result = await executePromise;
1230
- return convertResourceResult(result, requestedUri, resource.config.mimeType);
1271
+ return convertResourceResult(result, requestedUri, resource.config.mimeType, opts?.stateless);
1231
1272
  })
1232
1273
  );
1233
1274
  });
1234
1275
  server.setRequestHandler("resources/subscribe", async (req, sdkCtx) => {
1276
+ if (opts?.stateless) throw new ProtocolError3(ProtocolErrorCode3.MethodNotFound, STATELESS_SUBSCRIBE_ERROR);
1235
1277
  const uri = req.params.uri;
1236
1278
  const token = await this._resolveToken(sdkCtx.http?.authInfo);
1237
1279
  const resolved = this._resolveResource(uri);
@@ -1240,7 +1282,7 @@ var FastMCP = class {
1240
1282
  }
1241
1283
  const resource = resolved.resource;
1242
1284
  if (resource.config.auth) await runAuthCheck(resource.config.auth, token);
1243
- const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec);
1285
+ const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless);
1244
1286
  return contextStore.run(
1245
1287
  ctx,
1246
1288
  () => runMiddlewareChain(this._middleware, "resources/subscribe", req.params, ctx, async () => {
@@ -1255,9 +1297,10 @@ var FastMCP = class {
1255
1297
  );
1256
1298
  });
1257
1299
  server.setRequestHandler("resources/unsubscribe", async (req, sdkCtx) => {
1300
+ if (opts?.stateless) throw new ProtocolError3(ProtocolErrorCode3.MethodNotFound, STATELESS_SUBSCRIBE_ERROR);
1258
1301
  const uri = req.params.uri;
1259
1302
  const token = await this._resolveToken(sdkCtx.http?.authInfo);
1260
- const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec);
1303
+ const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless);
1261
1304
  return contextStore.run(
1262
1305
  ctx,
1263
1306
  () => runMiddlewareChain(this._middleware, "resources/unsubscribe", req.params, ctx, async () => {
@@ -1269,7 +1312,7 @@ var FastMCP = class {
1269
1312
  });
1270
1313
  server.setRequestHandler("prompts/list", async (req, sdkCtx) => {
1271
1314
  const token = await this._resolveToken(sdkCtx.http?.authInfo);
1272
- const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec);
1315
+ const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless);
1273
1316
  return contextStore.run(
1274
1317
  ctx,
1275
1318
  () => runMiddlewareChain(this._middleware, "prompts/list", req.params, ctx, async () => {
@@ -1349,7 +1392,7 @@ var FastMCP = class {
1349
1392
  );
1350
1393
  }
1351
1394
  }
1352
- const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec);
1395
+ const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless);
1353
1396
  return contextStore.run(
1354
1397
  ctx,
1355
1398
  () => runMiddlewareChain(this._middleware, "prompts/get", req.params, ctx, async () => {
@@ -1367,13 +1410,13 @@ var FastMCP = class {
1367
1410
  () => clearTimeout(timer)
1368
1411
  );
1369
1412
  }
1370
- return convertPromptResult(await executePromise);
1413
+ return convertPromptResult(await executePromise, opts?.stateless);
1371
1414
  })
1372
1415
  );
1373
1416
  });
1374
1417
  server.setRequestHandler("completion/complete", async (req, sdkCtx) => {
1375
1418
  const token = await this._resolveToken(sdkCtx.http?.authInfo);
1376
- const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec);
1419
+ const ctx = createContext(server, sdkCtx, token, sessionState, this._requestStateCodec, opts?.stateless);
1377
1420
  return contextStore.run(
1378
1421
  ctx,
1379
1422
  () => runMiddlewareChain(this._middleware, "completion/complete", req.params, ctx, async () => {
@@ -1995,6 +2038,7 @@ var FastMCP = class {
1995
2038
  const port = options?.port ?? parseInt(process.env.MCP_PORT ?? process.env.PORT ?? "3000", 10);
1996
2039
  const host = options?.host ?? process.env.MCP_HOST ?? "127.0.0.1";
1997
2040
  const path = options?.path ?? process.env.MCP_PATH ?? "/mcp";
2041
+ this._stateless = options?.stateless ?? envBool("FASTMCP_STATELESS_HTTP") ?? false;
1998
2042
  if (transport === "stdio") {
1999
2043
  const { StdioServerTransport, serveStdio } = await import("@modelcontextprotocol/server/stdio");
2000
2044
  const stdioTransport = new StdioServerTransport(options?.stdin, options?.stdout);
@@ -2028,6 +2072,24 @@ var FastMCP = class {
2028
2072
  }
2029
2073
  return this._modernHandler;
2030
2074
  }
2075
+ /** Lazily builds the stateless legacy (2025-era) HTTP handler. One per FastMCP
2076
+ * instance, mirroring _getModernHandler's per-instance, per-request-factory model.
2077
+ *
2078
+ * The SDK serves each POST from a fresh instance of the factory over a transport
2079
+ * built with `sessionIdGenerator: undefined`, and answers GET and DELETE with 405
2080
+ * because both are session operations that mean nothing per request.
2081
+ *
2082
+ * Only reachable when `_stateless` is on. The sessionful transport in
2083
+ * _dispatchLegacyHttp is untouched and still serves every other deployment. */
2084
+ _getStatelessLegacyHandler() {
2085
+ if (!this._statelessLegacyHandler) {
2086
+ this._statelessLegacyHandler = legacyStatelessFallback(
2087
+ () => this._makeServer(/* @__PURE__ */ new Map(), { stateless: true }),
2088
+ (error) => console.error("[fastmcp] stateless legacy serving failed:", error)
2089
+ );
2090
+ }
2091
+ return this._statelessLegacyHandler;
2092
+ }
2031
2093
  /**
2032
2094
  * Dual-era HTTP dispatch shared by the OAuth and non-OAuth serve paths. Assumes CORS
2033
2095
  * and auth have already been handled by the caller (req.auth already set, if any —
@@ -2045,7 +2107,11 @@ var FastMCP = class {
2045
2107
  const legacy = await isLegacyRequest(request);
2046
2108
  const parsedBody = req.method === "POST" ? await request.json().catch(() => void 0) : void 0;
2047
2109
  if (legacy) {
2048
- await this._dispatchLegacyHttp(req, res, parsedBody);
2110
+ if (this._stateless) {
2111
+ await toNodeHandler({ fetch: this._getStatelessLegacyHandler() })(req, res, parsedBody);
2112
+ } else {
2113
+ await this._dispatchLegacyHttp(req, res, parsedBody);
2114
+ }
2049
2115
  } else {
2050
2116
  await toNodeHandler(this._getModernHandler())(req, res, parsedBody);
2051
2117
  }
@@ -2218,6 +2284,7 @@ var FastMCP = class {
2218
2284
  await this._modernHandler.close();
2219
2285
  this._modernHandler = null;
2220
2286
  }
2287
+ this._statelessLegacyHandler = null;
2221
2288
  if (this._stdioHandle) {
2222
2289
  await this._stdioHandle.close();
2223
2290
  this._stdioHandle = null;