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/README.md CHANGED
@@ -15,14 +15,15 @@ A TypeScript framework for building [MCP](https://glama.ai/mcp) servers capable
15
15
  - [Audio content](#returning-an-audio)
16
16
  - [Logging](#logging)
17
17
  - [Error handling](#errors)
18
- - [SSE](#sse)
19
- - [HTTP Streaming](#http-streaming)
18
+ - [HTTP Streaming](#http-streaming) (with SSE compatibility)
20
19
  - CORS (enabled by default)
21
20
  - [Progress notifications](#progress)
21
+ - [Streaming output](#streaming-output)
22
22
  - [Typed server events](#typed-server-events)
23
23
  - [Prompt argument auto-completion](#prompt-argument-auto-completion)
24
24
  - [Sampling](#requestsampling)
25
25
  - [Configurable ping behavior](#configurable-ping-behavior)
26
+ - [Health-check endpoint](#health-check-endpoint)
26
27
  - [Roots](#roots-management)
27
28
  - CLI for [testing](#test-with-mcp-cli) and [debugging](#inspect-with-mcp-inspector)
28
29
 
@@ -87,24 +88,6 @@ If you are looking for a boilerplate repository to build your own MCP server, ch
87
88
 
88
89
  FastMCP supports multiple transport options for remote communication, allowing an MCP hosted on a remote machine to be accessed over the network.
89
90
 
90
- #### SSE
91
-
92
- [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) (SSE) provide a mechanism for servers to send real-time updates to clients over an HTTPS connection.
93
-
94
- You can run the server with SSE support:
95
-
96
- ```ts
97
- server.start({
98
- transportType: "sse",
99
- sse: {
100
- endpoint: "/sse",
101
- port: 8080,
102
- },
103
- });
104
- ```
105
-
106
- This will start the server and listen for SSE connections on `http://localhost:8080/sse`.
107
-
108
91
  #### HTTP Streaming
109
92
 
110
93
  [HTTP streaming](https://www.cloudflare.com/learning/video/what-is-http-live-streaming/) provides a more efficient alternative to SSE in environments that support it, with potentially better performance for larger payloads.
@@ -115,7 +98,6 @@ You can run the server with HTTP streaming support:
115
98
  server.start({
116
99
  transportType: "httpStream",
117
100
  httpStream: {
118
- endpoint: "/stream",
119
101
  port: 8080,
120
102
  },
121
103
  });
@@ -125,10 +107,10 @@ This will start the server and listen for HTTP streaming connections on `http://
125
107
 
126
108
  You can connect to these servers using the appropriate client transport.
127
109
 
128
- For SSE connections:
110
+ For HTTP streaming connections:
129
111
 
130
112
  ```ts
131
- import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
113
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
132
114
 
133
115
  const client = new Client(
134
116
  {
@@ -140,15 +122,17 @@ const client = new Client(
140
122
  },
141
123
  );
142
124
 
143
- const transport = new SSEClientTransport(new URL(`http://localhost:8080/sse`));
125
+ const transport = new StreamableHTTPClientTransport(
126
+ new URL(`http://localhost:8080/stream`),
127
+ );
144
128
 
145
129
  await client.connect(transport);
146
130
  ```
147
131
 
148
- For HTTP streaming connections:
132
+ For SSE connections:
149
133
 
150
134
  ```ts
151
- import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
135
+ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
152
136
 
153
137
  const client = new Client(
154
138
  {
@@ -160,9 +144,7 @@ const client = new Client(
160
144
  },
161
145
  );
162
146
 
163
- const transport = new StreamableHTTPClientTransport(
164
- new URL(`http://localhost:8080/stream`),
165
- );
147
+ const transport = new SSEClientTransport(new URL(`http://localhost:8080/sse`));
166
148
 
167
149
  await client.connect(transport);
168
150
  ```
@@ -420,6 +402,47 @@ By default, ping behavior is optimized for each transport type:
420
402
 
421
403
  This configurable approach helps reduce log verbosity and optimize performance for different usage scenarios.
422
404
 
405
+ ### Health-check Endpoint
406
+
407
+ When you run FastMCP with the `httpStream` transport you can optionally expose a
408
+ simple HTTP endpoint that returns a plain-text response useful for load-balancer
409
+ or container orchestration liveness checks.
410
+
411
+ Enable (or customise) the endpoint via the `health` key in the server options:
412
+
413
+ ```ts
414
+ const server = new FastMCP({
415
+ name: "My Server",
416
+ version: "1.0.0",
417
+ health: {
418
+ // Enable / disable (default: true)
419
+ enabled: true,
420
+ // Body returned by the endpoint (default: 'ok')
421
+ message: "healthy",
422
+ // Path that should respond (default: '/health')
423
+ path: "/healthz",
424
+ // HTTP status code to return (default: 200)
425
+ status: 200,
426
+ },
427
+ });
428
+
429
+ await server.start({
430
+ transportType: "httpStream",
431
+ httpStream: { port: 8080 },
432
+ });
433
+ ```
434
+
435
+ Now a request to `http://localhost:8080/healthz` will return:
436
+
437
+ ```
438
+ HTTP/1.1 200 OK
439
+ content-type: text/plain
440
+
441
+ healthy
442
+ ```
443
+
444
+ The endpoint is ignored when the server is started with the `stdio` transport.
445
+
423
446
  #### Roots Management
424
447
 
425
448
  FastMCP supports [Roots](https://modelcontextprotocol.io/docs/concepts/roots) - Feature that allows clients to provide a set of filesystem-like root locations that can be listed and dynamically updated. The Roots feature can be configured or disabled in server options:
@@ -668,6 +691,86 @@ server.addTool({
668
691
  });
669
692
  ```
670
693
 
694
+ #### Streaming Output
695
+
696
+ FastMCP supports streaming partial results from tools while they're still executing, enabling responsive UIs and real-time feedback. This is particularly useful for:
697
+
698
+ - Long-running operations that generate content incrementally
699
+ - Progressive generation of text, images, or other media
700
+ - Operations where users benefit from seeing immediate partial results
701
+
702
+ To enable streaming for a tool, add the `streamingHint` annotation and use the `streamContent` method:
703
+
704
+ ```js
705
+ server.addTool({
706
+ name: "generateText",
707
+ description: "Generate text incrementally",
708
+ parameters: z.object({
709
+ prompt: z.string(),
710
+ }),
711
+ annotations: {
712
+ streamingHint: true, // Signals this tool uses streaming
713
+ readOnlyHint: true,
714
+ },
715
+ execute: async (args, { streamContent }) => {
716
+ // Send initial content immediately
717
+ await streamContent({ type: "text", text: "Starting generation...\n" });
718
+
719
+ // Simulate incremental content generation
720
+ const words = "The quick brown fox jumps over the lazy dog.".split(" ");
721
+ for (const word of words) {
722
+ await streamContent({ type: "text", text: word + " " });
723
+ await new Promise((resolve) => setTimeout(resolve, 300)); // Simulate delay
724
+ }
725
+
726
+ // When using streamContent, you can:
727
+ // 1. Return void (if all content was streamed)
728
+ // 2. Return a final result (which will be appended to streamed content)
729
+
730
+ // Option 1: All content was streamed, so return void
731
+ return;
732
+
733
+ // Option 2: Return final content that will be appended
734
+ // return "Generation complete!";
735
+ },
736
+ });
737
+ ```
738
+
739
+ Streaming works with all content types (text, image, audio) and can be combined with progress reporting:
740
+
741
+ ```js
742
+ server.addTool({
743
+ name: "processData",
744
+ description: "Process data with streaming updates",
745
+ parameters: z.object({
746
+ datasetSize: z.number(),
747
+ }),
748
+ annotations: {
749
+ streamingHint: true,
750
+ },
751
+ execute: async (args, { streamContent, reportProgress }) => {
752
+ const total = args.datasetSize;
753
+
754
+ for (let i = 0; i < total; i++) {
755
+ // Report numeric progress
756
+ await reportProgress({ progress: i, total });
757
+
758
+ // Stream intermediate results
759
+ if (i % 10 === 0) {
760
+ await streamContent({
761
+ type: "text",
762
+ text: `Processed ${i} of ${total} items...\n`,
763
+ });
764
+ }
765
+
766
+ await new Promise((resolve) => setTimeout(resolve, 50));
767
+ }
768
+
769
+ return "Processing complete!";
770
+ },
771
+ });
772
+ ```
773
+
671
774
  #### Tool Annotations
672
775
 
673
776
  As of the MCP Specification (2025-03-26), tools can include annotations that provide richer context and control by adding metadata about a tool's behavior:
@@ -892,8 +995,6 @@ server.addPrompt({
892
995
  FastMCP allows you to `authenticate` clients using a custom function:
893
996
 
894
997
  ```ts
895
- import { AuthError } from "fastmcp";
896
-
897
998
  const server = new FastMCP({
898
999
  name: "My Server",
899
1000
  version: "1.0.0",
package/dist/FastMCP.d.ts CHANGED
@@ -49,6 +49,7 @@ type Context<T extends FastMCPSessionAuth> = {
49
49
  };
50
50
  reportProgress: (progress: Progress) => Promise<void>;
51
51
  session: T | undefined;
52
+ streamContent: (content: Content | Content[]) => Promise<void>;
52
53
  };
53
54
  type Extra = unknown;
54
55
  type Extras = Record<string, Extra>;
@@ -187,6 +188,38 @@ type ResourceTemplateArgumentsToObject<T extends {
187
188
  };
188
189
  type ServerOptions<T extends FastMCPSessionAuth> = {
189
190
  authenticate?: Authenticate<T>;
191
+ /**
192
+ * Configuration for the health-check endpoint that can be exposed when the
193
+ * server is running using the HTTP Stream transport. When enabled, the
194
+ * server will respond to an HTTP GET request with the configured path (by
195
+ * default "/health") rendering a plain-text response (by default "ok") and
196
+ * the configured status code (by default 200).
197
+ *
198
+ * The endpoint is only added when the server is started with
199
+ * `transportType: "httpStream"` – it is ignored for the stdio transport.
200
+ */
201
+ health?: {
202
+ /**
203
+ * When set to `false` the health-check endpoint is disabled.
204
+ * @default true
205
+ */
206
+ enabled?: boolean;
207
+ /**
208
+ * Plain-text body returned by the endpoint.
209
+ * @default "ok"
210
+ */
211
+ message?: string;
212
+ /**
213
+ * HTTP path that should be handled.
214
+ * @default "/health"
215
+ */
216
+ path?: string;
217
+ /**
218
+ * HTTP response status that will be returned.
219
+ * @default 200
220
+ */
221
+ status?: number;
222
+ };
190
223
  instructions?: string;
191
224
  name: string;
192
225
  ping?: {
@@ -221,9 +254,15 @@ type ServerOptions<T extends FastMCPSessionAuth> = {
221
254
  version: `${number}.${number}.${number}`;
222
255
  };
223
256
  type Tool<T extends FastMCPSessionAuth, Params extends ToolParameters = ToolParameters> = {
224
- annotations?: ToolAnnotations;
257
+ annotations?: {
258
+ /**
259
+ * When true, the tool leverages incremental content streaming
260
+ * Return void for tools that handle all their output via streaming
261
+ */
262
+ streamingHint?: boolean;
263
+ } & ToolAnnotations;
225
264
  description?: string;
226
- execute: (args: StandardSchemaV1.InferOutput<Params>, context: Context<T>) => Promise<AudioContent | ContentResult | ImageContent | string | TextContent>;
265
+ execute: (args: StandardSchemaV1.InferOutput<Params>, context: Context<T>) => Promise<AudioContent | ContentResult | ImageContent | string | TextContent | void>;
227
266
  name: string;
228
267
  parameters?: Params;
229
268
  timeoutMs?: number;
@@ -337,16 +376,9 @@ declare class FastMCP<T extends Record<string, unknown> | undefined = undefined>
337
376
  */
338
377
  start(options?: {
339
378
  httpStream: {
340
- endpoint: `/${string}`;
341
379
  port: number;
342
380
  };
343
381
  transportType: "httpStream";
344
- } | {
345
- sse: {
346
- endpoint: `/${string}`;
347
- port: number;
348
- };
349
- transportType: "sse";
350
382
  } | {
351
383
  transportType: "stdio";
352
384
  }): Promise<void>;
package/dist/FastMCP.js CHANGED
@@ -19,7 +19,7 @@ import { EventEmitter } from "events";
19
19
  import { fileTypeFromBuffer } from "file-type";
20
20
  import { readFile } from "fs/promises";
21
21
  import Fuse from "fuse.js";
22
- import { startHTTPStreamServer, startSSEServer } from "mcp-proxy";
22
+ import { startHTTPServer } from "mcp-proxy";
23
23
  import { setTimeout as delay } from "timers/promises";
24
24
  import { fetch } from "undici";
25
25
  import parseURITemplate from "uri-templates";
@@ -354,7 +354,7 @@ ${e instanceof Error ? e.stack : JSON.stringify(e)}`
354
354
  const pingConfig = this.#pingConfig || {};
355
355
  let defaultEnabled = false;
356
356
  if ("type" in transport) {
357
- if (transport.type === "sse" || transport.type === "httpStream") {
357
+ if (transport.type === "httpStream") {
358
358
  defaultEnabled = true;
359
359
  }
360
360
  }
@@ -773,10 +773,21 @@ ${e instanceof Error ? e.stack : JSON.stringify(e)}`
773
773
  });
774
774
  }
775
775
  };
776
+ const streamContent = async (content) => {
777
+ const contentArray = Array.isArray(content) ? content : [content];
778
+ await this.#server.notification({
779
+ method: "notifications/tool/streamContent",
780
+ params: {
781
+ content: contentArray,
782
+ toolName: request.params.name
783
+ }
784
+ });
785
+ };
776
786
  const executeToolPromise = tool.execute(args, {
777
787
  log,
778
788
  reportProgress,
779
- session: this.#auth
789
+ session: this.#auth,
790
+ streamContent
780
791
  });
781
792
  const maybeStringResult = await (tool.timeoutMs ? Promise.race([
782
793
  executeToolPromise,
@@ -790,7 +801,11 @@ ${e instanceof Error ? e.stack : JSON.stringify(e)}`
790
801
  }, tool.timeoutMs);
791
802
  })
792
803
  ]) : executeToolPromise);
793
- if (typeof maybeStringResult === "string") {
804
+ if (maybeStringResult === void 0 || maybeStringResult === null) {
805
+ result = ContentResultZodSchema.parse({
806
+ content: []
807
+ });
808
+ } else if (typeof maybeStringResult === "string") {
794
809
  result = ContentResultZodSchema.parse({
795
810
  content: [{ text: maybeStringResult, type: "text" }]
796
811
  });
@@ -837,7 +852,6 @@ var FastMCP = class extends FastMCPEventEmitter {
837
852
  #resources = [];
838
853
  #resourcesTemplates = [];
839
854
  #sessions = [];
840
- #sseServer = null;
841
855
  #tools = [];
842
856
  /**
843
857
  * Adds a prompt to the server.
@@ -887,8 +901,8 @@ var FastMCP = class extends FastMCPEventEmitter {
887
901
  this.emit("connect", {
888
902
  session
889
903
  });
890
- } else if (options.transportType === "sse") {
891
- this.#sseServer = await startSSEServer({
904
+ } else if (options.transportType === "httpStream") {
905
+ this.#httpStreamServer = await startHTTPServer({
892
906
  createServer: async (request) => {
893
907
  let auth;
894
908
  if (this.#authenticate) {
@@ -906,7 +920,6 @@ var FastMCP = class extends FastMCPEventEmitter {
906
920
  version: this.#options.version
907
921
  });
908
922
  },
909
- endpoint: options.sse.endpoint,
910
923
  onClose: (session) => {
911
924
  this.emit("disconnect", {
912
925
  session
@@ -918,46 +931,28 @@ var FastMCP = class extends FastMCPEventEmitter {
918
931
  session
919
932
  });
920
933
  },
921
- port: options.sse.port
922
- });
923
- console.info(
924
- `[FastMCP info] server is running on SSE at http://localhost:${options.sse.port}${options.sse.endpoint}`
925
- );
926
- } else if (options.transportType === "httpStream") {
927
- this.#httpStreamServer = await startHTTPStreamServer({
928
- createServer: async (request) => {
929
- let auth;
930
- if (this.#authenticate) {
931
- auth = await this.#authenticate(request);
934
+ onUnhandledRequest: async (req, res) => {
935
+ const healthConfig = this.#options.health ?? {};
936
+ const enabled = healthConfig.enabled === void 0 ? true : healthConfig.enabled;
937
+ if (enabled) {
938
+ const path = healthConfig.path ?? "/health";
939
+ try {
940
+ if (req.method === "GET" && new URL(req.url || "", "http://localhost").pathname === path) {
941
+ res.writeHead(healthConfig.status ?? 200, {
942
+ "Content-Type": "text/plain"
943
+ }).end(healthConfig.message ?? "ok");
944
+ return;
945
+ }
946
+ } catch (error) {
947
+ console.error("[FastMCP error] health endpoint error", error);
948
+ }
932
949
  }
933
- return new FastMCPSession({
934
- auth,
935
- name: this.#options.name,
936
- ping: this.#options.ping,
937
- prompts: this.#prompts,
938
- resources: this.#resources,
939
- resourcesTemplates: this.#resourcesTemplates,
940
- roots: this.#options.roots,
941
- tools: this.#tools,
942
- version: this.#options.version
943
- });
944
- },
945
- endpoint: options.httpStream.endpoint,
946
- onClose: (session) => {
947
- this.emit("disconnect", {
948
- session
949
- });
950
- },
951
- onConnect: async (session) => {
952
- this.#sessions.push(session);
953
- this.emit("connect", {
954
- session
955
- });
950
+ res.writeHead(404).end();
956
951
  },
957
952
  port: options.httpStream.port
958
953
  });
959
954
  console.info(
960
- `[FastMCP info] server is running on HTTP Stream at http://localhost:${options.httpStream.port}${options.httpStream.endpoint}`
955
+ `[FastMCP info] server is running on HTTP Stream at http://localhost:${options.httpStream.port}/stream`
961
956
  );
962
957
  } else {
963
958
  throw new Error("Invalid transport type");
@@ -967,9 +962,6 @@ var FastMCP = class extends FastMCPEventEmitter {
967
962
  * Stops the server.
968
963
  */
969
964
  async stop() {
970
- if (this.#sseServer) {
971
- await this.#sseServer.close();
972
- }
973
965
  if (this.#httpStreamServer) {
974
966
  await this.#httpStreamServer.close();
975
967
  }