fastmcp 1.6.1 → 1.8.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 +76 -10
- package/dist/FastMCP.d.ts +42 -8
- package/dist/FastMCP.js +184 -103
- package/dist/FastMCP.js.map +1 -1
- package/jsr.json +1 -1
- package/package.json +5 -4
- package/src/FastMCP.test.ts +234 -22
- package/src/FastMCP.ts +233 -113
- package/src/examples/addition.ts +26 -0
- package/vitest.config.js +9 -0
package/src/FastMCP.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
3
|
import {
|
|
4
4
|
CallToolRequestSchema,
|
|
5
|
+
ClientCapabilities,
|
|
5
6
|
ErrorCode,
|
|
6
7
|
GetPromptRequestSchema,
|
|
7
8
|
ListPromptsRequestSchema,
|
|
@@ -9,14 +10,28 @@ import {
|
|
|
9
10
|
ListToolsRequestSchema,
|
|
10
11
|
McpError,
|
|
11
12
|
ReadResourceRequestSchema,
|
|
13
|
+
Root,
|
|
12
14
|
ServerCapabilities,
|
|
13
15
|
SetLevelRequestSchema,
|
|
14
16
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
15
17
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
16
18
|
import { z } from "zod";
|
|
19
|
+
import { setTimeout as delay } from "timers/promises";
|
|
17
20
|
import { readFile } from "fs/promises";
|
|
18
21
|
import { fileTypeFromBuffer } from "file-type";
|
|
19
|
-
import {
|
|
22
|
+
import { StrictEventEmitter } from "strict-event-emitter-types";
|
|
23
|
+
import { EventEmitter } from "events";
|
|
24
|
+
import { startSSEServer } from "mcp-proxy";
|
|
25
|
+
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
26
|
+
|
|
27
|
+
export type SSEServer = {
|
|
28
|
+
close: () => Promise<void>;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type FastMCPEvents = {
|
|
32
|
+
connect: (event: { session: FastMCPSession }) => void;
|
|
33
|
+
disconnect: (event: { session: FastMCPSession }) => void;
|
|
34
|
+
};
|
|
20
35
|
|
|
21
36
|
/**
|
|
22
37
|
* Generates an image content object from a URL, file path, or buffer.
|
|
@@ -223,65 +238,131 @@ type LoggingLevel =
|
|
|
223
238
|
| "alert"
|
|
224
239
|
| "emergency";
|
|
225
240
|
|
|
226
|
-
export class
|
|
227
|
-
#
|
|
228
|
-
#resources: Resource[];
|
|
229
|
-
#prompts: Prompt[];
|
|
230
|
-
#server: Server | null = null;
|
|
231
|
-
#options: ServerOptions;
|
|
241
|
+
export class FastMCPSession {
|
|
242
|
+
#capabilities: ServerCapabilities = {};
|
|
232
243
|
#loggingLevel: LoggingLevel = "info";
|
|
244
|
+
#server: Server;
|
|
245
|
+
#clientCapabilities?: ClientCapabilities;
|
|
246
|
+
#roots: Root[] = [];
|
|
247
|
+
|
|
248
|
+
constructor({
|
|
249
|
+
name,
|
|
250
|
+
version,
|
|
251
|
+
tools,
|
|
252
|
+
resources,
|
|
253
|
+
prompts,
|
|
254
|
+
}: {
|
|
255
|
+
name: string;
|
|
256
|
+
version: string;
|
|
257
|
+
tools: Tool[];
|
|
258
|
+
resources: Resource[];
|
|
259
|
+
prompts: Prompt[];
|
|
260
|
+
}) {
|
|
261
|
+
if (tools.length) {
|
|
262
|
+
this.#capabilities.tools = {};
|
|
263
|
+
}
|
|
233
264
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
265
|
+
if (resources.length) {
|
|
266
|
+
this.#capabilities.resources = {};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (prompts.length) {
|
|
270
|
+
this.#capabilities.prompts = {};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
this.#capabilities.logging = {};
|
|
274
|
+
|
|
275
|
+
this.#server = new Server(
|
|
276
|
+
{ name: name, version: version },
|
|
277
|
+
{ capabilities: this.#capabilities },
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
this.setupErrorHandling();
|
|
281
|
+
this.setupLoggingHandlers();
|
|
282
|
+
|
|
283
|
+
if (tools.length) {
|
|
284
|
+
this.setupToolHandlers(tools);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (resources.length) {
|
|
288
|
+
this.setupResourceHandlers(resources);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (prompts.length) {
|
|
292
|
+
this.setupPromptHandlers(prompts);
|
|
293
|
+
}
|
|
239
294
|
}
|
|
240
295
|
|
|
241
|
-
|
|
242
|
-
this
|
|
296
|
+
public get clientCapabilities(): ClientCapabilities | null {
|
|
297
|
+
return this.#clientCapabilities ?? null;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
public get server(): Server {
|
|
301
|
+
return this.#server;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
public async connect(transport: Transport) {
|
|
305
|
+
if (this.#server.transport) {
|
|
306
|
+
throw new UnexpectedStateError("Server is already connected");
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
await this.#server.connect(transport);
|
|
310
|
+
|
|
311
|
+
let attempt = 0;
|
|
312
|
+
|
|
313
|
+
while (attempt++ < 10) {
|
|
314
|
+
const capabilities = await this.#server.getClientCapabilities();
|
|
243
315
|
|
|
244
|
-
|
|
245
|
-
|
|
316
|
+
if (capabilities) {
|
|
317
|
+
this.#clientCapabilities = capabilities;
|
|
318
|
+
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
await delay(100);
|
|
246
323
|
}
|
|
247
324
|
|
|
248
|
-
if (this.#
|
|
249
|
-
this.
|
|
325
|
+
if (this.#clientCapabilities?.roots) {
|
|
326
|
+
const roots = await this.#server.listRoots();
|
|
327
|
+
|
|
328
|
+
this.#roots = roots.roots;
|
|
250
329
|
}
|
|
251
330
|
|
|
252
|
-
if (this.#
|
|
253
|
-
|
|
331
|
+
if (!this.#clientCapabilities) {
|
|
332
|
+
throw new UnexpectedStateError("Server did not connect");
|
|
254
333
|
}
|
|
334
|
+
}
|
|
255
335
|
|
|
256
|
-
|
|
257
|
-
|
|
336
|
+
public get roots(): Root[] {
|
|
337
|
+
return this.#roots;
|
|
338
|
+
}
|
|
258
339
|
|
|
259
|
-
|
|
260
|
-
|
|
340
|
+
public async close() {
|
|
341
|
+
await this.#server.close();
|
|
261
342
|
}
|
|
262
343
|
|
|
263
|
-
private setupErrorHandling(
|
|
264
|
-
server.onerror = (error) => {
|
|
344
|
+
private setupErrorHandling() {
|
|
345
|
+
this.#server.onerror = (error) => {
|
|
265
346
|
console.error("[MCP Error]", error);
|
|
266
347
|
};
|
|
267
|
-
|
|
268
|
-
process.on("SIGINT", async () => {
|
|
269
|
-
await server.close();
|
|
270
|
-
process.exit(0);
|
|
271
|
-
});
|
|
272
348
|
}
|
|
273
349
|
|
|
274
|
-
/**
|
|
275
|
-
* Returns the current logging level.
|
|
276
|
-
*/
|
|
277
350
|
public get loggingLevel(): LoggingLevel {
|
|
278
351
|
return this.#loggingLevel;
|
|
279
352
|
}
|
|
280
353
|
|
|
281
|
-
private
|
|
282
|
-
server.setRequestHandler(
|
|
354
|
+
private setupLoggingHandlers() {
|
|
355
|
+
this.#server.setRequestHandler(SetLevelRequestSchema, (request) => {
|
|
356
|
+
this.#loggingLevel = request.params.level;
|
|
357
|
+
|
|
358
|
+
return {};
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private setupToolHandlers(tools: Tool[]) {
|
|
363
|
+
this.#server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
283
364
|
return {
|
|
284
|
-
tools:
|
|
365
|
+
tools: tools.map((tool) => {
|
|
285
366
|
return {
|
|
286
367
|
name: tool.name,
|
|
287
368
|
description: tool.description,
|
|
@@ -293,10 +374,8 @@ export class FastMCP {
|
|
|
293
374
|
};
|
|
294
375
|
});
|
|
295
376
|
|
|
296
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
297
|
-
const tool =
|
|
298
|
-
(tool) => tool.name === request.params.name,
|
|
299
|
-
);
|
|
377
|
+
this.#server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
378
|
+
const tool = tools.find((tool) => tool.name === request.params.name);
|
|
300
379
|
|
|
301
380
|
if (!tool) {
|
|
302
381
|
throw new McpError(
|
|
@@ -326,7 +405,7 @@ export class FastMCP {
|
|
|
326
405
|
|
|
327
406
|
try {
|
|
328
407
|
const reportProgress = async (progress: Progress) => {
|
|
329
|
-
await server.notification({
|
|
408
|
+
await this.#server.notification({
|
|
330
409
|
method: "notifications/progress",
|
|
331
410
|
params: {
|
|
332
411
|
...progress,
|
|
@@ -337,7 +416,7 @@ export class FastMCP {
|
|
|
337
416
|
|
|
338
417
|
const log = {
|
|
339
418
|
debug: (message: string, context?: SerializableValue) => {
|
|
340
|
-
server.sendLoggingMessage({
|
|
419
|
+
this.#server.sendLoggingMessage({
|
|
341
420
|
level: "debug",
|
|
342
421
|
data: {
|
|
343
422
|
message,
|
|
@@ -346,7 +425,7 @@ export class FastMCP {
|
|
|
346
425
|
});
|
|
347
426
|
},
|
|
348
427
|
error: (message: string, context?: SerializableValue) => {
|
|
349
|
-
server.sendLoggingMessage({
|
|
428
|
+
this.#server.sendLoggingMessage({
|
|
350
429
|
level: "error",
|
|
351
430
|
data: {
|
|
352
431
|
message,
|
|
@@ -355,7 +434,7 @@ export class FastMCP {
|
|
|
355
434
|
});
|
|
356
435
|
},
|
|
357
436
|
info: (message: string, context?: SerializableValue) => {
|
|
358
|
-
server.sendLoggingMessage({
|
|
437
|
+
this.#server.sendLoggingMessage({
|
|
359
438
|
level: "info",
|
|
360
439
|
data: {
|
|
361
440
|
message,
|
|
@@ -364,7 +443,7 @@ export class FastMCP {
|
|
|
364
443
|
});
|
|
365
444
|
},
|
|
366
445
|
warn: (message: string, context?: SerializableValue) => {
|
|
367
|
-
server.sendLoggingMessage({
|
|
446
|
+
this.#server.sendLoggingMessage({
|
|
368
447
|
level: "warning",
|
|
369
448
|
data: {
|
|
370
449
|
message,
|
|
@@ -408,10 +487,10 @@ export class FastMCP {
|
|
|
408
487
|
});
|
|
409
488
|
}
|
|
410
489
|
|
|
411
|
-
private setupResourceHandlers(
|
|
412
|
-
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
490
|
+
private setupResourceHandlers(resources: Resource[]) {
|
|
491
|
+
this.#server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
413
492
|
return {
|
|
414
|
-
resources:
|
|
493
|
+
resources: resources.map((resource) => {
|
|
415
494
|
return {
|
|
416
495
|
uri: resource.uri,
|
|
417
496
|
name: resource.name,
|
|
@@ -421,48 +500,51 @@ export class FastMCP {
|
|
|
421
500
|
};
|
|
422
501
|
});
|
|
423
502
|
|
|
424
|
-
server.setRequestHandler(
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
if (!resource) {
|
|
430
|
-
throw new McpError(
|
|
431
|
-
ErrorCode.MethodNotFound,
|
|
432
|
-
`Unknown resource: ${request.params.uri}`,
|
|
503
|
+
this.#server.setRequestHandler(
|
|
504
|
+
ReadResourceRequestSchema,
|
|
505
|
+
async (request) => {
|
|
506
|
+
const resource = resources.find(
|
|
507
|
+
(resource) => resource.uri === request.params.uri,
|
|
433
508
|
);
|
|
434
|
-
}
|
|
435
509
|
|
|
436
|
-
|
|
510
|
+
if (!resource) {
|
|
511
|
+
throw new McpError(
|
|
512
|
+
ErrorCode.MethodNotFound,
|
|
513
|
+
`Unknown resource: ${request.params.uri}`,
|
|
514
|
+
);
|
|
515
|
+
}
|
|
437
516
|
|
|
438
|
-
|
|
439
|
-
result = await resource.load();
|
|
440
|
-
} catch (error) {
|
|
441
|
-
throw new McpError(
|
|
442
|
-
ErrorCode.InternalError,
|
|
443
|
-
`Error reading resource: ${error}`,
|
|
444
|
-
{
|
|
445
|
-
uri: resource.uri,
|
|
446
|
-
},
|
|
447
|
-
);
|
|
448
|
-
}
|
|
517
|
+
let result: Awaited<ReturnType<Resource["load"]>>;
|
|
449
518
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
519
|
+
try {
|
|
520
|
+
result = await resource.load();
|
|
521
|
+
} catch (error) {
|
|
522
|
+
throw new McpError(
|
|
523
|
+
ErrorCode.InternalError,
|
|
524
|
+
`Error reading resource: ${error}`,
|
|
525
|
+
{
|
|
526
|
+
uri: resource.uri,
|
|
527
|
+
},
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return {
|
|
532
|
+
contents: [
|
|
533
|
+
{
|
|
534
|
+
uri: resource.uri,
|
|
535
|
+
mimeType: resource.mimeType,
|
|
536
|
+
...result,
|
|
537
|
+
},
|
|
538
|
+
],
|
|
539
|
+
};
|
|
540
|
+
},
|
|
541
|
+
);
|
|
460
542
|
}
|
|
461
543
|
|
|
462
|
-
private setupPromptHandlers(
|
|
463
|
-
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
544
|
+
private setupPromptHandlers(prompts: Prompt[]) {
|
|
545
|
+
this.#server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
464
546
|
return {
|
|
465
|
-
prompts:
|
|
547
|
+
prompts: prompts.map((prompt) => {
|
|
466
548
|
return {
|
|
467
549
|
name: prompt.name,
|
|
468
550
|
description: prompt.description,
|
|
@@ -472,8 +554,8 @@ export class FastMCP {
|
|
|
472
554
|
};
|
|
473
555
|
});
|
|
474
556
|
|
|
475
|
-
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
476
|
-
const prompt =
|
|
557
|
+
this.#server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
558
|
+
const prompt = prompts.find(
|
|
477
559
|
(prompt) => prompt.name === request.params.name,
|
|
478
560
|
);
|
|
479
561
|
|
|
@@ -519,6 +601,31 @@ export class FastMCP {
|
|
|
519
601
|
};
|
|
520
602
|
});
|
|
521
603
|
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
const FastMCPEventEmitterBase: {
|
|
607
|
+
new (): StrictEventEmitter<EventEmitter, FastMCPEvents>;
|
|
608
|
+
} = EventEmitter;
|
|
609
|
+
|
|
610
|
+
class FastMCPEventEmitter extends FastMCPEventEmitterBase {}
|
|
611
|
+
|
|
612
|
+
export class FastMCP extends FastMCPEventEmitter {
|
|
613
|
+
#options: ServerOptions;
|
|
614
|
+
#prompts: Prompt[] = [];
|
|
615
|
+
#resources: Resource[] = [];
|
|
616
|
+
#sessions: FastMCPSession[] = [];
|
|
617
|
+
#sseServer: SSEServer | null = null;
|
|
618
|
+
#tools: Tool[] = [];
|
|
619
|
+
|
|
620
|
+
constructor(public options: ServerOptions) {
|
|
621
|
+
super();
|
|
622
|
+
|
|
623
|
+
this.#options = options;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
public get sessions(): FastMCPSession[] {
|
|
627
|
+
return this.#sessions;
|
|
628
|
+
}
|
|
522
629
|
|
|
523
630
|
/**
|
|
524
631
|
* Adds a tool to the server.
|
|
@@ -541,8 +648,6 @@ export class FastMCP {
|
|
|
541
648
|
this.#prompts.push(prompt);
|
|
542
649
|
}
|
|
543
650
|
|
|
544
|
-
#sseServer: SSEServer | null = null;
|
|
545
|
-
|
|
546
651
|
/**
|
|
547
652
|
* Starts the server.
|
|
548
653
|
*/
|
|
@@ -556,41 +661,56 @@ export class FastMCP {
|
|
|
556
661
|
transportType: "stdio",
|
|
557
662
|
},
|
|
558
663
|
) {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
if (this.#tools.length) {
|
|
562
|
-
capabilities.tools = {};
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
if (this.#resources.length) {
|
|
566
|
-
capabilities.resources = {};
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
if (this.#prompts.length) {
|
|
570
|
-
capabilities.prompts = {};
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
capabilities.logging = {};
|
|
664
|
+
if (options.transportType === "stdio") {
|
|
665
|
+
const transport = new StdioServerTransport();
|
|
574
666
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
667
|
+
const session = new FastMCPSession({
|
|
668
|
+
name: this.#options.name,
|
|
669
|
+
version: this.#options.version,
|
|
670
|
+
tools: this.#tools,
|
|
671
|
+
resources: this.#resources,
|
|
672
|
+
prompts: this.#prompts,
|
|
673
|
+
});
|
|
579
674
|
|
|
580
|
-
|
|
675
|
+
await session.connect(transport);
|
|
581
676
|
|
|
582
|
-
|
|
583
|
-
const transport = new StdioServerTransport();
|
|
677
|
+
this.#sessions.push(session);
|
|
584
678
|
|
|
585
|
-
|
|
679
|
+
this.emit("connect", {
|
|
680
|
+
session,
|
|
681
|
+
});
|
|
586
682
|
|
|
587
683
|
console.error(`server is running on stdio`);
|
|
588
684
|
} else if (options.transportType === "sse") {
|
|
589
|
-
this.#sseServer = await startSSEServer({
|
|
685
|
+
this.#sseServer = await startSSEServer<FastMCPSession>({
|
|
590
686
|
endpoint: options.sse.endpoint as `/${string}`,
|
|
591
687
|
port: options.sse.port,
|
|
592
|
-
|
|
688
|
+
createServer: async () => {
|
|
689
|
+
return new FastMCPSession({
|
|
690
|
+
name: this.#options.name,
|
|
691
|
+
version: this.#options.version,
|
|
692
|
+
tools: this.#tools,
|
|
693
|
+
resources: this.#resources,
|
|
694
|
+
prompts: this.#prompts,
|
|
695
|
+
});
|
|
696
|
+
},
|
|
697
|
+
onClose: (session) => {
|
|
698
|
+
this.emit("disconnect", {
|
|
699
|
+
session,
|
|
700
|
+
});
|
|
701
|
+
},
|
|
702
|
+
onConnect: async (session) => {
|
|
703
|
+
this.#sessions.push(session);
|
|
704
|
+
|
|
705
|
+
this.emit("connect", {
|
|
706
|
+
session,
|
|
707
|
+
});
|
|
708
|
+
},
|
|
593
709
|
});
|
|
710
|
+
|
|
711
|
+
console.error(
|
|
712
|
+
`server is running on SSE at http://localhost:${options.sse.port}${options.sse.endpoint}`,
|
|
713
|
+
);
|
|
594
714
|
} else {
|
|
595
715
|
throw new Error("Invalid transport type");
|
|
596
716
|
}
|
package/src/examples/addition.ts
CHANGED
|
@@ -21,6 +21,32 @@ server.addTool({
|
|
|
21
21
|
},
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
+
server.addResource({
|
|
25
|
+
uri: "file:///logs/app.log",
|
|
26
|
+
name: "Application Logs",
|
|
27
|
+
mimeType: "text/plain",
|
|
28
|
+
async load() {
|
|
29
|
+
return {
|
|
30
|
+
text: "Example log content",
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
server.addPrompt({
|
|
36
|
+
name: "git-commit",
|
|
37
|
+
description: "Generate a Git commit message",
|
|
38
|
+
arguments: [
|
|
39
|
+
{
|
|
40
|
+
name: "changes",
|
|
41
|
+
description: "Git diff or description of changes",
|
|
42
|
+
required: true,
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
load: async (args) => {
|
|
46
|
+
return `Generate a concise but descriptive commit message for these changes:\n\n${args.changes}`;
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
24
50
|
server.start({
|
|
25
51
|
transportType: "stdio",
|
|
26
52
|
});
|