fastmcp 4.13.1 → 4.14.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/README.md CHANGED
@@ -903,6 +903,52 @@ By default, ping behavior is optimized for each transport type:
903
903
 
904
904
  This configurable approach helps reduce log verbosity and optimize performance for different usage scenarios.
905
905
 
906
+ #### Keeping Long Tool Calls Alive (`streamKeepalive`)
907
+
908
+ Pings travel on the standalone server-to-client stream, so they never keep an
909
+ individual tool call's connection open — and in `stateless` mode that stream does
910
+ not exist at all. A tool that runs for minutes without producing output leaves
911
+ its response connection silent, and an idle-connection timeout in front of the
912
+ server (AWS ALB defaults to 60 seconds) closes it before the result is written.
913
+
914
+ This applies to **both** session modes. A stateful deployment keeps its
915
+ standalone stream warm with pings while the connection carrying the tool call
916
+ goes idle and is closed anyway; stateless has no standalone stream to begin
917
+ with.
918
+
919
+ `streamKeepalive` writes periodically to the in-flight tool call's own response
920
+ stream, which is the connection that would otherwise be closed. It is opt-in:
921
+
922
+ ```ts
923
+ const server = new FastMCP({
924
+ name: "My Server",
925
+ version: "1.0.0",
926
+ streamKeepalive: {
927
+ // Opt in; disabled by default
928
+ enabled: true,
929
+ // Keep comfortably below the shortest idle timeout on the path
930
+ intervalMs: 20000,
931
+ },
932
+ });
933
+ ```
934
+
935
+ Each keepalive is a `notifications/message` (level configurable via `logLevel`,
936
+ default `debug`) tagged with the logger `fastmcp-keepalive`, related to the tool
937
+ call being served. Keepalives start when a tool begins executing and stop when
938
+ the request stops waiting for it — on success, error, timeout, or client
939
+ cancellation — so idle connections stay quiet. Because the messages are related
940
+ to the request, this works in stateful and `stateless` mode alike, and needs no
941
+ client support beyond the standard logging notification.
942
+
943
+ Limits worth knowing:
944
+
945
+ - It has no effect with `httpStream.enableJsonResponse`, which buffers a single
946
+ JSON reply instead of streaming, so there is no open stream to write to.
947
+ - It only covers tool calls. Other long-running requests (`resources/read`,
948
+ `prompts/get`) still write nothing until they finish.
949
+ - On `stdio` there is no proxy to keep alive, so enabling it there only adds
950
+ notification traffic.
951
+
906
952
  ### Health-check Endpoint
907
953
 
908
954
  When you run FastMCP with the `httpStream` transport you can optionally expose a
package/dist/FastMCP.cjs CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
 
13
13
 
14
- var _chunkO72VCMGVcjs = require('./chunk-O72VCMGV.cjs');
14
+ var _chunkXK5NJ5W2cjs = require('./chunk-XK5NJ5W2.cjs');
15
15
 
16
16
 
17
17
 
@@ -49,5 +49,5 @@ var _chunk5FVNY65Mcjs = require('./chunk-5FVNY65M.cjs');
49
49
 
50
50
 
51
51
 
