fastmcp 2.1.4 → 2.2.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.
package/README.md CHANGED
@@ -13,6 +13,7 @@ A TypeScript framework for building [MCP](https://glama.ai/mcp) servers capable
13
13
  - [Sessions](#sessions)
14
14
  - [Image content](#returning-an-image)
15
15
  - [Audio content](#returning-an-audio)
16
+ - [Embedded](#embedded-resources)
16
17
  - [Logging](#logging)
17
18
  - [Error handling](#errors)
18
19
  - [HTTP Streaming](#http-streaming) (with SSE compatibility)
@@ -915,6 +916,114 @@ server.addResourceTemplate({
915
916
  });
916
917
  ```
917
918
 
919
+ ### Embedded Resources
920
+
921
+ FastMCP provides a convenient `embedded()` method that simplifies including resources in tool responses. This feature reduces code duplication and makes it easier to reference resources from within tools.
922
+
923
+ #### Basic Usage
924
+
925
+ ```js
926
+ server.addTool({
927
+ name: "get_user_data",
928
+ description: "Retrieve user information",
929
+ parameters: z.object({
930
+ userId: z.string(),
931
+ }),
932
+ execute: async (args) => {
933
+ return {
934
+ content: [
935
+ {
936
+ type: "resource",
937
+ resource: await server.embedded(`user://profile/${args.userId}`),
938
+ },
939
+ ],
940
+ };
941
+ },
942
+ });
943
+ ```
944
+
945
+ #### Working with Resource Templates
946
+
947
+ The `embedded()` method works seamlessly with resource templates:
948
+
949
+ ```js
950
+ // Define a resource template
951
+ server.addResourceTemplate({
952
+ uriTemplate: "docs://project/{section}",
953
+ name: "Project Documentation",
954
+ mimeType: "text/markdown",
955
+ arguments: [
956
+ {
957
+ name: "section",
958
+ required: true,
959
+ },
960
+ ],
961
+ async load(args) {
962
+ const docs = {
963
+ "getting-started": "# Getting Started\n\nWelcome to our project!",
964
+ "api-reference": "# API Reference\n\nAuthentication is required.",
965
+ };
966
+ return {
967
+ text: docs[args.section] || "Documentation not found",
968
+ };
969
+ },
970
+ });
971
+
972
+ // Use embedded resources in a tool
973
+ server.addTool({
974
+ name: "get_documentation",
975
+ description: "Retrieve project documentation",
976
+ parameters: z.object({
977
+ section: z.enum(["getting-started", "api-reference"]),
978
+ }),
979
+ execute: async (args) => {
980
+ return {
981
+ content: [
982
+ {
983
+ type: "resource",
984
+ resource: await server.embedded(`docs://project/${args.section}`),
985
+ },
986
+ ],
987
+ };
988
+ },
989
+ });
990
+ ```
991
+
992
+ #### Working with Direct Resources
993
+
994
+ It also works with directly defined resources:
995
+
996
+ ```js
997
+ // Define a direct resource
998
+ server.addResource({
999
+ uri: "system://status",
1000
+ name: "System Status",
1001
+ mimeType: "text/plain",
1002
+ async load() {
1003
+ return {
1004
+ text: "System operational",
1005
+ };
1006
+ },
1007
+ });
1008
+
1009
+ // Use in a tool
1010
+ server.addTool({
1011
+ name: "get_system_status",
1012
+ description: "Get current system status",
1013
+ parameters: z.object({}),
1014
+ execute: async () => {
1015
+ return {
1016
+ content: [
1017
+ {
1018
+ type: "resource",
1019
+ resource: await server.embedded("system://status"),
1020
+ },
1021
+ ],
1022
+ };
1023
+ },
1024
+ });
1025
+ ```
1026
+
918
1027
  ### Prompts
919
1028
 
920
1029
  [Prompts](https://modelcontextprotocol.io/docs/concepts/prompts) enable servers to define reusable prompt templates and workflows that clients can easily surface to users and LLMs. They provide a powerful way to standardize and share common LLM interactions.
@@ -1197,6 +1306,7 @@ Follow the guide https://modelcontextprotocol.io/quickstart/user and add the fol
1197
1306
  - [aiamblichus/mcp-chat-adapter](https://github.com/aiamblichus/mcp-chat-adapter) – provides a clean interface for LLMs to use chat completion
1198
1307
  - [eyaltoledano/claude-task-master](https://github.com/eyaltoledano/claude-task-master) – advanced AI project/task manager powered by FastMCP
1199
1308
  - [cswkim/discogs-mcp-server](https://github.com/cswkim/discogs-mcp-server) - connects to the Discogs API for interacting with your music collection
1309
+ - [Panzer-Jack/feuse-mcp](https://github.com/Panzer-Jack/feuse-mcp) - Frontend Useful MCP Tools - Essential utilities for web developers to automate API integration and code generation
1200
1310
 
1201
1311
  ## Acknowledgements
1202
1312
 
package/dist/FastMCP.d.ts CHANGED
@@ -22,6 +22,7 @@ type FastMCPSessionEvents = {
22
22
  error: (event: {
23
23
  error: Error;
24
24
  }) => void;
25
+ ready: () => void;
25
26
  rootsChanged: (event: {
26
27
  roots: Root[];
27
28
  }) => void;
@@ -94,7 +95,16 @@ type AudioContent = {
94
95
  mimeType: string;
95
96
  type: "audio";
96
97
  };
97
- type Content = AudioContent | ImageContent | TextContent;
98
+ type ResourceContent = {
99
+ resource: {
100
+ blob?: string;
101
+ mimeType?: string;
102
+ text?: string;
103
+ uri: string;
104
+ };
105
+ type: "resource";
106
+ };
107
+ type Content = AudioContent | ImageContent | ResourceContent | TextContent;
98
108
  type ContentResult = {
99
109
  content: Content[];
100
110
  isError?: boolean;
@@ -264,7 +274,7 @@ type Tool<T extends FastMCPSessionAuth, Params extends ToolParameters = ToolPara
264
274
  streamingHint?: boolean;
265
275
  } & ToolAnnotations;
266
276
  description?: string;
267
- execute: (args: StandardSchemaV1.InferOutput<Params>, context: Context<T>) => Promise<AudioContent | ContentResult | ImageContent | string | TextContent | void>;
277
+ execute: (args: StandardSchemaV1.InferOutput<Params>, context: Context<T>) => Promise<AudioContent | ContentResult | ImageContent | ResourceContent | string | TextContent | void>;
268
278
  name: string;
269
279
  parameters?: Params;
270
280
  timeoutMs?: number;
@@ -316,6 +326,7 @@ declare class FastMCPSessionEventEmitter extends FastMCPSessionEventEmitterBase
316
326
  declare class FastMCPSession<T extends FastMCPSessionAuth = FastMCPSessionAuth> extends FastMCPSessionEventEmitter {
317
327
  #private;
318
328
  get clientCapabilities(): ClientCapabilities | null;
329
+ get isReady(): boolean;
319
330
  get loggingLevel(): LoggingLevel;
320
331
  get roots(): Root[];
321
332
  get server(): Server;
@@ -334,6 +345,7 @@ declare class FastMCPSession<T extends FastMCPSessionAuth = FastMCPSessionAuth>
334
345
  close(): Promise<void>;
335
346
  connect(transport: Transport): Promise<void>;
336
347
  requestSampling(message: z.infer<typeof CreateMessageRequestSchema>["params"]): Promise<SamplingResponse>;
348
+ waitForReady(): Promise<void>;
337
349
  private addPrompt;
338
350
  private addResource;
339
351
  private addResourceTemplate;
@@ -373,6 +385,13 @@ declare class FastMCP<T extends Record<string, unknown> | undefined = undefined>
373
385
  * Adds a tool to the server.
374
386
  */
375
387
  addTool<Params extends ToolParameters>(tool: Tool<T, Params>): void;
388
+ /**
389
+ * Embeds a resource by URI, making it easy to include resources in tool responses.
390
+ *
391
+ * @param uri - The URI of the resource to embed
392
+ * @returns Promise<ResourceContent> - The embedded resource content
393
+ */
394
+ embedded(uri: string): Promise<ResourceContent["resource"]>;
376
395
  /**
377
396
  * Starts the server.
378
397
  */
package/dist/FastMCP.js CHANGED
@@ -171,10 +171,20 @@ var AudioContentZodSchema = z.object({
171
171
  mimeType: z.string(),
172
172
  type: z.literal("audio")
173
173
  }).strict();
174
+ var ResourceContentZodSchema = z.object({
175
+ resource: z.object({
176
+ blob: z.string().optional(),
177
+ mimeType: z.string().optional(),
178
+ text: z.string().optional(),
179
+ uri: z.string()
180
+ }),
181
+ type: z.literal("resource")
182
+ }).strict();
174
183
  var ContentZodSchema = z.discriminatedUnion("type", [
175
184
  TextContentZodSchema,
176
185
  ImageContentZodSchema,
177
- AudioContentZodSchema
186
+ AudioContentZodSchema,
187
+ ResourceContentZodSchema
178
188
  ]);
179
189
  var ContentResultZodSchema = z.object({
180
190
  content: ContentZodSchema.array(),
@@ -201,6 +211,9 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
201
211
  get clientCapabilities() {
202
212
  return this.#clientCapabilities ?? null;
203
213
  }
214
+ get isReady() {
215
+ return this.#connectionState === "ready";
216
+ }
204
217
  get loggingLevel() {
205
218
  return this.#loggingLevel;
206
219
  }
@@ -213,6 +226,7 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
213
226
  #auth;
214
227
  #capabilities = {};
215
228
  #clientCapabilities;
229
+ #connectionState = "connecting";
216
230
  #loggingLevel = "info";
217
231
  #pingConfig;
218
232
  #pingInterval = null;
@@ -279,6 +293,7 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
279
293
  }
280
294
  }
281
295
  async close() {
296
+ this.#connectionState = "closed";
282
297
  if (this.#pingInterval) {
283
298
  clearInterval(this.#pingInterval);
284
299
  }
@@ -292,64 +307,105 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
292
307
  if (this.#server.transport) {
293
308
  throw new UnexpectedStateError("Server is already connected");
294
309
  }
295
- await this.#server.connect(transport);
296
- let attempt = 0;
297
- while (attempt++ < 10) {
298
- const capabilities = await this.#server.getClientCapabilities();
299
- if (capabilities) {
300
- this.#clientCapabilities = capabilities;
301
- break;
310
+ this.#connectionState = "connecting";
311
+ try {
312
+ await this.#server.connect(transport);
313
+ let attempt = 0;
314
+ while (attempt++ < 10) {
315
+ const capabilities = this.#server.getClientCapabilities();
316
+ if (capabilities) {
317
+ this.#clientCapabilities = capabilities;
318
+ break;
319
+ }
320
+ await delay(100);
302
321
  }
303
- await delay(100);
304
- }
305
- if (!this.#clientCapabilities) {
306
- console.warn("[FastMCP warning] could not infer client capabilities");
307
- }
308
- if (this.#clientCapabilities?.roots?.listChanged && typeof this.#server.listRoots === "function") {
309
- try {
310
- const roots = await this.#server.listRoots();
311
- this.#roots = roots.roots;
312
- } catch (e) {
313
- if (e instanceof McpError && e.code === ErrorCode.MethodNotFound) {
314
- console.debug(
315
- "[FastMCP debug] listRoots method not supported by client"
316
- );
317
- } else {
318
- console.error(
319
- `[FastMCP error] received error listing roots.
322
+ if (!this.#clientCapabilities) {
323
+ console.warn("[FastMCP warning] could not infer client capabilities");
324
+ }
325
+ if (this.#clientCapabilities?.roots?.listChanged && typeof this.#server.listRoots === "function") {
326
+ try {
327
+ const roots = await this.#server.listRoots();
328
+ this.#roots = roots.roots;
329
+ } catch (e) {
330
+ if (e instanceof McpError && e.code === ErrorCode.MethodNotFound) {
331
+ console.debug(
332
+ "[FastMCP debug] listRoots method not supported by client"
333
+ );
334
+ } else {
335
+ console.error(
336
+ `[FastMCP error] received error listing roots.
320
337
 
321
338
  ${e instanceof Error ? e.stack : JSON.stringify(e)}`
322
- );
339
+ );
340
+ }
323
341
  }
324
342
  }
325
- }
326
- if (this.#clientCapabilities) {
327
- const pingConfig = this.#getPingConfig(transport);
328
- if (pingConfig.enabled) {
329
- this.#pingInterval = setInterval(async () => {
330
- try {
331
- await this.#server.ping();
332
- } catch {
333
- const logLevel = pingConfig.logLevel;
334
- if (logLevel === "debug") {
335
- console.debug("[FastMCP debug] server ping failed");
336
- } else if (logLevel === "warning") {
337
- console.warn(
338
- "[FastMCP warning] server is not responding to ping"
339
- );
340
- } else if (logLevel === "error") {
341
- console.error("[FastMCP error] server is not responding to ping");
342
- } else {
343
- console.info("[FastMCP info] server ping failed");
343
+ if (this.#clientCapabilities) {
344
+ const pingConfig = this.#getPingConfig(transport);
345
+ if (pingConfig.enabled) {
346
+ this.#pingInterval = setInterval(async () => {
347
+ try {
348
+ await this.#server.ping();
349
+ } catch {
350
+ const logLevel = pingConfig.logLevel;
351
+ if (logLevel === "debug") {
352
+ console.debug("[FastMCP debug] server ping failed");
353
+ } else if (logLevel === "warning") {
354
+ console.warn(
355
+ "[FastMCP warning] server is not responding to ping"
356
+ );
357
+ } else if (logLevel === "error") {
358
+ console.error(
359
+ "[FastMCP error] server is not responding to ping"
360
+ );
361
+ } else {
362
+ console.info("[FastMCP info] server ping failed");
363
+ }
344
364
  }
345
- }
346
- }, pingConfig.intervalMs);
365
+ }, pingConfig.intervalMs);
366
+ }
347
367
  }
368
+ this.#connectionState = "ready";
369
+ this.emit("ready");
370
+ } catch (error) {
371
+ this.#connectionState = "error";
372
+ const errorEvent = {
373
+ error: error instanceof Error ? error : new Error(String(error))
374
+ };
375
+ this.emit("error", errorEvent);
376
+ throw error;
348
377
  }
349
378
  }
350
379
  async requestSampling(message) {
351
380
  return this.#server.createMessage(message);
352
381
  }
382
+ waitForReady() {
383
+ if (this.isReady) {
384
+ return Promise.resolve();
385
+ }
386
+ if (this.#connectionState === "error" || this.#connectionState === "closed") {
387
+ return Promise.reject(
388
+ new Error(`Connection is in ${this.#connectionState} state`)
389
+ );
390
+ }
391
+ return new Promise((resolve, reject) => {
392
+ const timeout = setTimeout(() => {
393
+ reject(
394
+ new Error(
395
+ "Connection timeout: Session failed to become ready within 5 seconds"
396
+ )
397
+ );
398
+ }, 5e3);
399
+ this.once("ready", () => {
400
+ clearTimeout(timeout);
401
+ resolve();
402
+ });
403
+ this.once("error", (event) => {
404
+ clearTimeout(timeout);
405
+ reject(event.error);
406
+ });
407
+ });
408
+ }
353
409
  #getPingConfig(transport) {
354
410
  const pingConfig = this.#pingConfig || {};
355
411
  let defaultEnabled = false;
@@ -889,6 +945,66 @@ var FastMCP = class extends FastMCPEventEmitter {
889
945
  addTool(tool) {
890
946
  this.#tools.push(tool);
891
947
  }
948
+ /**
949
+ * Embeds a resource by URI, making it easy to include resources in tool responses.
950
+ *
951
+ * @param uri - The URI of the resource to embed
952
+ * @returns Promise<ResourceContent> - The embedded resource content
953
+ */
954
+ async embedded(uri) {
955
+ const directResource = this.#resources.find(
956
+ (resource) => resource.uri === uri
957
+ );
958
+ if (directResource) {
959
+ const result = await directResource.load();
960
+ const results = Array.isArray(result) ? result : [result];
961
+ const firstResult = results[0];
962
+ const resourceData = {
963
+ mimeType: directResource.mimeType,
964
+ uri
965
+ };
966
+ if ("text" in firstResult) {
967
+ resourceData.text = firstResult.text;
968
+ }
969
+ if ("blob" in firstResult) {
970
+ resourceData.blob = firstResult.blob;
971
+ }
972
+ return resourceData;
973
+ }
974
+ for (const template of this.#resourcesTemplates) {
975
+ const templateBase = template.uriTemplate.split("{")[0];
976
+ if (uri.startsWith(templateBase)) {
977
+ const params = {};
978
+ const templateParts = template.uriTemplate.split("/");
979
+ const uriParts = uri.split("/");
980
+ for (let i = 0; i < templateParts.length; i++) {
981
+ const templatePart = templateParts[i];
982
+ if (templatePart?.startsWith("{") && templatePart.endsWith("}")) {
983
+ const paramName = templatePart.slice(1, -1);
984
+ const paramValue = uriParts[i];
985
+ if (paramValue) {
986
+ params[paramName] = paramValue;
987
+ }
988
+ }
989
+ }
990
+ const result = await template.load(
991
+ params
992
+ );
993
+ const resourceData = {
994
+ mimeType: template.mimeType,
995
+ uri
996
+ };
997
+ if ("text" in result) {
998
+ resourceData.text = result.text;
999
+ }
1000
+ if ("blob" in result) {
1001
+ resourceData.blob = result.blob;
1002
+ }
1003
+ return resourceData;
1004
+ }
1005
+ }
1006
+ throw new UnexpectedStateError(`Resource not found: ${uri}`, { uri });
1007
+ }
892
1008
  /**
893
1009
  * Starts the server.
894
1010
  */
@@ -948,13 +1064,30 @@ var FastMCP = class extends FastMCPEventEmitter {
948
1064
  const enabled = healthConfig.enabled === void 0 ? true : healthConfig.enabled;
949
1065
  if (enabled) {
950
1066
  const path = healthConfig.path ?? "/health";
1067
+ const url = new URL(req.url || "", "http://localhost");
951
1068
  try {
952
- if (req.method === "GET" && new URL(req.url || "", "http://localhost").pathname === path) {
1069
+ if (req.method === "GET" && url.pathname === path) {
953
1070
  res.writeHead(healthConfig.status ?? 200, {
954
1071
  "Content-Type": "text/plain"
955
1072
  }).end(healthConfig.message ?? "ok");
956
1073
  return;
957
1074
  }
1075
+ if (req.method === "GET" && url.pathname === "/ready") {
1076
+ const readySessions = this.#sessions.filter(
1077
+ (s) => s.isReady
1078
+ ).length;
1079
+ const totalSessions = this.#sessions.length;
1080
+ const allReady = readySessions === totalSessions && totalSessions > 0;
1081
+ const response = {
1082
+ ready: readySessions,
1083
+ status: allReady ? "ready" : totalSessions === 0 ? "no_sessions" : "initializing",
1084
+ total: totalSessions
1085
+ };
1086
+ res.writeHead(allReady ? 200 : 503, {
1087
+ "Content-Type": "application/json"
1088
+ }).end(JSON.stringify(response));
1089
+ return;
1090
+ }
958
1091
  } catch (error) {
959
1092
  console.error("[FastMCP error] health endpoint error", error);
960
1093
  }