fastmcp 1.23.0 → 1.23.2

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
@@ -1,5 +1,6 @@
1
1
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
3
4
  import {
4
5
  AudioContent,
5
6
  CallToolRequestSchema,
@@ -20,19 +21,18 @@ import {
20
21
  SetLevelRequestSchema,
21
22
  } from "@modelcontextprotocol/sdk/types.js";
22
23
  import { StandardSchemaV1 } from "@standard-schema/spec";
23
- import { toJsonSchema } from "xsschema";
24
- import { z } from "zod";
25
- import { setTimeout as delay } from "timers/promises";
26
- import { readFile } from "fs/promises";
27
- import { fileTypeFromBuffer } from "file-type";
28
- import { StrictEventEmitter } from "strict-event-emitter-types";
29
24
  import { EventEmitter } from "events";
25
+ import { fileTypeFromBuffer } from "file-type";
26
+ import { readFile } from "fs/promises";
30
27
  import Fuse from "fuse.js";
31
- import { startSSEServer } from "mcp-proxy";
32
- import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
33
- import parseURITemplate from "uri-templates";
34
28
  import http from "http";
29
+ import { startSSEServer } from "mcp-proxy";
30
+ import { StrictEventEmitter } from "strict-event-emitter-types";
31
+ import { setTimeout as delay } from "timers/promises";
35
32
  import { fetch } from "undici";
33
+ import parseURITemplate from "uri-templates";
34
+ import { toJsonSchema } from "xsschema";
35
+ import { z } from "zod";
36
36
 
37
37
  export type SSEServer = {
38
38
  close: () => Promise<void>;
@@ -44,15 +44,15 @@ type FastMCPEvents<T extends FastMCPSessionAuth> = {
44
44
  };
45
45
 
46
46
  type FastMCPSessionEvents = {
47
- rootsChanged: (event: { roots: Root[] }) => void;
48
47
  error: (event: { error: Error }) => void;
48
+ rootsChanged: (event: { roots: Root[] }) => void;
49
49
  };
50
50
 
51
51
  /**
52
52
  * Generates an image content object from a URL, file path, or buffer.
53
53
  */
54
54
  export const imageContent = async (
55
- input: { url: string } | { path: string } | { buffer: Buffer },
55
+ input: { buffer: Buffer } | { path: string } | { url: string },
56
56
  ): Promise<ImageContent> => {
57
57
  let rawData: Buffer;
58
58
 
@@ -79,14 +79,14 @@ export const imageContent = async (
79
79
  const base64Data = rawData.toString("base64");
80
80
 
81
81
  return {
82
- type: "image",
83
82
  data: base64Data,
84
83
  mimeType: mimeType?.mime ?? "image/png",
84
+ type: "image",
85
85
  } as const;
86
86
  };
87
87
 
88
88
  export const audioContent = async (
89
- input: { url: string } | { path: string } | { buffer: Buffer },
89
+ input: { buffer: Buffer } | { path: string } | { url: string },
90
90
  ): Promise<AudioContent> => {
91
91
  let rawData: Buffer;
92
92
 
@@ -113,47 +113,29 @@ export const audioContent = async (
113
113
  const base64Data = rawData.toString("base64");
114
114
 
115
115
  return {
116
- type: "audio",
117
116
  data: base64Data,
118
117
  mimeType: mimeType?.mime ?? "audio/mpeg",
118
+ type: "audio",
119
119
  } as const;
120
120
  };
121
121
 
122
- abstract class FastMCPError extends Error {
123
- public constructor(message?: string) {
124
- super(message);
125
- this.name = new.target.name;
126
- }
127
- }
122
+ type Context<T extends FastMCPSessionAuth> = {
123
+ log: {
124
+ debug: (message: string, data?: SerializableValue) => void;
125
+ error: (message: string, data?: SerializableValue) => void;
126
+ info: (message: string, data?: SerializableValue) => void;
127
+ warn: (message: string, data?: SerializableValue) => void;
128
+ };
129
+ reportProgress: (progress: Progress) => Promise<void>;
130
+ session: T | undefined;
131
+ };
128
132
 
129
133
  type Extra = unknown;
130
134
 
131
135
  type Extras = Record<string, Extra>;
132
136
 
133
- export class UnexpectedStateError extends FastMCPError {
134
- public extras?: Extras;
135
-
136
- public constructor(message: string, extras?: Extras) {
137
- super(message);
138
- this.name = new.target.name;
139
- this.extras = extras;
140
- }
141
- }
142
-
143
- /**
144
- * An error that is meant to be surfaced to the user.
145
- */
146
- export class UserError extends UnexpectedStateError {}
147
-
148
- type ToolParameters = StandardSchemaV1;
149
-
150
137
  type Literal = boolean | null | number | string | undefined;
151
138
 
152
- type SerializableValue =
153
- | Literal
154
- | SerializableValue[]
155
- | { [key: string]: SerializableValue };
156
-
157
139
  type Progress = {
158
140
  /**
159
141
  * The progress thus far. This should increase every time progress is made, even if the total is unknown.
@@ -165,41 +147,58 @@ type Progress = {
165
147
  total?: number;
166
148
  };
167
149
 
168
- type Context<T extends FastMCPSessionAuth> = {
169
- session: T | undefined;
170
- reportProgress: (progress: Progress) => Promise<void>;
171
- log: {
172
- debug: (message: string, data?: SerializableValue) => void;
173
- error: (message: string, data?: SerializableValue) => void;
174
- info: (message: string, data?: SerializableValue) => void;
175
- warn: (message: string, data?: SerializableValue) => void;
176
- };
177
- };
150
+ type SerializableValue =
151
+ | { [key: string]: SerializableValue }
152
+ | Literal
153
+ | SerializableValue[];
178
154
 
179
155
  type TextContent = {
180
- type: "text";
181
156
  text: string;
157
+ type: "text";
182
158
  };
183
159
 
160
+ type ToolParameters = StandardSchemaV1;
161
+
162
+ abstract class FastMCPError extends Error {
163
+ public constructor(message?: string) {
164
+ super(message);
165
+ this.name = new.target.name;
166
+ }
167
+ }
168
+
169
+ export class UnexpectedStateError extends FastMCPError {
170
+ public extras?: Extras;
171
+
172
+ public constructor(message: string, extras?: Extras) {
173
+ super(message);
174
+ this.name = new.target.name;
175
+ this.extras = extras;
176
+ }
177
+ }
178
+
179
+ /**
180
+ * An error that is meant to be surfaced to the user.
181
+ */
182
+ export class UserError extends UnexpectedStateError {}
183
+
184
184
  const TextContentZodSchema = z
185
185
  .object({
186
- type: z.literal("text"),
187
186
  /**
188
187
  * The text content of the message.
189
188
  */
190
189
  text: z.string(),
190
+ type: z.literal("text"),
191
191
  })
192
192
  .strict() satisfies z.ZodType<TextContent>;
193
193
 
194
194
  type ImageContent = {
195
- type: "image";
196
195
  data: string;
197
196
  mimeType: string;
197
+ type: "image";
198
198
  };
199
199
 
200
200
  const ImageContentZodSchema = z
201
201
  .object({
202
- type: z.literal("image"),
203
202
  /**
204
203
  * The base64-encoded image data.
205
204
  */
@@ -208,10 +207,11 @@ const ImageContentZodSchema = z
208
207
  * The MIME type of the image. Different providers may support different image types.
209
208
  */
210
209
  mimeType: z.string(),
210
+ type: z.literal("image"),
211
211
  })
212
212
  .strict() satisfies z.ZodType<ImageContent>;
213
213
 
214
- type Content = TextContent | ImageContent;
214
+ type Content = ImageContent | TextContent;
215
215
 
216
216
  const ContentZodSchema = z.discriminatedUnion("type", [
217
217
  TextContentZodSchema,
@@ -231,9 +231,9 @@ const ContentResultZodSchema = z
231
231
  .strict() satisfies z.ZodType<ContentResult>;
232
232
 
233
233
  type Completion = {
234
- values: string[];
235
- total?: number;
236
234
  hasMore?: boolean;
235
+ total?: number;
236
+ values: string[];
237
237
  };
238
238
 
239
239
  /**
@@ -241,140 +241,85 @@ type Completion = {
241
241
  */
242
242
  const CompletionZodSchema = z.object({
243
243
  /**
244
- * An array of completion values. Must not exceed 100 items.
244
+ * Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown.
245
245
  */
246
- values: z.array(z.string()).max(100),
246
+ hasMore: z.optional(z.boolean()),
247
247
  /**
248
248
  * The total number of completion options available. This can exceed the number of values actually sent in the response.
249
249
  */
250
250
  total: z.optional(z.number().int()),
251
251
  /**
252
- * Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown.
252
+ * An array of completion values. Must not exceed 100 items.
253
253
  */
254
- hasMore: z.optional(z.boolean()),
254
+ values: z.array(z.string()).max(100),
255
255
  }) satisfies z.ZodType<Completion>;
256
256
 
257
- /**
258
- * Tool annotations as defined in MCP Specification (2025-03-26)
259
- * These provide hints about a tool's behavior.
260
- */
261
- type ToolAnnotations = {
262
- /**
263
- * A human-readable title for the tool, useful for UI display
264
- */
265
- title?: string;
266
-
267
- /**
268
- * If true, indicates the tool does not modify its environment
269
- * @default false
270
- */
271
- readOnlyHint?: boolean;
272
-
273
- /**
274
- * If true, the tool may perform destructive updates
275
- * Only meaningful when readOnlyHint is false
276
- * @default true
277
- */
278
- destructiveHint?: boolean;
279
-
280
- /**
281
- * If true, calling the tool repeatedly with the same arguments has no additional effect
282
- * Only meaningful when readOnlyHint is false
283
- * @default false
284
- */
285
- idempotentHint?: boolean;
286
-
287
- /**
288
- * If true, the tool may interact with an "open world" of external entities
289
- * @default true
290
- */
291
- openWorldHint?: boolean;
292
- };
257
+ type ArgumentValueCompleter = (value: string) => Promise<Completion>;
293
258
 
294
- type Tool<
295
- T extends FastMCPSessionAuth,
296
- Params extends ToolParameters = ToolParameters,
259
+ type InputPrompt<
260
+ Arguments extends InputPromptArgument[] = InputPromptArgument[],
261
+ Args = PromptArgumentsToObject<Arguments>,
297
262
  > = {
298
- name: string;
263
+ arguments?: InputPromptArgument[];
299
264
  description?: string;
300
- parameters?: Params;
301
- annotations?: ToolAnnotations;
302
- execute: (
303
- args: StandardSchemaV1.InferOutput<Params>,
304
- context: Context<T>,
305
- ) => Promise<
306
- string | ContentResult | TextContent | ImageContent | AudioContent
307
- >;
265
+ load: (args: Args) => Promise<string>;
266
+ name: string;
308
267
  };
309
268
 
310
- type ResourceResult =
311
- | {
312
- text: string;
313
- }
314
- | {
315
- blob: string;
316
- };
317
-
318
- type InputResourceTemplateArgument = Readonly<{
319
- name: string;
320
- description?: string;
269
+ type InputPromptArgument = Readonly<{
321
270
  complete?: ArgumentValueCompleter;
322
- }>;
323
-
324
- type ResourceTemplateArgument = Readonly<{
325
- name: string;
326
271
  description?: string;
327
- complete?: ArgumentValueCompleter;
272
+ enum?: string[];
273
+ name: string;
274
+ required?: boolean;
328
275
  }>;
329
276
 
330
- type ResourceTemplate<
277
+ type InputResourceTemplate<
331
278
  Arguments extends ResourceTemplateArgument[] = ResourceTemplateArgument[],
332
279
  > = {
333
- uriTemplate: string;
334
- name: string;
335
- description?: string;
336
- mimeType?: string;
337
280
  arguments: Arguments;
338
- complete?: (name: string, value: string) => Promise<Completion>;
281
+ description?: string;
339
282
  load: (
340
283
  args: ResourceTemplateArgumentsToObject<Arguments>,
341
284
  ) => Promise<ResourceResult>;
285
+ mimeType?: string;
286
+ name: string;
287
+ uriTemplate: string;
342
288
  };
343
289
 
344
- type ResourceTemplateArgumentsToObject<T extends { name: string }[]> = {
345
- [K in T[number]["name"]]: string;
346
- };
290
+ type InputResourceTemplateArgument = Readonly<{
291
+ complete?: ArgumentValueCompleter;
292
+ description?: string;
293
+ name: string;
294
+ }>;
347
295
 
348
- type InputResourceTemplate<
349
- Arguments extends ResourceTemplateArgument[] = ResourceTemplateArgument[],
296
+ type LoggingLevel =
297
+ | "alert"
298
+ | "critical"
299
+ | "debug"
300
+ | "emergency"
301
+ | "error"
302
+ | "info"
303
+ | "notice"
304
+ | "warning";
305
+
306
+ type Prompt<
307
+ Arguments extends PromptArgument[] = PromptArgument[],
308
+ Args = PromptArgumentsToObject<Arguments>,
350
309
  > = {
351
- uriTemplate: string;
352
- name: string;
310
+ arguments?: PromptArgument[];
311
+ complete?: (name: string, value: string) => Promise<Completion>;
353
312
  description?: string;
354
- mimeType?: string;
355
- arguments: Arguments;
356
- load: (
357
- args: ResourceTemplateArgumentsToObject<Arguments>,
358
- ) => Promise<ResourceResult>;
359
- };
360
-
361
- type Resource = {
362
- uri: string;
313
+ load: (args: Args) => Promise<string>;
363
314
  name: string;
364
- description?: string;
365
- mimeType?: string;
366
- load: () => Promise<ResourceResult | ResourceResult[]>;
367
- complete?: (name: string, value: string) => Promise<Completion>;
368
315
  };
369
316
 
370
- type ArgumentValueCompleter = (value: string) => Promise<Completion>;
371
-
372
- type InputPromptArgument = Readonly<{
373
- name: string;
374
- description?: string;
375
- required?: boolean;
317
+ type PromptArgument = Readonly<{
376
318
  complete?: ArgumentValueCompleter;
319
+ description?: string;
377
320
  enum?: string[];
321
+ name: string;
322
+ required?: boolean;
378
323
  }>;
379
324
 
380
325
  type PromptArgumentsToObject<T extends { name: string; required?: boolean }[]> =
@@ -387,98 +332,171 @@ type PromptArgumentsToObject<T extends { name: string; required?: boolean }[]> =
387
332
  : string | undefined;
388
333
  };
389
334
 
390
- type InputPrompt<
391
- Arguments extends InputPromptArgument[] = InputPromptArgument[],
392
- Args = PromptArgumentsToObject<Arguments>,
393
- > = {
394
- name: string;
395
- description?: string;
396
- arguments?: InputPromptArgument[];
397
- load: (args: Args) => Promise<string>;
335
+ type Resource = {
336
+ complete?: (name: string, value: string) => Promise<Completion>;
337
+ description?: string;
338
+ load: () => Promise<ResourceResult | ResourceResult[]>;
339
+ mimeType?: string;
340
+ name: string;
341
+ uri: string;
398
342
  };
399
343
 
400
- type PromptArgument = Readonly<{
401
- name: string;
402
- description?: string;
403
- required?: boolean;
404
- complete?: ArgumentValueCompleter;
405
- enum?: string[];
406
- }>;
344
+ type ResourceResult =
345
+ | {
346
+ blob: string;
347
+ }
348
+ | {
349
+ text: string;
350
+ };
407
351
 
408
- type Prompt<
409
- Arguments extends PromptArgument[] = PromptArgument[],
410
- Args = PromptArgumentsToObject<Arguments>,
352
+ type ResourceTemplate<
353
+ Arguments extends ResourceTemplateArgument[] = ResourceTemplateArgument[],
411
354
  > = {
412
- arguments?: PromptArgument[];
355
+ arguments: Arguments;
413
356
  complete?: (name: string, value: string) => Promise<Completion>;
414
357
  description?: string;
415
- load: (args: Args) => Promise<string>;
358
+ load: (
359
+ args: ResourceTemplateArgumentsToObject<Arguments>,
360
+ ) => Promise<ResourceResult>;
361
+ mimeType?: string;
416
362
  name: string;
363
+ uriTemplate: string;
417
364
  };
418
365
 
419
- type ServerOptions<T extends FastMCPSessionAuth> = {
366
+ type ResourceTemplateArgument = Readonly<{
367
+ complete?: ArgumentValueCompleter;
368
+ description?: string;
420
369
  name: string;
421
- version: `${number}.${number}.${number}`;
370
+ }>;
371
+
372
+ type ResourceTemplateArgumentsToObject<T extends { name: string }[]> = {
373
+ [K in T[number]["name"]]: string;
374
+ };
375
+
376
+ type ServerOptions<T extends FastMCPSessionAuth> = {
422
377
  authenticate?: Authenticate<T>;
423
378
  instructions?: string;
379
+ name: string;
380
+ version: `${number}.${number}.${number}`;
424
381
  };
425
382
 
426
- type LoggingLevel =
427
- | "debug"
428
- | "info"
429
- | "notice"
430
- | "warning"
431
- | "error"
432
- | "critical"
433
- | "alert"
434
- | "emergency";
383
+ type Tool<
384
+ T extends FastMCPSessionAuth,
385
+ Params extends ToolParameters = ToolParameters,
386
+ > = {
387
+ annotations?: ToolAnnotations;
388
+ description?: string;
389
+ execute: (
390
+ args: StandardSchemaV1.InferOutput<Params>,
391
+ context: Context<T>,
392
+ ) => Promise<
393
+ AudioContent | ContentResult | ImageContent | string | TextContent
394
+ >;
395
+ name: string;
396
+ parameters?: Params;
397
+ };
398
+
399
+ /**
400
+ * Tool annotations as defined in MCP Specification (2025-03-26)
401
+ * These provide hints about a tool's behavior.
402
+ */
403
+ type ToolAnnotations = {
404
+ /**
405
+ * If true, the tool may perform destructive updates
406
+ * Only meaningful when readOnlyHint is false
407
+ * @default true
408
+ */
409
+ destructiveHint?: boolean;
410
+
411
+ /**
412
+ * If true, calling the tool repeatedly with the same arguments has no additional effect
413
+ * Only meaningful when readOnlyHint is false
414
+ * @default false
415
+ */
416
+ idempotentHint?: boolean;
417
+
418
+ /**
419
+ * If true, the tool may interact with an "open world" of external entities
420
+ * @default true
421
+ */
422
+ openWorldHint?: boolean;
423
+
424
+ /**
425
+ * If true, indicates the tool does not modify its environment
426
+ * @default false
427
+ */
428
+ readOnlyHint?: boolean;
429
+
430
+ /**
431
+ * A human-readable title for the tool, useful for UI display
432
+ */
433
+ title?: string;
434
+ };
435
435
 
436
436
  const FastMCPSessionEventEmitterBase: {
437
437
  new (): StrictEventEmitter<EventEmitter, FastMCPSessionEvents>;
438
438
  } = EventEmitter;
439
439
 
440
- class FastMCPSessionEventEmitter extends FastMCPSessionEventEmitterBase {}
440
+ type FastMCPSessionAuth = Record<string, unknown> | undefined;
441
441
 
442
442
  type SamplingResponse = {
443
+ content: AudioContent | ImageContent | TextContent;
443
444
  model: string;
444
- stopReason?: "endTurn" | "stopSequence" | "maxTokens" | string;
445
- role: "user" | "assistant";
446
- content: TextContent | ImageContent | AudioContent;
445
+ role: "assistant" | "user";
446
+ stopReason?: "endTurn" | "maxTokens" | "stopSequence" | string;
447
447
  };
448
448
 
449
- type FastMCPSessionAuth = Record<string, unknown> | undefined;
449
+ class FastMCPSessionEventEmitter extends FastMCPSessionEventEmitterBase {}
450
450
 
451
451
  export class FastMCPSession<
452
452
  T extends FastMCPSessionAuth = FastMCPSessionAuth,
453
453
  > extends FastMCPSessionEventEmitter {
454
+ public get clientCapabilities(): ClientCapabilities | null {
455
+ return this.#clientCapabilities ?? null;
456
+ }
457
+ public get loggingLevel(): LoggingLevel {
458
+ return this.#loggingLevel;
459
+ }
460
+ public get roots(): Root[] {
461
+ return this.#roots;
462
+ }
463
+ public get server(): Server {
464
+ return this.#server;
465
+ }
466
+ #auth: T | undefined;
454
467
  #capabilities: ServerCapabilities = {};
455
468
  #clientCapabilities?: ClientCapabilities;
456
469
  #loggingLevel: LoggingLevel = "info";
470
+ #pingInterval: null | ReturnType<typeof setInterval> = null;
471
+
457
472
  #prompts: Prompt[] = [];
473
+
458
474
  #resources: Resource[] = [];
475
+
459
476
  #resourceTemplates: ResourceTemplate[] = [];
477
+
460
478
  #roots: Root[] = [];
479
+
461
480
  #server: Server;
462
- #auth: T | undefined;
463
481
 
464
482
  constructor({
465
483
  auth,
466
- name,
467
- version,
468
484
  instructions,
469
- tools,
485
+ name,
486
+ prompts,
470
487
  resources,
471
488
  resourcesTemplates,
472
- prompts,
489
+ tools,
490
+ version,
473
491
  }: {
474
492
  auth?: T;
475
- name: string;
476
- version: string;
477
493
  instructions?: string;
478
- tools: Tool<T>[];
494
+ name: string;
495
+ prompts: Prompt[];
479
496
  resources: Resource[];
480
497
  resourcesTemplates: InputResourceTemplate[];
481
- prompts: Prompt[];
498
+ tools: Tool<T>[];
499
+ version: string;
482
500
  }) {
483
501
  super();
484
502
 
@@ -537,92 +555,16 @@ export class FastMCPSession<
537
555
  }
538
556
  }
539
557
 
540
- private addResource(inputResource: Resource) {
541
- this.#resources.push(inputResource);
542
- }
543
-
544
- private addResourceTemplate(inputResourceTemplate: InputResourceTemplate) {
545
- const completers: Record<string, ArgumentValueCompleter> = {};
546
-
547
- for (const argument of inputResourceTemplate.arguments ?? []) {
548
- if (argument.complete) {
549
- completers[argument.name] = argument.complete;
550
- }
558
+ public async close() {
559
+ if (this.#pingInterval) {
560
+ clearInterval(this.#pingInterval);
551
561
  }
552
562
 
553
- const resourceTemplate = {
554
- ...inputResourceTemplate,
555
- complete: async (name: string, value: string) => {
556
- if (completers[name]) {
557
- return await completers[name](value);
558
- }
559
-
560
- return {
561
- values: [],
562
- };
563
- },
564
- };
565
-
566
- this.#resourceTemplates.push(resourceTemplate);
567
- }
568
-
569
- private addPrompt(inputPrompt: InputPrompt) {
570
- const completers: Record<string, ArgumentValueCompleter> = {};
571
- const enums: Record<string, string[]> = {};
572
-
573
- for (const argument of inputPrompt.arguments ?? []) {
574
- if (argument.complete) {
575
- completers[argument.name] = argument.complete;
576
- }
577
-
578
- if (argument.enum) {
579
- enums[argument.name] = argument.enum;
580
- }
563
+ try {
564
+ await this.#server.close();
565
+ } catch (error) {
566
+ console.error("[FastMCP error]", "could not close server", error);
581
567
  }
582
-
583
- const prompt = {
584
- ...inputPrompt,
585
- complete: async (name: string, value: string) => {
586
- if (completers[name]) {
587
- return await completers[name](value);
588
- }
589
-
590
- if (enums[name]) {
591
- const fuse = new Fuse(enums[name], {
592
- keys: ["value"],
593
- });
594
-
595
- const result = fuse.search(value);
596
-
597
- return {
598
- values: result.map((item) => item.item),
599
- total: result.length,
600
- };
601
- }
602
-
603
- return {
604
- values: [],
605
- };
606
- },
607
- };
608
-
609
- this.#prompts.push(prompt);
610
- }
611
-
612
- public get clientCapabilities(): ClientCapabilities | null {
613
- return this.#clientCapabilities ?? null;
614
- }
615
-
616
- public get server(): Server {
617
- return this.#server;
618
- }
619
-
620
- #pingInterval: ReturnType<typeof setInterval> | null = null;
621
-
622
- public async requestSampling(
623
- message: z.infer<typeof CreateMessageRequestSchema>["params"],
624
- ): Promise<SamplingResponse> {
625
- return this.#server.createMessage(message);
626
568
  }
627
569
 
628
570
  public async connect(transport: Transport) {
@@ -675,30 +617,82 @@ export class FastMCPSession<
675
617
  }
676
618
  }
677
619
 
678
- public get roots(): Root[] {
679
- return this.#roots;
620
+ public async requestSampling(
621
+ message: z.infer<typeof CreateMessageRequestSchema>["params"],
622
+ ): Promise<SamplingResponse> {
623
+ return this.#server.createMessage(message);
680
624
  }
681
625
 
682
- public async close() {
683
- if (this.#pingInterval) {
684
- clearInterval(this.#pingInterval);
685
- }
626
+ private addPrompt(inputPrompt: InputPrompt) {
627
+ const completers: Record<string, ArgumentValueCompleter> = {};
628
+ const enums: Record<string, string[]> = {};
686
629
 
687
- try {
688
- await this.#server.close();
689
- } catch (error) {
690
- console.error("[FastMCP error]", "could not close server", error);
630
+ for (const argument of inputPrompt.arguments ?? []) {
631
+ if (argument.complete) {
632
+ completers[argument.name] = argument.complete;
633
+ }
634
+
635
+ if (argument.enum) {
636
+ enums[argument.name] = argument.enum;
637
+ }
691
638
  }
692
- }
693
639
 
694
- private setupErrorHandling() {
695
- this.#server.onerror = (error) => {
696
- console.error("[FastMCP error]", error);
640
+ const prompt = {
641
+ ...inputPrompt,
642
+ complete: async (name: string, value: string) => {
643
+ if (completers[name]) {
644
+ return await completers[name](value);
645
+ }
646
+
647
+ if (enums[name]) {
648
+ const fuse = new Fuse(enums[name], {
649
+ keys: ["value"],
650
+ });
651
+
652
+ const result = fuse.search(value);
653
+
654
+ return {
655
+ total: result.length,
656
+ values: result.map((item) => item.item),
657
+ };
658
+ }
659
+
660
+ return {
661
+ values: [],
662
+ };
663
+ },
697
664
  };
665
+
666
+ this.#prompts.push(prompt);
698
667
  }
699
668
 
700
- public get loggingLevel(): LoggingLevel {
701
- return this.#loggingLevel;
669
+ private addResource(inputResource: Resource) {
670
+ this.#resources.push(inputResource);
671
+ }
672
+
673
+ private addResourceTemplate(inputResourceTemplate: InputResourceTemplate) {
674
+ const completers: Record<string, ArgumentValueCompleter> = {};
675
+
676
+ for (const argument of inputResourceTemplate.arguments ?? []) {
677
+ if (argument.complete) {
678
+ completers[argument.name] = argument.complete;
679
+ }
680
+ }
681
+
682
+ const resourceTemplate = {
683
+ ...inputResourceTemplate,
684
+ complete: async (name: string, value: string) => {
685
+ if (completers[name]) {
686
+ return await completers[name](value);
687
+ }
688
+
689
+ return {
690
+ values: [],
691
+ };
692
+ },
693
+ };
694
+
695
+ this.#resourceTemplates.push(resourceTemplate);
702
696
  }
703
697
 
704
698
  private setupCompleteHandlers() {
@@ -774,19 +768,10 @@ export class FastMCPSession<
774
768
  });
775
769
  }
776
770
 
777
- private setupRootsHandlers() {
778
- this.#server.setNotificationHandler(
779
- RootsListChangedNotificationSchema,
780
- () => {
781
- this.#server.listRoots().then((roots) => {
782
- this.#roots = roots.roots;
783
-
784
- this.emit("rootsChanged", {
785
- roots: roots.roots,
786
- });
787
- });
788
- },
789
- );
771
+ private setupErrorHandling() {
772
+ this.#server.onerror = (error) => {
773
+ console.error("[FastMCP error]", error);
774
+ };
790
775
  }
791
776
 
792
777
  private setupLoggingHandlers() {
@@ -797,137 +782,63 @@ export class FastMCPSession<
797
782
  });
798
783
  }
799
784
 
800
- private setupToolHandlers(tools: Tool<T>[]) {
801
- this.#server.setRequestHandler(ListToolsRequestSchema, async () => {
785
+ private setupPromptHandlers(prompts: Prompt[]) {
786
+ this.#server.setRequestHandler(ListPromptsRequestSchema, async () => {
802
787
  return {
803
- tools: await Promise.all(
804
- tools.map(async (tool) => {
805
- return {
806
- name: tool.name,
807
- description: tool.description,
808
- inputSchema: tool.parameters
809
- ? await toJsonSchema(tool.parameters)
810
- : undefined,
811
- annotations: tool.annotations,
812
- };
813
- }),
814
- ),
788
+ prompts: prompts.map((prompt) => {
789
+ return {
790
+ arguments: prompt.arguments,
791
+ complete: prompt.complete,
792
+ description: prompt.description,
793
+ name: prompt.name,
794
+ };
795
+ }),
815
796
  };
816
797
  });
817
798
 
818
- this.#server.setRequestHandler(CallToolRequestSchema, async (request) => {
819
- const tool = tools.find((tool) => tool.name === request.params.name);
799
+ this.#server.setRequestHandler(GetPromptRequestSchema, async (request) => {
800
+ const prompt = prompts.find(
801
+ (prompt) => prompt.name === request.params.name,
802
+ );
820
803
 
821
- if (!tool) {
804
+ if (!prompt) {
822
805
  throw new McpError(
823
806
  ErrorCode.MethodNotFound,
824
- `Unknown tool: ${request.params.name}`,
807
+ `Unknown prompt: ${request.params.name}`,
825
808
  );
826
809
  }
827
810
 
828
- let args: any = undefined;
829
-
830
- if (tool.parameters) {
831
- const parsed = await tool.parameters["~standard"].validate(
832
- request.params.arguments,
833
- );
811
+ const args = request.params.arguments;
834
812
 
835
- if (parsed.issues) {
813
+ for (const arg of prompt.arguments ?? []) {
814
+ if (arg.required && !(args && arg.name in args)) {
836
815
  throw new McpError(
837
- ErrorCode.InvalidParams,
838
- `Invalid ${request.params.name} parameters`,
816
+ ErrorCode.InvalidRequest,
817
+ `Missing required argument: ${arg.name}`,
839
818
  );
840
819
  }
841
-
842
- args = parsed.value;
843
820
  }
844
821
 
845
- const progressToken = request.params?._meta?.progressToken;
846
-
847
- let result: ContentResult;
848
-
849
- try {
850
- const reportProgress = async (progress: Progress) => {
851
- await this.#server.notification({
852
- method: "notifications/progress",
853
- params: {
854
- ...progress,
855
- progressToken,
856
- },
857
- });
858
- };
859
-
860
- const log = {
861
- debug: (message: string, context?: SerializableValue) => {
862
- this.#server.sendLoggingMessage({
863
- level: "debug",
864
- data: {
865
- message,
866
- context,
867
- },
868
- });
869
- },
870
- error: (message: string, context?: SerializableValue) => {
871
- this.#server.sendLoggingMessage({
872
- level: "error",
873
- data: {
874
- message,
875
- context,
876
- },
877
- });
878
- },
879
- info: (message: string, context?: SerializableValue) => {
880
- this.#server.sendLoggingMessage({
881
- level: "info",
882
- data: {
883
- message,
884
- context,
885
- },
886
- });
887
- },
888
- warn: (message: string, context?: SerializableValue) => {
889
- this.#server.sendLoggingMessage({
890
- level: "warning",
891
- data: {
892
- message,
893
- context,
894
- },
895
- });
896
- },
897
- };
898
-
899
- const maybeStringResult = await tool.execute(args, {
900
- reportProgress,
901
- log,
902
- session: this.#auth,
903
- });
822
+ let result: Awaited<ReturnType<Prompt["load"]>>;
904
823
 
905
- if (typeof maybeStringResult === "string") {
906
- result = ContentResultZodSchema.parse({
907
- content: [{ type: "text", text: maybeStringResult }],
908
- });
909
- } else if ("type" in maybeStringResult) {
910
- result = ContentResultZodSchema.parse({
911
- content: [maybeStringResult],
912
- });
913
- } else {
914
- result = ContentResultZodSchema.parse(maybeStringResult);
915
- }
824
+ try {
825
+ result = await prompt.load(args as Record<string, string | undefined>);
916
826
  } catch (error) {
917
- if (error instanceof UserError) {
918
- return {
919
- content: [{ type: "text", text: error.message }],
920
- isError: true,
921
- };
922
- }
923
-
924
- return {
925
- content: [{ type: "text", text: `Error: ${error}` }],
926
- isError: true,
927
- };
827
+ throw new McpError(
828
+ ErrorCode.InternalError,
829
+ `Error loading prompt: ${error}`,
830
+ );
928
831
  }
929
832
 
930
- return result;
833
+ return {
834
+ description: prompt.description,
835
+ messages: [
836
+ {
837
+ content: { text: result, type: "text" },
838
+ role: "user",
839
+ },
840
+ ],
841
+ };
931
842
  });
932
843
  }
933
844
 
@@ -936,9 +847,9 @@ export class FastMCPSession<
936
847
  return {
937
848
  resources: resources.map((resource) => {
938
849
  return {
939
- uri: resource.uri,
940
- name: resource.name,
941
850
  mimeType: resource.mimeType,
851
+ name: resource.name,
852
+ uri: resource.uri,
942
853
  };
943
854
  }),
944
855
  };
@@ -972,9 +883,9 @@ export class FastMCPSession<
972
883
  return {
973
884
  contents: [
974
885
  {
975
- uri: uri,
976
886
  mimeType: resourceTemplate.mimeType,
977
887
  name: resourceTemplate.name,
888
+ uri: uri,
978
889
  ...result,
979
890
  },
980
891
  ],
@@ -1008,9 +919,9 @@ export class FastMCPSession<
1008
919
  if (Array.isArray(maybeArrayResult)) {
1009
920
  return {
1010
921
  contents: maybeArrayResult.map((result) => ({
1011
- uri: resource.uri,
1012
922
  mimeType: resource.mimeType,
1013
923
  name: resource.name,
924
+ uri: resource.uri,
1014
925
  ...result,
1015
926
  })),
1016
927
  };
@@ -1018,9 +929,9 @@ export class FastMCPSession<
1018
929
  return {
1019
930
  contents: [
1020
931
  {
1021
- uri: resource.uri,
1022
932
  mimeType: resource.mimeType,
1023
933
  name: resource.name,
934
+ uri: resource.uri,
1024
935
  ...maybeArrayResult,
1025
936
  },
1026
937
  ],
@@ -1051,63 +962,152 @@ export class FastMCPSession<
1051
962
  );
1052
963
  }
1053
964
 
1054
- private setupPromptHandlers(prompts: Prompt[]) {
1055
- this.#server.setRequestHandler(ListPromptsRequestSchema, async () => {
965
+ private setupRootsHandlers() {
966
+ this.#server.setNotificationHandler(
967
+ RootsListChangedNotificationSchema,
968
+ () => {
969
+ this.#server.listRoots().then((roots) => {
970
+ this.#roots = roots.roots;
971
+
972
+ this.emit("rootsChanged", {
973
+ roots: roots.roots,
974
+ });
975
+ });
976
+ },
977
+ );
978
+ }
979
+
980
+ private setupToolHandlers(tools: Tool<T>[]) {
981
+ this.#server.setRequestHandler(ListToolsRequestSchema, async () => {
1056
982
  return {
1057
- prompts: prompts.map((prompt) => {
1058
- return {
1059
- name: prompt.name,
1060
- description: prompt.description,
1061
- arguments: prompt.arguments,
1062
- complete: prompt.complete,
1063
- };
1064
- }),
983
+ tools: await Promise.all(
984
+ tools.map(async (tool) => {
985
+ return {
986
+ annotations: tool.annotations,
987
+ description: tool.description,
988
+ inputSchema: tool.parameters
989
+ ? await toJsonSchema(tool.parameters)
990
+ : undefined,
991
+ name: tool.name,
992
+ };
993
+ }),
994
+ ),
1065
995
  };
1066
996
  });