52
- exports.AuthProvider = _chunk5FVNY65Mcjs.AuthProvider; exports.AzureProvider = _chunk5FVNY65Mcjs.AzureProvider; exports.DiscoveryDocumentCache = _chunkO72VCMGVcjs.DiscoveryDocumentCache; exports.FastMCP = _chunkO72VCMGVcjs.FastMCP; exports.FastMCPError = _chunkO72VCMGVcjs.FastMCPError; exports.FastMCPSession = _chunkO72VCMGVcjs.FastMCPSession; exports.GitHubProvider = _chunk5FVNY65Mcjs.GitHubProvider; exports.GoogleProvider = _chunk5FVNY65Mcjs.GoogleProvider; exports.MEDIA_FETCH_TIMEOUT_MS = _chunkO72VCMGVcjs.MEDIA_FETCH_TIMEOUT_MS; exports.OAuthProvider = _chunk5FVNY65Mcjs.OAuthProvider; exports.ServerState = _chunkO72VCMGVcjs.ServerState; exports.SessionError = _chunkO72VCMGVcjs.SessionError; exports.UnexpectedStateError = _chunkO72VCMGVcjs.UnexpectedStateError; exports.UserError = _chunkO72VCMGVcjs.UserError; exports.audioContent = _chunkO72VCMGVcjs.audioContent; exports.getAuthSession = _chunk5FVNY65Mcjs.getAuthSession; exports.imageContent = _chunkO72VCMGVcjs.imageContent; exports.jsonSchemaAdapter = _chunkO72VCMGVcjs.jsonSchemaAdapter; exports.requireAll = _chunk5FVNY65Mcjs.requireAll; exports.requireAny = _chunk5FVNY65Mcjs.requireAny; exports.requireAuth = _chunk5FVNY65Mcjs.requireAuth; exports.requireRole = _chunk5FVNY65Mcjs.requireRole; exports.requireScopes = _chunk5FVNY65Mcjs.requireScopes;
52
+ exports.AuthProvider = _chunk5FVNY65Mcjs.AuthProvider; exports.AzureProvider = _chunk5FVNY65Mcjs.AzureProvider; exports.DiscoveryDocumentCache = _chunkXK5NJ5W2cjs.DiscoveryDocumentCache; exports.FastMCP = _chunkXK5NJ5W2cjs.FastMCP; exports.FastMCPError = _chunkXK5NJ5W2cjs.FastMCPError; exports.FastMCPSession = _chunkXK5NJ5W2cjs.FastMCPSession; exports.GitHubProvider = _chunk5FVNY65Mcjs.GitHubProvider; exports.GoogleProvider = _chunk5FVNY65Mcjs.GoogleProvider; exports.MEDIA_FETCH_TIMEOUT_MS = _chunkXK5NJ5W2cjs.MEDIA_FETCH_TIMEOUT_MS; exports.OAuthProvider = _chunk5FVNY65Mcjs.OAuthProvider; exports.ServerState = _chunkXK5NJ5W2cjs.ServerState; exports.SessionError = _chunkXK5NJ5W2cjs.SessionError; exports.UnexpectedStateError = _chunkXK5NJ5W2cjs.UnexpectedStateError; exports.UserError = _chunkXK5NJ5W2cjs.UserError; exports.audioContent = _chunkXK5NJ5W2cjs.audioContent; exports.getAuthSession = _chunk5FVNY65Mcjs.getAuthSession; exports.imageContent = _chunkXK5NJ5W2cjs.imageContent; exports.jsonSchemaAdapter = _chunkXK5NJ5W2cjs.jsonSchemaAdapter; exports.requireAll = _chunk5FVNY65Mcjs.requireAll; exports.requireAny = _chunk5FVNY65Mcjs.requireAny; exports.requireAuth = _chunk5FVNY65Mcjs.requireAuth; exports.requireRole = _chunk5FVNY65Mcjs.requireRole; exports.requireScopes = _chunk5FVNY65Mcjs.requireScopes;
53
53
  //# sourceMappingURL=FastMCP.cjs.map
@@ -697,6 +697,36 @@ type ServerOptions<T extends FastMCPSessionAuth> = {
697
697
  */
698
698
  enabled?: boolean;
699
699
  };
700
+ /**
701
+ * Writes periodically to an in-flight tool call's own response stream, so a
702
+ * proxy or load balancer does not close the connection as idle while a
703
+ * long-running tool produces no output.
704
+ *
705
+ * Unlike {@link ServerOptions.ping}, these messages are related to the
706
+ * request being served, so they travel on that request's stream instead of
707
+ * the standalone server-to-client stream. That makes them the only option
708
+ * that works with `httpStream.stateless`, where no standing server-to-client
709
+ * stream exists.
710
+ */
711
+ streamKeepalive?: {
712
+ /**
713
+ * Whether to write keepalives. Opt-in.
714
+ * @default false
715
+ */
716
+ enabled?: boolean;
717
+ /**
718
+ * Interval between keepalives. Keep it comfortably below the shortest idle
719
+ * timeout on the path (AWS ALB defaults to 60s). Values below 1ms fall back
720
+ * to the default.
721
+ * @default 20000 (20s)
722
+ */
723
+ intervalMs?: number;
724
+ /**
725
+ * Level reported on the keepalive notification.
726
+ * @default 'debug'
727
+ */
728
+ logLevel?: LoggingLevel;
729
+ };
700
730
  /**
701
731
  * General utilities
702
732
  */
