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/README.md +60 -0
- package/dist/FastMCP.d.ts +33 -6
- package/dist/FastMCP.js +111 -12
- package/dist/FastMCP.js.map +1 -1
- package/jsr.json +1 -1
- package/package.json +3 -2
- package/src/FastMCP.test.ts +236 -115
- package/src/FastMCP.ts +171 -18
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
346
|
-
|
|
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
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
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:
|
|
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
|
|
827
|
+
public addPrompt<const Args extends InputPromptArgument[]>(
|
|
828
|
+
prompt: InputPrompt<Args>,
|
|
829
|
+
) {
|
|
677
830
|
this.#prompts.push(prompt);
|
|
678
831
|
}
|
|
679
832
|
|