1067
997
 
1068
- this.#server.setRequestHandler(GetPromptRequestSchema, async (request) => {
1069
- const prompt = prompts.find(
1070
- (prompt) => prompt.name === request.params.name,
1071
- );
998
+ this.#server.setRequestHandler(CallToolRequestSchema, async (request) => {
999
+ const tool = tools.find((tool) => tool.name === request.params.name);
1072
1000
 
1073
- if (!prompt) {
1001
+ if (!tool) {
1074
1002
  throw new McpError(
1075
1003
  ErrorCode.MethodNotFound,
1076
- `Unknown prompt: ${request.params.name}`,
1004
+ `Unknown tool: ${request.params.name}`,
1077
1005
  );
1078
1006
  }
1079
1007
 
1080
- const args = request.params.arguments;
1008
+ let args: unknown = undefined;
1081
1009
 
1082
- for (const arg of prompt.arguments ?? []) {
1083
- if (arg.required && !(args && arg.name in args)) {
1010
+ if (tool.parameters) {
1011
+ const parsed = await tool.parameters["~standard"].validate(
1012
+ request.params.arguments,
1013
+ );
1014
+
1015
+ if (parsed.issues) {
1084
1016
  throw new McpError(
1085
- ErrorCode.InvalidRequest,
1086
- `Missing required argument: ${arg.name}`,
1017
+ ErrorCode.InvalidParams,
1018
+ `Invalid ${request.params.name} parameters: ${JSON.stringify(parsed.issues)}`,
1087
1019
  );
1088
1020
  }
1021
+
1022
+ args = parsed.value;
1089
1023
  }
1090
1024
 
1091
- let result: Awaited<ReturnType<Prompt["load"]>>;
1025
+ const progressToken = request.params?._meta?.progressToken;
1026
+
1027
+ let result: ContentResult;
1092
1028
 
1093
1029
  try {
1094
- result = await prompt.load(args as Record<string, string | undefined>);
1030
+ const reportProgress = async (progress: Progress) => {
1031
+ await this.#server.notification({
1032
+ method: "notifications/progress",
1033
+ params: {
1034
+ ...progress,
1035
+ progressToken,
1036
+ },
1037
+ });
1038
+ };
1039
+
1040
+ const log = {
1041
+ debug: (message: string, context?: SerializableValue) => {
1042
+ this.#server.sendLoggingMessage({
1043
+ data: {
1044
+ context,
1045
+ message,
1046
+ },
1047
+ level: "debug",
1048
+ });
1049
+ },
1050
+ error: (message: string, context?: SerializableValue) => {
1051
+ this.#server.sendLoggingMessage({
1052
+ data: {
1053
+ context,
1054
+ message,
1055
+ },
1056
+ level: "error",
1057
+ });
1058
+ },
1059
+ info: (message: string, context?: SerializableValue) => {
1060
+ this.#server.sendLoggingMessage({
1061
+ data: {
1062
+ context,
1063
+ message,
1064
+ },
1065
+ level: "info",
1066
+ });
1067
+ },
1068
+ warn: (message: string, context?: SerializableValue) => {
1069
+ this.#server.sendLoggingMessage({
1070
+ data: {
1071
+ context,
1072
+ message,
1073
+ },
1074
+ level: "warning",
1075
+ });
1076
+ },
1077
+ };
1078
+
1079
+ const maybeStringResult = await tool.execute(args, {
1080
+ log,
1081
+ reportProgress,
1082
+ session: this.#auth,
1083
+ });
1084
+
1085
+ if (typeof maybeStringResult === "string") {
1086
+ result = ContentResultZodSchema.parse({
1087
+ content: [{ text: maybeStringResult, type: "text" }],
1088
+ });
1089
+ } else if ("type" in maybeStringResult) {
1090
+ result = ContentResultZodSchema.parse({
1091
+ content: [maybeStringResult],
1092
+ });
1093
+ } else {
1094
+ result = ContentResultZodSchema.parse(maybeStringResult);
1095
+ }
1095
1096
  } catch (error) {
1096
- throw new McpError(
1097
- ErrorCode.InternalError,
1098
- `Error loading prompt: ${error}`,
1099
- );
1097
+ if (error instanceof UserError) {
1098
+ return {
1099
+ content: [{ text: error.message, type: "text" }],
1100
+ isError: true,
1101
+ };
1102
+ }
1103
+
1104
+ return {
1105
+ content: [{ text: `Error: ${error}`, type: "text" }],
1106
+ isError: true,
1107
+ };
1100
1108
  }