@@ -840,7 +870,7 @@ declare class FastMCPSession<T extends FastMCPSessionAuth = FastMCPSessionAuth>
840
870
  get server(): Server;
841
871
  get sessionId(): string | undefined;
842
872
  set sessionId(value: string | undefined);
843
- constructor({ auth, instructions, logger, name, onToolCall, ping, prompts, resources, resourcesTemplates, roots, sessionId, stateless, tools, transportType, utils, version, }: {
873
+ constructor({ auth, instructions, logger, name, onToolCall, ping, prompts, resources, resourcesTemplates, roots, sessionId, stateless, streamKeepalive, tools, transportType, utils, version, }: {
844
874
  auth?: T;
845
875
  instructions?: string;
846
876
  logger: Logger;
@@ -853,6 +883,7 @@ declare class FastMCPSession<T extends FastMCPSessionAuth = FastMCPSessionAuth>
853
883
  roots?: ServerOptions<T>["roots"];
854
884
  sessionId?: string;
855
885
  stateless?: boolean;
886
+ streamKeepalive?: ServerOptions<T>["streamKeepalive"];
856
887
  tools: Tool<T>[];
857
888
  transportType?: "httpStream" | "stdio";
858
889
  utils?: ServerOptions<T>["utils"];
package/dist/FastMCP.d.ts CHANGED
@@ -697,6 +697,36 @@ type ServerOptions<T extends FastMCPSessionAuth> = {
697
697
  */
698
698
  enabled?: boolean;
699
699
  };
700
+ /**
701
+ * Writes periodically to an in-flight tool call's own response stream, so a
702
+ * proxy or load balancer does not close the connection as idle while a
703
+ * long-running tool produces no output.
704
+ *
705
+ * Unlike {@link ServerOptions.ping}, these messages are related to the
706
+ * request being served, so they travel on that request's stream instead of
707
+ * the standalone server-to-client stream. That makes them the only option
708
+ * that works with `httpStream.stateless`, where no standing server-to-client
709
+ * stream exists.
710
+ */
711
+ streamKeepalive?: {
712
+ /**
713
+ * Whether to write keepalives. Opt-in.
714
+ * @default false
715
+ */
716
+ enabled?: boolean;
717
+ /**
718
+ * Interval between keepalives. Keep it comfortably below the shortest idle
719
+ * timeout on the path (AWS ALB defaults to 60s). Values below 1ms fall back
720
+ * to the default.
721
+ * @default 20000 (20s)
722
+ */
723
+ intervalMs?: number;
724
+ /**
725
+ * Level reported on the keepalive notification.
726
+ * @default 'debug'
727
+ */
728
+ logLevel?: LoggingLevel;
729
+ };
700
730
  /**
701
731
  * General utilities
702
732
  */
@@ -840,7 +870,7 @@ declare class FastMCPSession<T extends FastMCPSessionAuth = FastMCPSessionAuth>
840
870
  get server(): Server;
841
871
  get sessionId(): string | undefined;
842
872
  set sessionId(value: string | undefined);
843
- constructor({ auth, instructions, logger, name, onToolCall, ping, prompts, resources, resourcesTemplates, roots, sessionId, stateless, tools, transportType, utils, version, }: {
873
+ constructor({ auth, instructions, logger, name, onToolCall, ping, prompts, resources, resourcesTemplates, roots, sessionId, stateless, streamKeepalive, tools, transportType, utils, version, }: {
844
874
  auth?: T;
845
875
  instructions?: string;
846
876
  logger: Logger;
@@ -853,6 +883,7 @@ declare class FastMCPSession<T extends FastMCPSessionAuth = FastMCPSessionAuth>
853
883
  roots?: ServerOptions<T>["roots"];
854
884
  sessionId?: string;
855
885
  stateless?: boolean;
886
+ streamKeepalive?: ServerOptions<T>["streamKeepalive"];
856
887
  tools: Tool<T>[];
857
888
  transportType?: "httpStream" | "stdio";
858
889
  utils?: ServerOptions<T>["utils"];
package/dist/FastMCP.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  audioContent,
12
12
  imageContent,
13
13
  jsonSchemaAdapter
14
- } from "./chunk-3BMGHXXY.js";
14
+ } from "./chunk-LMDMFMPI.js";
15
15
  import {
16
16
  AuthProvider,
17
17
  AzureProvider,
@@ -355,6 +355,8 @@ function assertToolSchemas(tool) {
355
355
  assertStandardSchema(tool.name, "outputSchema", tool.outputSchema);
356
356
  }
357
357
  }
358
+ var STREAM_KEEPALIVE_LOGGER = "fastmcp-keepalive";
359
+ var STREAM_KEEPALIVE_DEFAULT_INTERVAL_MS = 2e4;
358
360
  var TextContentZodSchema = z.object({
359
361
  /**
360
362
  * The text content of the message.
@@ -483,6 +485,7 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
483
485
  * instance — so capabilities can never be inferred here.
484
486
  */
485
487
  #stateless;
488
+ #streamKeepaliveConfig;
486
489
  /**
487
490
  * Resource URIs the connected client has subscribed to via
488
491
  * `resources/subscribe`. Used to scope `notifications/resources/updated`
@@ -503,6 +506,7 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
503
506
  roots,
504
507
  sessionId,
505
508
  stateless = false,
509
+ streamKeepalive,
506
510
  tools,
507
511
  transportType,
508
512
  utils,
@@ -516,6 +520,7 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
516
520
  this.#rootsConfig = roots;
517
521
  this.#sessionId = sessionId;
518
522
  this.#stateless = stateless;
523
+ this.#streamKeepaliveConfig = streamKeepalive;
519
524
  this.#needsEventLoopFlush = transportType === "httpStream";
520
525
  if (tools.length) {
521
526
  this.#capabilities.tools = {};
@@ -843,6 +848,44 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
843
848
  logLevel: pingConfig.logLevel || "debug"
844
849
  };
845
850
  }
851
+ /**
852
+ * Periodically writes to the response stream of an in-flight tool call, so an
853
+ * idle-connection timeout (proxy, load balancer) does not close it while a
854
+ * long-running tool produces no output of its own.
855
+ *
856
+ * The notification is related to the tool call, so it travels on that
857
+ * request's own stream, which is the only server-to-client route that exists
858
+ * when running stateless.
859
+ *
860
+ * @returns a function that stops the keepalive.
861
+ */
862
+ #startStreamKeepalive(extra, toolName) {
863
+ const config = this.#streamKeepaliveConfig;
864
+ if (!config?.enabled) {
865
+ return () => {
866
+ };
867
+ }
868
+ const intervalMs = config.intervalMs && config.intervalMs > 0 ? config.intervalMs : STREAM_KEEPALIVE_DEFAULT_INTERVAL_MS;
869
+ const timer = setInterval(() => {
870
+ extra.sendNotification({
871
+ method: "notifications/message",
872
+ params: {
873
+ data: { message: `keepalive while '${toolName}' is running` },
874
+ level: config.logLevel ?? "debug",
875
+ logger: STREAM_KEEPALIVE_LOGGER
876
+ }
877
+ }).catch((error) => {
878
+ this.#logger.debug(
879
+ `[FastMCP debug] stream keepalive for '${toolName}' failed:`,
880
+ error instanceof Error ? error.message : String(error)
881
+ );
882
+ });
883
+ }, intervalMs);
884
+ timer.unref?.();
885
+ const stop = () => clearInterval(timer);
886
+ extra.signal?.addEventListener("abort", stop, { once: true });
887
+ return stop;
888
+ }
846
889
  async #validateStructuredContent(tool, value, toolName) {
847
890
  if (!tool.outputSchema) {
848
891
  return value;
@@ -1260,169 +1303,178 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
1260
1303
  tools: cachedToolsList
1261
1304
  };
1262
1305
  });
1263
- this.#server.setRequestHandler(CallToolRequestSchema, async (request) => {
1264
- const tool = toolsMap.get(request.params.name);
1265
- if (!tool) {
1266
- throw new McpError(
1267
- ErrorCode.MethodNotFound,
1268
- `Unknown tool: ${request.params.name}`
1269
- );
1270
- }
1271
- let args = void 0;
1272
- if (tool.parameters) {
1273
- const parsed = await tool.parameters["~standard"].validate(
1274
- request.params.arguments
1275
- );
1276
- if (parsed.issues) {
1277
- const friendlyErrors = this.#formatSchemaIssues(parsed.issues);
1306
+ this.#server.setRequestHandler(
1307
+ CallToolRequestSchema,
1308
+ async (request, extra) => {
1309
+ const tool = toolsMap.get(request.params.name);
1310
+ if (!tool) {
1278
1311
  throw new McpError(
1279
- ErrorCode.InvalidParams,
1280
- `Tool '${request.params.name}' parameter validation failed: ${friendlyErrors}. Please check the parameter types and values according to the tool's schema.`
1312
+ ErrorCode.MethodNotFound,
1313
+ `Unknown tool: ${request.params.name}`
1281
1314
  );
1282
1315
  }
1283
- args = parsed.value;
1284
- }
1285
- const progressToken = request.params?._meta?.progressToken;
1286
- let result;
1287
- try {
1288
- const reportProgress = async (progress) => {
1289
- if (progressToken === void 0) {
1290
- return;
1316
+ let args = void 0;
1317
+ if (tool.parameters) {
1318
+ const parsed = await tool.parameters["~standard"].validate(
1319
+ request.params.arguments
1320
+ );
1321
+ if (parsed.issues) {
1322
+ const friendlyErrors = this.#formatSchemaIssues(parsed.issues);
1323
+ throw new McpError(
1324
+ ErrorCode.InvalidParams,
1325
+ `Tool '${request.params.name}' parameter validation failed: ${friendlyErrors}. Please check the parameter types and values according to the tool's schema.`
1326
+ );
1291
1327
  }
1292
- try {
1293
- await this.#server.notification({
1294
- method: "notifications/progress",
1295
- params: {
1296
- ...progress,
1297
- progressToken
1328
+ args = parsed.value;
1329
+ }
1330
+ const progressToken = request.params?._meta?.progressToken;
1331
+ let result;
1332
+ try {
1333
+ const reportProgress = async (progress) => {
1334
+ if (progressToken === void 0) {
1335
+ return;
1336
+ }
1337
+ try {
1338
+ await this.#server.notification({
1339
+ method: "notifications/progress",
1340
+ params: {
1341
+ ...progress,
1342
+ progressToken
1343
+ }
1344
+ });
1345
+ if (this.#needsEventLoopFlush) {
1346
+ await new Promise((resolve) => setImmediate(resolve));
1298
1347
  }
1299
- });
1300
- if (this.#needsEventLoopFlush) {
1301
- await new Promise((resolve) => setImmediate(resolve));
1348
+ } catch (progressError) {
1349
+ this.#logger.warn(
1350
+ `[FastMCP warning] Failed to report progress for tool '${request.params.name}':`,
1351
+ progressError instanceof Error ? progressError.message : String(progressError)
1352
+ );
1302
1353
  }
1303
- } catch (progressError) {
1304
- this.#logger.warn(
1305
- `[FastMCP warning] Failed to report progress for tool '${request.params.name}':`,
1306
- progressError instanceof Error ? progressError.message : String(progressError)
1307
- );
1308
- }
1309
- };
1310
- const log = this.#createLog();
1311
- const streamContent = async (content) => {
1312
- const contentArray = Array.isArray(content) ? content : [content];
1313
- try {
1314
- await this.#server.notification({
1315
- method: "notifications/tool/streamContent",
1316
- params: {
1317
- content: contentArray,
1318
- toolName: request.params.name
1354
+ };
1355
+ const log = this.#createLog();
1356
+ const streamContent = async (content) => {
1357
+ const contentArray = Array.isArray(content) ? content : [content];
1358
+ try {
1359
+ await this.#server.notification({
1360
+ method: "notifications/tool/streamContent",
1361
+ params: {
1362
+ content: contentArray,
1363
+ toolName: request.params.name
1364
+ }
1365
+ });
1366
+ if (this.#needsEventLoopFlush) {
1367
+ await new Promise((resolve) => setImmediate(resolve));
1319
1368
  }
1320
- });
1321
- if (this.#needsEventLoopFlush) {
1322
- await new Promise((resolve) => setImmediate(resolve));
1369
+ } catch (streamError) {
1370
+ this.#logger.warn(
1371
+ `[FastMCP warning] Failed to stream content for tool '${request.params.name}':`,
1372
+ streamError instanceof Error ? streamError.message : String(streamError)
1373
+ );
1323
1374
  }
1324
- } catch (streamError) {
1325
- this.#logger.warn(
1326
- `[FastMCP warning] Failed to stream content for tool '${request.params.name}':`,
1327
- streamError instanceof Error ? streamError.message : String(streamError)
1328
- );
1375
+ };
1376
+ if (this.#onToolCall) {
1377
+ await this.#onToolCall({
1378
+ arguments: args ?? {},
1379
+ toolName: request.params.name
1380
+ });
1329
1381
  }
1330
- };
1331
- if (this.#onToolCall) {
1332
- await this.#onToolCall({
1333
- arguments: args ?? {},
1334
- toolName: request.params.name
1335
- });
1336
- }
1337
- const executeToolPromise = tool.execute(args, {
1338
- client: {
1339
- version: this.#server.getClientVersion()
1340
- },
1341
- elicit: (params, options) => this.#server.elicitInput(params, options),
1342
- log,
1343
- reportProgress,
1344
- requestId: typeof request.params?._meta?.requestId === "string" ? request.params._meta.requestId : void 0,
1345
- session: this.#auth,
1346
- sessionId: this.#sessionId,
1347
- streamContent
1348
- });
1349
- const maybeStringResult = await (tool.timeoutMs ? Promise.race([
1350
- executeToolPromise,
1351
- new Promise((_, reject) => {
1352
- const timeoutId = setTimeout(() => {
1353
- reject(
1354
- new UserError(
1355
- `Tool '${request.params.name}' timed out after ${tool.timeoutMs}ms. Consider increasing timeoutMs or optimizing the tool implementation.`
1356
- )
1382
+ const executeToolPromise = Promise.resolve(
1383
+ tool.execute(args, {
1384
+ client: {
1385
+ version: this.#server.getClientVersion()
1386
+ },
1387
+ elicit: (params, options) => this.#server.elicitInput(params, options),
1388
+ log,
1389
+ reportProgress,
1390
+ requestId: typeof request.params?._meta?.requestId === "string" ? request.params._meta.requestId : void 0,
1391
+ session: this.#auth,
1392
+ sessionId: this.#sessionId,
1393
+ streamContent
1394
+ })
1395
+ );
1396
+ const stopStreamKeepalive = this.#startStreamKeepalive(
1397
+ extra,
1398
+ request.params.name
1399
+ );
1400
+ const maybeStringResult = await (tool.timeoutMs ? Promise.race([
1401
+ executeToolPromise,
1402
+ new Promise((_, reject) => {
1403
+ const timeoutId = setTimeout(() => {
1404
+ reject(
1405
+ new UserError(
1406
+ `Tool '${request.params.name}' timed out after ${tool.timeoutMs}ms. Consider increasing timeoutMs or optimizing the tool implementation.`
1407
+ )
1408
+ );
1409
+ }, tool.timeoutMs);
1410
+ executeToolPromise.then(
1411
+ () => clearTimeout(timeoutId),
1412
+ () => clearTimeout(timeoutId)
1357
1413
  );
1358
- }, tool.timeoutMs);
1359
- executeToolPromise.then(
1360
- () => clearTimeout(timeoutId),
1361
- () => clearTimeout(timeoutId)
1362
- );
1363
- })
1364
- ]) : executeToolPromise);
1365
- await delay(1);
1366
- if (maybeStringResult === void 0 || maybeStringResult === null) {
1367
- result = ContentResultZodSchema.parse({
1368
- content: []
1369
- });
1370
- } else if (typeof maybeStringResult === "string") {
1371
- result = ContentResultZodSchema.parse({
1372
- content: [{ text: maybeStringResult, type: "text" }]
1373
- });
1374
- } else if ("type" in maybeStringResult) {
1375
- result = ContentResultZodSchema.parse({
1376
- content: [maybeStringResult]
1377
- });
1378
- } else if ("content" in maybeStringResult) {
1379
- result = ContentResultZodSchema.parse(maybeStringResult);
1380
- if (result.structuredContent !== void 0 && tool.outputSchema) {
1381
- result.structuredContent = await this.#validateStructuredContent(
1414
+ })
1415
+ ]) : executeToolPromise).finally(stopStreamKeepalive);
1416
+ await delay(1);
1417
+ if (maybeStringResult === void 0 || maybeStringResult === null) {
1418
+ result = ContentResultZodSchema.parse({
1419
+ content: []
1420
+ });
1421
+ } else if (typeof maybeStringResult === "string") {
1422
+ result = ContentResultZodSchema.parse({
1423
+ content: [{ text: maybeStringResult, type: "text" }]
1424
+ });
1425
+ } else if ("type" in maybeStringResult) {
1426
+ result = ContentResultZodSchema.parse({
1427
+ content: [maybeStringResult]
1428
+ });
1429
+ } else if ("content" in maybeStringResult) {
1430
+ result = ContentResultZodSchema.parse(maybeStringResult);
1431
+ if (result.structuredContent !== void 0 && tool.outputSchema) {
1432
+ result.structuredContent = await this.#validateStructuredContent(
1433
+ tool,
1434
+ result.structuredContent,
1435
+ request.params.name
1436
+ );
1437
+ }
1438
+ } else if (tool.outputSchema) {
1439
+ const structuredContent = await this.#validateStructuredContent(
1382
1440
  tool,
1383
- result.structuredContent,
1441
+ maybeStringResult,
1384
1442
  request.params.name
1385
1443
  );
1444
+ result = ContentResultZodSchema.parse({
1445
+ content: [
1446
+ {
1447
+ text: JSON.stringify(structuredContent),
1448
+ type: "text"
1449
+ }
1450
+ ],
1451
+ structuredContent
1452
+ });
1453
+ } else {
1454
+ result = ContentResultZodSchema.parse(maybeStringResult);
1386
1455
  }
