fastmcp 1.27.7 → 2.1.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/src/FastMCP.ts CHANGED
@@ -25,7 +25,7 @@ import { fileTypeFromBuffer } from "file-type";
25
25
  import { readFile } from "fs/promises";
26
26
  import Fuse from "fuse.js";
27
27
  import http from "http";
28
- import { startHTTPStreamServer, startSSEServer } from "mcp-proxy";
28
+ import { startHTTPServer } from "mcp-proxy";
29
29
  import { StrictEventEmitter } from "strict-event-emitter-types";
30
30
  import { setTimeout as delay } from "timers/promises";
31
31
  import { fetch } from "undici";
@@ -180,6 +180,7 @@ type Context<T extends FastMCPSessionAuth> = {
180
180
  };
181
181
  reportProgress: (progress: Progress) => Promise<void>;
182
182
  session: T | undefined;
183
+ streamContent: (content: Content | Content[]) => Promise<void>;
183
184
  };
184
185
 
185
186
  type Extra = unknown;
@@ -445,8 +446,44 @@ type ResourceTemplateArgumentsToObject<T extends { name: string }[]> = {
445
446
 
446
447
  type ServerOptions<T extends FastMCPSessionAuth> = {
447
448
  authenticate?: Authenticate<T>;
449
+ /**
450
+ * Configuration for the health-check endpoint that can be exposed when the
451
+ * server is running using the HTTP Stream transport. When enabled, the
452
+ * server will respond to an HTTP GET request with the configured path (by
453
+ * default "/health") rendering a plain-text response (by default "ok") and
454
+ * the configured status code (by default 200).
455
+ *
456
+ * The endpoint is only added when the server is started with
457
+ * `transportType: "httpStream"` – it is ignored for the stdio transport.
458
+ */
459
+ health?: {
460
+ /**
461
+ * When set to `false` the health-check endpoint is disabled.
462
+ * @default true
463
+ */
464
+ enabled?: boolean;
465
+
466
+ /**
467
+ * Plain-text body returned by the endpoint.
468
+ * @default "ok"
469
+ */
470
+ message?: string;
471
+
472
+ /**
473
+ * HTTP path that should be handled.
474
+ * @default "/health"
475
+ */
476
+ path?: string;
477
+
478
+ /**
479
+ * HTTP response status that will be returned.
480
+ * @default 200
481
+ */
482
+ status?: number;
483
+ };
448
484
  instructions?: string;
449
485
  name: string;
486
+
450
487
  ping?: {
451
488
  /**
452
489
  * Whether ping should be enabled by default.
@@ -483,13 +520,19 @@ type Tool<
483
520
  T extends FastMCPSessionAuth,
484
521
  Params extends ToolParameters = ToolParameters,
485
522
  > = {
486
- annotations?: ToolAnnotations;
523
+ annotations?: {
524
+ /**
525
+ * When true, the tool leverages incremental content streaming
526
+ * Return void for tools that handle all their output via streaming
527
+ */
528
+ streamingHint?: boolean;
529
+ } & ToolAnnotations;
487
530
  description?: string;
488
531
  execute: (
489
532
  args: StandardSchemaV1.InferOutput<Params>,
490
533
  context: Context<T>,
491
534
  ) => Promise<
492
- AudioContent | ContentResult | ImageContent | string | TextContent
535
+ AudioContent | ContentResult | ImageContent | string | TextContent | void
493
536
  >;
494
537
  name: string;
495
538
  parameters?: Params;
@@ -767,7 +810,7 @@ export class FastMCPSession<
767
810
 
768
811
  if ("type" in transport) {
769
812
  // Enable by default for SSE and HTTP streaming
770
- if (transport.type === "sse" || transport.type === "httpStream") {
813
+ if (transport.type === "httpStream") {
771
814
  defaultEnabled = true;
772
815
  }
773
816
  }
@@ -1266,14 +1309,29 @@ export class FastMCPSession<
1266
1309
  };
1267
1310
 
1268
1311
  // Create a promise for tool execution
1312
+ // Streams partial results while a tool is still executing
1313
+ // Enables progressive rendering and real-time feedback
1314
+ const streamContent = async (content: Content | Content[]) => {
1315
+ const contentArray = Array.isArray(content) ? content : [content];
1316
+
1317
+ await this.#server.notification({
1318
+ method: "notifications/tool/streamContent",
1319
+ params: {
1320
+ content: contentArray,
1321
+ toolName: request.params.name,
1322
+ },
1323
+ });
1324
+ };
1325
+
1269
1326
  const executeToolPromise = tool.execute(args, {
1270
1327
  log,
1271
1328
  reportProgress,
1272
1329
  session: this.#auth,
1330
+ streamContent,
1273
1331
  });