1101
1109
 
1102
- return {
1103
- description: prompt.description,
1104
- messages: [
1105
- {
1106
- role: "user",
1107
- content: { type: "text", text: result },
1108
- },
1109
- ],
1110
- };
1110
+ return result;
1111
1111
  });
1112
1112
  }
1113
1113
  }
@@ -1116,21 +1116,25 @@ const FastMCPEventEmitterBase: {
1116
1116
  new (): StrictEventEmitter<EventEmitter, FastMCPEvents<FastMCPSessionAuth>>;
1117
1117
  } = EventEmitter;
1118
1118
 
1119
- class FastMCPEventEmitter extends FastMCPEventEmitterBase {}
1120
-
1121
1119
  type Authenticate<T> = (request: http.IncomingMessage) => Promise<T>;
1122
1120
 
1121
+ class FastMCPEventEmitter extends FastMCPEventEmitterBase {}
1122
+
1123
1123
  export class FastMCP<
1124
1124
  T extends Record<string, unknown> | undefined = undefined,
1125
1125
  > extends FastMCPEventEmitter {
1126
+ public get sessions(): FastMCPSession<T>[] {
1127
+ return this.#sessions;
1128
+ }
1129
+ #authenticate: Authenticate<T> | undefined;
1126
1130
  #options: ServerOptions<T>;
1127
1131
  #prompts: InputPrompt[] = [];
1128
1132
  #resources: Resource[] = [];
1129
1133
  #resourcesTemplates: InputResourceTemplate[] = [];
1130
1134
  #sessions: FastMCPSession<T>[] = [];
1131
- #sseServer: SSEServer | null = null;
1135
+ #sseServer: null | SSEServer = null;
1136
+
1132
1137
  #tools: Tool<T>[] = [];
1133
- #authenticate: Authenticate<T> | undefined;
1134
1138
 
1135
1139
  constructor(public options: ServerOptions<T>) {
1136
1140
  super();
@@ -1139,15 +1143,13 @@ export class FastMCP<
1139
1143
  this.#authenticate = options.authenticate;
1140
1144
  }
1141
1145
 
1142
- public get sessions(): FastMCPSession<T>[] {
1143
- return this.#sessions;
1144
- }
1145
-
1146
1146
  /**
1147
- * Adds a tool to the server.
1147
+ * Adds a prompt to the server.
1148
1148
  */
1149
- public addTool<Params extends ToolParameters>(tool: Tool<T, Params>) {
1150
- this.#tools.push(tool as unknown as Tool<T>);
1149
+ public addPrompt<const Args extends InputPromptArgument[]>(
1150
+ prompt: InputPrompt<Args>,
1151
+ ) {
1152
+ this.#prompts.push(prompt);
1151
1153
  }
1152
1154
 
1153
1155
  /**
@@ -1167,12 +1169,10 @@ export class FastMCP<
1167
1169
  }
1168
1170
 
1169
1171
  /**
1170
- * Adds a prompt to the server.
1172
+ * Adds a tool to the server.
1171
1173
  */
1172
- public addPrompt<const Args extends InputPromptArgument[]>(
1173
- prompt: InputPrompt<Args>,
1174
- ) {
1175
- this.#prompts.push(prompt);
1174
+ public addTool<Params extends ToolParameters>(tool: Tool<T, Params>) {
1175
+ this.#tools.push(tool as unknown as Tool<T>);
1176
1176
  }
1177
1177
 
1178
1178
  /**
@@ -1180,11 +1180,11 @@ export class FastMCP<
1180
1180
  */
1181
1181
  public async start(
1182
1182
  options:
1183
- | { transportType: "stdio" }
1184
1183
  | {
1185
- transportType: "sse";
1186
1184
  sse: { endpoint: `/${string}`; port: number };
1187
- } = {
1185
+ transportType: "sse";
1186
+ }
1187
+ | { transportType: "stdio" } = {
1188
1188
  transportType: "stdio",
1189
1189
  },
1190
1190
  ) {
@@ -1192,13 +1192,13 @@ export class FastMCP<
1192
1192
  const transport = new StdioServerTransport();
1193
1193
 
1194
1194
  const session = new FastMCPSession<T>({
1195
- name: this.#options.name,
1196
- version: this.#options.version,
1197
1195
  instructions: this.#options.instructions,
1198
- tools: this.#tools,
1196
+ name: this.#options.name,
1197
+ prompts: this.#prompts,
1199
1198
  resources: this.#resources,
1200
1199
  resourcesTemplates: this.#resourcesTemplates,
1201
- prompts: this.#prompts,
1200
+ tools: this.#tools,
1201
+ version: this.#options.version,
1202
1202
  });
1203
1203
 
1204
1204
  await session.connect(transport);
@@ -1210,8 +1210,6 @@ export class FastMCP<
1210
1210
  });
1211
1211
  } else if (options.transportType === "sse") {
1212
1212
  this.#sseServer = await startSSEServer<FastMCPSession<T>>({
1213
- endpoint: options.sse.endpoint as `/${string}`,
1214
- port: options.sse.port,
1215
1213
  createServer: async (request) => {
1216
1214
  let auth: T | undefined;
1217
1215
 
@@ -1222,13 +1220,14 @@ export class FastMCP<
1222
1220
  return new FastMCPSession<T>({
1223
1221
  auth,
1224
1222
  name: this.#options.name,
1225
- version: this.#options.version,
1226
- tools: this.#tools,
1223
+ prompts: this.#prompts,
1227
1224
  resources: this.#resources,
1228
1225
  resourcesTemplates: this.#resourcesTemplates,
1229
- prompts: this.#prompts,
1226
+ tools: this.#tools,
1227
+ version: this.#options.version,
1230
1228
  });
1231
1229
  },
1230
+ endpoint: options.sse.endpoint as `/${string}`,
1232
1231
  onClose: (session) => {
1233
1232
  this.emit("disconnect", {
1234
1233
  session,
@@ -1241,6 +1240,7 @@ export class FastMCP<
1241
1240
  session,
1242
1241
  });
1243
1242
  },
1243
+ port: options.sse.port,
1244
1244
  });
1245
1245
 
1246
1246
  console.info(
@@ -1263,11 +1263,11 @@ export class FastMCP<
1263
1263
 
1264
1264
  export type { Context };
1265
1265
  export type { Tool, ToolParameters };
1266
- export type { Content, TextContent, ImageContent, ContentResult };
1266
+ export type { Content, ContentResult, ImageContent, TextContent };
1267
1267
  export type { Progress, SerializableValue };
1268
1268
  export type { Resource, ResourceResult };
1269
1269
  export type { ResourceTemplate, ResourceTemplateArgument };
1270
1270
  export type { Prompt, PromptArgument };
1271
1271
  export type { InputPrompt, InputPromptArgument };
1272
- export type { ServerOptions, LoggingLevel };
1272
+ export type { LoggingLevel, ServerOptions };
1273
1273
  export type { FastMCPEvents, FastMCPSessionEvents };