1387
- } else if (tool.outputSchema) {
1388
- const structuredContent = await this.#validateStructuredContent(
1389
- tool,
1390
- maybeStringResult,
1391
- request.params.name
1392
- );
1393
- result = ContentResultZodSchema.parse({
1456
+ } catch (error) {
1457
+ if (error instanceof UserError) {
1458
+ return {
1459
+ content: [{ text: error.message, type: "text" }],
1460
+ isError: true,
1461
+ ...error.extras ? { structuredContent: error.extras } : {}
1462
+ };
1463
+ }
1464
+ const errorMessage = error instanceof Error ? error.message : String(error);
1465
+ return {
1394
1466
  content: [
1395
1467
  {
1396
- text: JSON.stringify(structuredContent),
1468
+ text: `Tool '${request.params.name}' execution failed: ${errorMessage}`,
1397
1469
  type: "text"
1398
1470
  }
1399
1471
  ],
1400
- structuredContent
1401
- });
1402
- } else {
1403
- result = ContentResultZodSchema.parse(maybeStringResult);
1404
- }
1405
- } catch (error) {
1406
- if (error instanceof UserError) {
1407
- return {
1408
- content: [{ text: error.message, type: "text" }],
1409
- isError: true,
1410
- ...error.extras ? { structuredContent: error.extras } : {}
1472
+ isError: true
1411
1473
  };
1412
1474
  }
1413
- const errorMessage = error instanceof Error ? error.message : String(error);
1414
- return {
1415
- content: [
1416
- {
1417
- text: `Tool '${request.params.name}' execution failed: ${errorMessage}`,
1418
- type: "text"
1419
- }
1420
- ],
1421
- isError: true
1422
- };
1475
+ return result;
1423
1476
  }
