fastmcp 1.9.0 → 1.11.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
@@ -3,6 +3,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
3
3
  import {
4
4
  CallToolRequestSchema,
5
5
  ClientCapabilities,
6
+ CompleteRequestSchema,
6
7
  ErrorCode,
7
8
  GetPromptRequestSchema,
8
9
  ListPromptsRequestSchema,
@@ -22,6 +23,7 @@ import { readFile } from "fs/promises";
22
23
  import { fileTypeFromBuffer } from "file-type";
23
24
  import { StrictEventEmitter } from "strict-event-emitter-types";
24
25
  import { EventEmitter } from "events";
26
+ import Fuse from "fuse.js";
25
27
  import { startSSEServer } from "mcp-proxy";
26
28
  import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
27
29
 
@@ -36,6 +38,7 @@ type FastMCPEvents = {
36
38
 
37
39
  type FastMCPSessionEvents = {
38
40
  rootsChanged: (event: { roots: Root[] }) => void;
41
+ error: (event: { error: Error }) => void;
39
42
  };
40
43
 
41
44
  /**
@@ -86,7 +89,7 @@ type Extra = unknown;
86
89
 
87
90
  type Extras = Record<string, Extra>;
88
91
 
89
- class UnexpectedStateError extends FastMCPError {
92
+ export class UnexpectedStateError extends FastMCPError {
90
93
  public extras?: Extras;
91
94
 
92
95
  public constructor(message: string, extras?: Extras) {
@@ -185,6 +188,30 @@ const ContentResultZodSchema = z
185
188
  })
186
189
  .strict() satisfies z.ZodType<ContentResult>;
187
190
 
191
+ type Completion = {
192
+ values: string[];
193
+ total?: number;
194
+ hasMore?: boolean;
195
+ };
196
+
197
+ /**
198
+ * https://github.com/modelcontextprotocol/typescript-sdk/blob/3164da64d085ec4e022ae881329eee7b72f208d4/src/types.ts#L983-L1003
199
+ */
200
+ const CompletionZodSchema = z.object({
201
+ /**
202
+ * An array of completion values. Must not exceed 100 items.
203
+ */
204
+ values: z.array(z.string()).max(100),
205
+ /**
206
+ * The total number of completion options available. This can exceed the number of values actually sent in the response.
207
+ */
208
+ total: z.optional(z.number().int()),
209
+ /**
210
+ * Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown.
211
+ */
212
+ hasMore: z.optional(z.boolean()),
213
+ }) satisfies z.ZodType<Completion>;
214
+
188
215
  type Tool<Params extends ToolParameters = ToolParameters> = {
189
216
  name: string;
190
217
  description?: string;
@@ -203,13 +230,17 @@ type Resource = {
203
230
  load: () => Promise<{ text: string } | { blob: string }>;
204
231
  };
205
232
 
206
- type PromptArgument = Readonly<{
233
+ type ArgumentValueCompleter = (value: string) => Promise<Completion>;
234
+
235
+ type InputPromptArgument = Readonly<{
207
236
  name: string;
208
237
  description?: string;
209
238
  required?: boolean;
239
+ complete?: ArgumentValueCompleter;
240
+ enum?: string[];
210
241
  }>;
211
242
 
212
- type ArgumentsToObject<T extends PromptArgument[]> = {
243
+ type ArgumentsToObject<T extends InputPromptArgument[]> = {
213
244
  [K in T[number]["name"]]: Extract<
214
245
  T[number],
215
246
  { name: K }
@@ -218,14 +249,33 @@ type ArgumentsToObject<T extends PromptArgument[]> = {
218
249
  : string | undefined;
219
250
  };
220
251
 
252
+ type InputPrompt<
253
+ Arguments extends InputPromptArgument[] = InputPromptArgument[],
254
+ Args = ArgumentsToObject<Arguments>,
255
+ > = {
256
+ name: string;
257
+ description?: string;
258
+ arguments?: InputPromptArgument[];
259
+ load: (args: Args) => Promise<string>;
260
+ };
261
+
262
+ type PromptArgument = Readonly<{
263
+ name: string;
264
+ description?: string;
265
+ required?: boolean;
266
+ complete?: ArgumentValueCompleter;
267
+ enum?: string[];
268
+ }>;
269
+
221
270
  type Prompt<
222
271
  Arguments extends PromptArgument[] = PromptArgument[],
223
272
  Args = ArgumentsToObject<Arguments>,
224
273
  > = {
225
- name: string;
274
+ arguments?: PromptArgument[];
275
+ complete?: (name: string, value: string) => Promise<Completion>;
226
276
  description?: string;
227
- arguments?: Arguments;
228
277
  load: (args: Args) => Promise<string>;
278
+ name: string;
229
279
  };
230
280
 
231
281
  type ServerOptions = {
@@ -255,6 +305,7 @@ export class FastMCPSession extends FastMCPSessionEventEmitter {
255
305
  #server: Server;
256
306
  #clientCapabilities?: ClientCapabilities;
257
307
  #roots: Root[] = [];
308
+ #prompts: Prompt[] = [];
258
309
 
259
310
  constructor({
260
311
  name,
@@ -280,6 +331,10 @@ export class FastMCPSession extends FastMCPSessionEventEmitter {
280
331
  }
281
332
 
282
333
  if (prompts.length) {
334
+ for (const prompt of prompts) {
335
+ this.addPrompt(prompt);
336
+ }
337
+
283
338
  this.#capabilities.prompts = {};
284
339
  }
285
340
 
@@ -293,6 +348,7 @@ export class FastMCPSession extends FastMCPSessionEventEmitter {
293
348
  this.setupErrorHandling();
294
349
  this.setupLoggingHandlers();
295
350
  this.setupRootsHandlers();
351
+ this.setupCompleteHandlers();
296
352
 
297
353
  if (tools.length) {
298
354
  this.setupToolHandlers(tools);
@@ -307,6 +363,49 @@ export class FastMCPSession extends FastMCPSessionEventEmitter {
307
363
  }
308
364
  }
309
365
 
366
+ private addPrompt(inputPrompt: InputPrompt) {
367
+ const completers: Record<string, ArgumentValueCompleter> = {};
368
+ const enums: Record<string, string[]> = {};
369
+
370
+ for (const argument of inputPrompt.arguments ?? []) {
371
+ if (argument.complete) {
372
+ completers[argument.name] = argument.complete;
373
+ }
374
+
375
+ if (argument.enum) {
376
+ enums[argument.name] = argument.enum;
377
+ }
378
+ }
379
+
380
+ const prompt = {
381
+ ...inputPrompt,
382
+ complete: async (name: string, value: string) => {
383
+ if (completers[name]) {
384
+ return await completers[name](value);
385
+ }
386
+
387
+ if (enums[name]) {
388
+ const fuse = new Fuse(enums[name], {
389
+ keys: ["value"],
390
+ });
391
+
392
+ const result = fuse.search(value);
393
+
394
+ return {
395
+ values: result.map((item) => item.item),
396
+ total: result.length,
397
+ };
398
+ }
399
+
400
+ return {
401
+ values: [],
402
+ };
403
+ },
404
+ };
405
+
406
+ this.#prompts.push(prompt);
407
+ }
408
+
310
409
  public get clientCapabilities(): ClientCapabilities | null {
311
410
  return this.#clientCapabilities ?? null;
312
411
  }
@@ -315,6 +414,8 @@ export class FastMCPSession extends FastMCPSessionEventEmitter {
315
414
  return this.#server;
316
415
  }
317
416
 
417
+ #pingInterval: ReturnType<typeof setInterval> | null = null;
418
+
318
419
  public async connect(transport: Transport) {
319
420
  if (this.#server.transport) {
320
421
  throw new UnexpectedStateError("Server is already connected");
@@ -336,15 +437,25 @@ export class FastMCPSession extends FastMCPSessionEventEmitter {
336
437
  await delay(100);
337
438
  }
338
439
 
440
+ if (!this.#clientCapabilities) {
441
+ throw new UnexpectedStateError("Server did not connect");
442
+ }
443
+
339
444
  if (this.#clientCapabilities?.roots) {
340
445
  const roots = await this.#server.listRoots();
341
446
 
342
447
  this.#roots = roots.roots;
343
448
  }
344
449
 
345
- if (!this.#clientCapabilities) {
346
- throw new UnexpectedStateError("Server did not connect");
347
- }
450
+ this.#pingInterval = setInterval(async () => {
451
+ try {
452
+ await this.#server.ping();
453
+ } catch (error) {
454
+ this.emit("error", {
455
+ error: error as Error,
456
+ });
457
+ }
458
+ }, 1000);
348
459
  }
349
460
 
350
461
  public get roots(): Root[] {
@@ -352,6 +463,10 @@ export class FastMCPSession extends FastMCPSessionEventEmitter {
352
463
  }
353
464
 
354
465
  public async close() {
466
+ if (this.#pingInterval) {
467
+ clearInterval(this.#pingInterval);
468
+ }
469
+
355
470
  await this.#server.close();
356
471
  }
357
472
 
@@ -365,6 +480,43 @@ export class FastMCPSession extends FastMCPSessionEventEmitter {
365
480
  return this.#loggingLevel;
366
481
  }
367
482
 
483
+ private setupCompleteHandlers() {
484
+ this.#server.setRequestHandler(CompleteRequestSchema, async (request) => {
485
+ if (request.params.ref.type === "ref/prompt") {
486
+ const prompt = this.#prompts.find(
487
+ (prompt) => prompt.name === request.params.ref.name,
488
+ );
489
+
490
+ if (!prompt) {
491
+ throw new UnexpectedStateError("Unknown prompt", {
492
+ request,
493
+ });
494
+ }
495
+
496
+ if (!prompt.complete) {
497
+ throw new UnexpectedStateError("Prompt does not support completion", {
498
+ request,
499
+ });
500
+ }
501
+
502
+ const completion = CompletionZodSchema.parse(
503
+ await prompt.complete(
504
+ request.params.argument.name,
505
+ request.params.argument.value,
506
+ ),
507
+ );
508
+
509
+ return {
510
+ completion,
511
+ };
512
+ }
513
+
514
+ throw new UnexpectedStateError("Unexpected completion request", {
515
+ request,
516
+ });
517
+ });
518
+ }
519
+
368
520
  private setupRootsHandlers() {
369
521
  this.#server.setNotificationHandler(
370
522
  RootsListChangedNotificationSchema,
@@ -578,6 +730,7 @@ export class FastMCPSession extends FastMCPSessionEventEmitter {
578
730
  name: prompt.name,
579
731
  description: prompt.description,
580
732
  arguments: prompt.arguments,
733
+ complete: prompt.complete,
581
734
  };
582
735
  }),
583
736
  };
@@ -597,14 +750,12 @@ export class FastMCPSession extends FastMCPSessionEventEmitter {
597
750
 
598
751
  const args = request.params.arguments;
599
752
 
600
- if (prompt.arguments) {
601
- for (const arg of prompt.arguments) {
602
- if (arg.required && !(args && arg.name in args)) {
603
- throw new McpError(
604
- ErrorCode.InvalidRequest,
605
- `Missing required argument: ${arg.name}`,
606
- );
607
- }
753
+ for (const arg of prompt.arguments ?? []) {
754
+ if (arg.required && !(args && arg.name in args)) {
755
+ throw new McpError(
756
+ ErrorCode.InvalidRequest,
757
+ `Missing required argument: ${arg.name}`,
758
+ );
608
759
  }
609
760
  }
610
761
 
@@ -640,7 +791,7 @@ class FastMCPEventEmitter extends FastMCPEventEmitterBase {}
640
791
 
641
792
  export class FastMCP extends FastMCPEventEmitter {
642
793
  #options: ServerOptions;
643
- #prompts: Prompt[] = [];
794
+ #prompts: InputPrompt[] = [];
644
795
  #resources: Resource[] = [];
645
796
  #sessions: FastMCPSession[] = [];
646
797
  #sseServer: SSEServer | null = null;
@@ -673,7 +824,9 @@ export class FastMCP extends FastMCPEventEmitter {
673
824
  /**
674
825
  * Adds a prompt to the server.
675
826
  */
676
- public addPrompt<const Args extends PromptArgument[]>(prompt: Prompt<Args>) {
827
+ public addPrompt<const Args extends InputPromptArgument[]>(
828
+ prompt: InputPrompt<Args>,
829
+ ) {
677
830
  this.#prompts.push(prompt);
678
831
  }
679
832