1274
1332
 
1275
1333
  // Handle timeout if specified
1276
- const maybeStringResult = await (tool.timeoutMs
1334
+ const maybeStringResult = (await (tool.timeoutMs
1277
1335
  ? Promise.race([
1278
1336
  executeToolPromise,
1279
1337
  new Promise<never>((_, reject) => {
@@ -1286,9 +1344,20 @@ export class FastMCPSession<
1286
1344
  }, tool.timeoutMs);
1287
1345
  }),
1288
1346
  ])
1289
- : executeToolPromise);
1290
-
1291
- if (typeof maybeStringResult === "string") {
1347
+ : executeToolPromise)) as
1348
+ | AudioContent
1349
+ | ContentResult
1350
+ | ImageContent
1351
+ | null
1352
+ | string
1353
+ | TextContent
1354
+ | undefined;
1355
+
1356
+ if (maybeStringResult === undefined || maybeStringResult === null) {
1357
+ result = ContentResultZodSchema.parse({
1358
+ content: [],
1359
+ });
1360
+ } else if (typeof maybeStringResult === "string") {
1292
1361
  result = ContentResultZodSchema.parse({
1293
1362
  content: [{ text: maybeStringResult, type: "text" }],
1294
1363
  });
@@ -1339,7 +1408,6 @@ export class FastMCP<
1339
1408
  #resources: Resource[] = [];
1340
1409
  #resourcesTemplates: InputResourceTemplate[] = [];
1341
1410
  #sessions: FastMCPSession<T>[] = [];
1342
- #sseServer: null | SSEServer = null;
1343
1411
 
1344
1412
  #tools: Tool<T>[] = [];
1345
1413
 
@@ -1388,13 +1456,9 @@ export class FastMCP<
1388
1456
  public async start(
1389
1457
  options:
1390
1458
  | {
1391
- httpStream: { endpoint: `/${string}`; port: number };
1459
+ httpStream: { port: number };
1392
1460
  transportType: "httpStream";
1393
1461
  }
1394
- | {
1395
- sse: { endpoint: `/${string}`; port: number };
1396
- transportType: "sse";
1397
- }
1398
1462
  | { transportType: "stdio" } = {
1399
1463
  transportType: "stdio",
1400
1464
  },
@@ -1421,8 +1485,8 @@ export class FastMCP<
1421
1485
  this.emit("connect", {
1422
1486
  session,
1423
1487
  });
1424
- } else if (options.transportType === "sse") {
1425
- this.#sseServer = await startSSEServer<FastMCPSession<T>>({
1488
+ } else if (options.transportType === "httpStream") {
1489
+ this.#httpStreamServer = await startHTTPServer<FastMCPSession<T>>({
1426
1490
  createServer: async (request) => {
1427
1491
  let auth: T | undefined;
1428
1492
 
@@ -1442,7 +1506,6 @@ export class FastMCP<
1442
1506
  version: this.#options.version,
1443
1507
  });
1444
1508
  },
1445
- endpoint: options.sse.endpoint as `/${string}`,
1446
1509
  onClose: (session) => {
1447
1510
  this.emit("disconnect", {
1448
1511
  session,
@@ -1455,51 +1518,41 @@ export class FastMCP<
1455
1518
  session,
1456
1519
  });
1457
1520
  },