1424
- return result;
1425
- });
1477
+ );
1426
1478
  }
1427
1479
  };
1428
1480
  function camelToSnakeCase(str) {
@@ -1863,6 +1915,7 @@ var FastMCP = class extends FastMCPEventEmitter {
1863
1915
  resources: this.#resources,
1864
1916
  resourcesTemplates: this.#resourcesTemplates,
1865
1917
  roots: this.#options.roots,
1918
+ streamKeepalive: this.#options.streamKeepalive,
1866
1919
  tools: this.#tools,
1867
1920
  transportType: "stdio",
1868
1921
  utils: this.#options.utils,
@@ -2062,6 +2115,7 @@ var FastMCP = class extends FastMCPEventEmitter {
2062
2115
  roots: this.#options.roots,
2063
2116
  sessionId,
2064
2117
  stateless,
2118
+ streamKeepalive: this.#options.streamKeepalive,
2065
2119
  tools: allowedTools,
2066
2120
  transportType: "httpStream",
2067
2121
  utils: this.#options.utils,
@@ -2676,4 +2730,4 @@ export {
2676
2730
  FastMCPSession,
2677
2731
  FastMCP
2678
2732
  };
2679
- //# sourceMappingURL=chunk-3BMGHXXY.js.map
2733
+ //# sourceMappingURL=chunk-LMDMFMPI.js.map