1458
- port: options.sse.port,
1459
- });
1521
+ onUnhandledRequest: async (req, res) => {
1522
+ const healthConfig = this.#options.health ?? {};
1460
1523
 
1461
- console.info(
1462
- `[FastMCP info] server is running on SSE at http://localhost:${options.sse.port}${options.sse.endpoint}`,
1463
- );
1464
- } else if (options.transportType === "httpStream") {
1465
- this.#httpStreamServer = await startHTTPStreamServer<FastMCPSession<T>>({
1466
- createServer: async (request) => {
1467
- let auth: T | undefined;
1524
+ const enabled =
1525
+ healthConfig.enabled === undefined ? true : healthConfig.enabled;
1468
1526
 
1469
- if (this.#authenticate) {
1470
- auth = await this.#authenticate(request);
1471
- }
1527
+ if (enabled) {
1528
+ const path = healthConfig.path ?? "/health";
1472
1529
 
1473
- return new FastMCPSession<T>({
1474
- auth,
1475
- name: this.#options.name,
1476
- ping: this.#options.ping,
1477
- prompts: this.#prompts,
1478
- resources: this.#resources,
1479
- resourcesTemplates: this.#resourcesTemplates,
1480
- roots: this.#options.roots,
1481
- tools: this.#tools,
1482
- version: this.#options.version,
1483
- });
1484
- },
1485
- endpoint: options.httpStream.endpoint as `/${string}`,
1486
- onClose: (session) => {
1487
- this.emit("disconnect", {
1488
- session,
1489
- });
1490
- },
1491
- onConnect: async (session) => {
1492
- this.#sessions.push(session);
1530
+ try {
1531
+ if (
1532
+ req.method === "GET" &&
1533
+ new URL(req.url || "", "http://localhost").pathname === path
1534
+ ) {
1535
+ res
1536
+ .writeHead(healthConfig.status ?? 200, {
1537
+ "Content-Type": "text/plain",
1538
+ })
1539
+ .end(healthConfig.message ?? "ok");
1493
1540
 
1494
- this.emit("connect", {
1495
- session,
1496
- });
1541
+ return;
1542
+ }
1543
+ } catch (error) {
1544
+ console.error("[FastMCP error] health endpoint error", error);
1545
+ }
1546
+ }
1547
+
1548
+ // If the request was not handled above, return 404
1549
+ res.writeHead(404).end();
1497
1550
  },
1498
1551
  port: options.httpStream.port,
1499
1552
  });
1500
1553
 
1501
1554
  console.info(
1502
- `[FastMCP info] server is running on HTTP Stream at http://localhost:${options.httpStream.port}${options.httpStream.endpoint}`,
1555
+ `[FastMCP info] server is running on HTTP Stream at http://localhost:${options.httpStream.port}/stream`,
1503
1556
  );
1504
1557
  } else {
1505
1558
  throw new Error("Invalid transport type");
@@ -1510,9 +1563,6 @@ export class FastMCP<
1510
1563
  * Stops the server.
1511
1564
  */
1512
1565
  public async stop() {
1513
- if (this.#sseServer) {
1514
- await this.#sseServer.close();
1515
- }
1516
1566
  if (this.#httpStreamServer) {
1517
1567
  await this.#httpStreamServer.close();
1518
1568
  }
@@ -1,8 +1,12 @@
1
1
  /**
2
- * This is an example of a FastMCP server that adds two numbers.
2
+ * Example FastMCP server demonstrating core functionality plus streaming output.
3
3
  *
4
- * If you are looking for a complete example of an MCP server repository,
5
- * see https://github.com/punkpeye/fastmcp-boilerplate
4
+ * Features demonstrated:
5
+ * - Basic tool with type-safe parameters
6
+ * - Streaming-enabled tool for incremental output
7
+ * - Advanced tool annotations
8
+ *
9
+ * For a complete project template, see https://github.com/punkpeye/fastmcp-boilerplate
6
10
  */
7
11
  import { type } from "arktype";
8
12
  import * as v from "valibot";
@@ -118,6 +122,39 @@ server.addResource({
118
122
  uri: "file:///logs/app.log",
119
123
  });
120
124
 
125
+ server.addTool({
126
+ annotations: {
127
+ openWorldHint: false,
128
+ readOnlyHint: true,
129
+ streamingHint: true,
130
+ },
131
+ description: "Generate a poem line by line with streaming output",
132
+ execute: async (args, context) => {
133
+ const { theme } = args;
134
+ const lines = [
135
+ `Poem about ${theme} - line 1`,
136
+ `Poem about ${theme} - line 2`,
137
+ `Poem about ${theme} - line 3`,
138
+ `Poem about ${theme} - line 4`,
139
+ ];
140
+
141
+ for (const line of lines) {
142
+ await context.streamContent({
143
+ text: line,
144
+ type: "text",
145
+ });
146
+
147
+ await new Promise((resolve) => setTimeout(resolve, 1000));
148
+ }
149
+
150
+ return;
151
+ },
152
+ name: "stream-poem",
153
+ parameters: z.object({
154
+ theme: z.string().describe("Theme for the poem"),
155
+ }),
156
+ });
157
+
121
158
  server.addPrompt({
122
159
  arguments: [
123
160
  {
@@ -144,7 +181,6 @@ if (transportType === "httpStream") {
144
181
 
145
182
  server.start({
146
183
  httpStream: {
147
- endpoint: "/stream",
148
184
  port: PORT,
149
185
  },
150
186
  transportType: "httpStream",