fastmcp 1.6.1 → 1.7.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 +35 -10
- package/dist/FastMCP.d.ts +38 -8
- package/dist/FastMCP.js +171 -103
- package/dist/FastMCP.js.map +1 -1
- package/jsr.json +1 -1
- package/package.json +5 -4
- package/src/FastMCP.test.ts +127 -13
- package/src/FastMCP.ts +213 -113
- package/src/examples/addition.ts +26 -0
- package/vitest.config.js +9 -0
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastmcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"main": "dist/FastMCP.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "tsup",
|
|
7
|
-
"test": "vitest run && tsc",
|
|
7
|
+
"test": "vitest run && tsc && jsr publish --dry-run",
|
|
8
8
|
"format": "prettier --write . && eslint --fix ."
|
|
9
9
|
},
|
|
10
10
|
"bin": {
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
25
25
|
"execa": "^9.5.2",
|
|
26
26
|
"file-type": "^19.6.0",
|
|
27
|
-
"mcp-proxy": "^
|
|
27
|
+
"mcp-proxy": "^2.0.2",
|
|
28
|
+
"strict-event-emitter-types": "^2.0.0",
|
|
28
29
|
"yargs": "^17.7.2",
|
|
29
30
|
"zod": "^3.24.1",
|
|
30
31
|
"zod-to-json-schema": "^3.24.1"
|
|
@@ -51,8 +52,8 @@
|
|
|
51
52
|
"@types/yargs": "^17.0.33",
|
|
52
53
|
"eslint": "^9.17.0",
|
|
53
54
|
"eslint-plugin-perfectionist": "^4.4.0",
|
|
54
|
-
"eventsource": "^3.0.2",
|
|
55
55
|
"get-port-please": "^3.1.2",
|
|
56
|
+
"jsr": "^0.13.2",
|
|
56
57
|
"prettier": "^3.4.2",
|
|
57
58
|
"semantic-release": "^24.2.0",
|
|
58
59
|
"tsup": "^8.3.5",
|
package/src/FastMCP.test.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { FastMCP, UserError, imageContent } from "./FastMCP.js";
|
|
1
|
+
import { FastMCP, FastMCPSession, UserError, imageContent } from "./FastMCP.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { test, expect, vi } from "vitest";
|
|
4
4
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
5
5
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
6
6
|
import { getRandomPort } from "get-port-please";
|
|
7
|
-
import { EventSource } from "eventsource";
|
|
8
7
|
import { setTimeout as delay } from "timers/promises";
|
|
9
8
|
import {
|
|
10
9
|
ErrorCode,
|
|
@@ -12,9 +11,6 @@ import {
|
|
|
12
11
|
McpError,
|
|
13
12
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
14
13
|
|
|
15
|
-
// @ts-expect-error - figure out how to use --experimental-eventsource with vitest
|
|
16
|
-
global.EventSource = EventSource;
|
|
17
|
-
|
|
18
14
|
const runWithTestServer = async ({
|
|
19
15
|
run,
|
|
20
16
|
start,
|
|
@@ -26,6 +22,7 @@ const runWithTestServer = async ({
|
|
|
26
22
|
}: {
|
|
27
23
|
client: Client;
|
|
28
24
|
server: FastMCP;
|
|
25
|
+
session: FastMCPSession;
|
|
29
26
|
}) => Promise<void>;
|
|
30
27
|
}) => {
|
|
31
28
|
const port = await getRandomPort();
|
|
@@ -55,9 +52,15 @@ const runWithTestServer = async ({
|
|
|
55
52
|
new URL(`http://localhost:${port}/sse`),
|
|
56
53
|
);
|
|
57
54
|
|
|
58
|
-
await
|
|
55
|
+
const session = await new Promise<FastMCPSession>((resolve) => {
|
|
56
|
+
server.on("connect", (event) => {
|
|
57
|
+
resolve(event.session);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
client.connect(transport);
|
|
61
|
+
});
|
|
59
62
|
|
|
60
|
-
await run({ client, server });
|
|
63
|
+
await run({ client, server, session });
|
|
61
64
|
} finally {
|
|
62
65
|
await server.stop();
|
|
63
66
|
}
|
|
@@ -369,21 +372,19 @@ test("tracks tool progress", async () => {
|
|
|
369
372
|
test("sets logging levels", async () => {
|
|
370
373
|
await runWithTestServer({
|
|
371
374
|
start: async () => {
|
|
372
|
-
|
|
375
|
+
return new FastMCP({
|
|
373
376
|
name: "Test",
|
|
374
377
|
version: "1.0.0",
|
|
375
378
|
});
|
|
376
|
-
|
|
377
|
-
return server;
|
|
378
379
|
},
|
|
379
|
-
run: async ({ client,
|
|
380
|
+
run: async ({ client, session }) => {
|
|
380
381
|
await client.setLoggingLevel("debug");
|
|
381
382
|
|
|
382
|
-
expect(
|
|
383
|
+
expect(session.loggingLevel).toBe("debug");
|
|
383
384
|
|
|
384
385
|
await client.setLoggingLevel("info");
|
|
385
386
|
|
|
386
|
-
expect(
|
|
387
|
+
expect(session.loggingLevel).toBe("info");
|
|
387
388
|
},
|
|
388
389
|
});
|
|
389
390
|
});
|
|
@@ -543,3 +544,116 @@ test("adds prompts", async () => {
|
|
|
543
544
|
},
|
|
544
545
|
});
|
|
545
546
|
});
|
|
547
|
+
|
|
548
|
+
test("uses events to notify server of client connect/disconnect", async () => {
|
|
549
|
+
const port = await getRandomPort();
|
|
550
|
+
|
|
551
|
+
const server = new FastMCP({
|
|
552
|
+
name: "Test",
|
|
553
|
+
version: "1.0.0",
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
const onConnect = vi.fn();
|
|
557
|
+
const onDisconnect = vi.fn();
|
|
558
|
+
|
|
559
|
+
server.on("connect", onConnect);
|
|
560
|
+
server.on("disconnect", onDisconnect);
|
|
561
|
+
|
|
562
|
+
await server.start({
|
|
563
|
+
transportType: "sse",
|
|
564
|
+
sse: {
|
|
565
|
+
endpoint: "/sse",
|
|
566
|
+
port,
|
|
567
|
+
},
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
const client = new Client(
|
|
571
|
+
{
|
|
572
|
+
name: "example-client",
|
|
573
|
+
version: "1.0.0",
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
capabilities: {},
|
|
577
|
+
},
|
|
578
|
+
);
|
|
579
|
+
|
|
580
|
+
const transport = new SSEClientTransport(
|
|
581
|
+
new URL(`http://localhost:${port}/sse`),
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
await client.connect(transport);
|
|
585
|
+
|
|
586
|
+
await delay(100);
|
|
587
|
+
|
|
588
|
+
expect(onConnect).toHaveBeenCalledTimes(1);
|
|
589
|
+
expect(onDisconnect).toHaveBeenCalledTimes(0);
|
|
590
|
+
|
|
591
|
+
expect(server.sessions).toEqual([expect.any(FastMCPSession)]);
|
|
592
|
+
|
|
593
|
+
await client.close();
|
|
594
|
+
|
|
595
|
+
await delay(100);
|
|
596
|
+
|
|
597
|
+
expect(onConnect).toHaveBeenCalledTimes(1);
|
|
598
|
+
expect(onDisconnect).toHaveBeenCalledTimes(1);
|
|
599
|
+
|
|
600
|
+
await server.stop();
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
test("handles multiple clients", async () => {
|
|
604
|
+
const port = await getRandomPort();
|
|
605
|
+
|
|
606
|
+
const server = new FastMCP({
|
|
607
|
+
name: "Test",
|
|
608
|
+
version: "1.0.0",
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
await server.start({
|
|
612
|
+
transportType: "sse",
|
|
613
|
+
sse: {
|
|
614
|
+
endpoint: "/sse",
|
|
615
|
+
port,
|
|
616
|
+
},
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
const client1 = new Client(
|
|
620
|
+
{
|
|
621
|
+
name: "example-client",
|
|
622
|
+
version: "1.0.0",
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
capabilities: {},
|
|
626
|
+
},
|
|
627
|
+
);
|
|
628
|
+
|
|
629
|
+
const transport1 = new SSEClientTransport(
|
|
630
|
+
new URL(`http://localhost:${port}/sse`),
|
|
631
|
+
);
|
|
632
|
+
|
|
633
|
+
await client1.connect(transport1);
|
|
634
|
+
|
|
635
|
+
const client2 = new Client(
|
|
636
|
+
{
|
|
637
|
+
name: "example-client",
|
|
638
|
+
version: "1.0.0",
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
capabilities: {},
|
|
642
|
+
},
|
|
643
|
+
);
|
|
644
|
+
|
|
645
|
+
const transport2 = new SSEClientTransport(
|
|
646
|
+
new URL(`http://localhost:${port}/sse`),
|
|
647
|
+
);
|
|
648
|
+
|
|
649
|
+
await client2.connect(transport2);
|
|
650
|
+
|
|
651
|
+
await delay(100);
|
|
652
|
+
|
|
653
|
+
expect(server.sessions).toEqual([
|
|
654
|
+
expect.any(FastMCPSession),
|
|
655
|
+
expect.any(FastMCPSession),
|
|
656
|
+
]);
|
|
657
|
+
|
|
658
|
+
await server.stop();
|
|
659
|
+
});
|
package/src/FastMCP.ts
CHANGED
|
@@ -16,7 +16,19 @@ import { zodToJsonSchema } from "zod-to-json-schema";
|
|
|
16
16
|
import { z } from "zod";
|
|
17
17
|
import { readFile } from "fs/promises";
|
|
18
18
|
import { fileTypeFromBuffer } from "file-type";
|
|
19
|
-
import {
|
|
19
|
+
import { StrictEventEmitter } from "strict-event-emitter-types";
|
|
20
|
+
import { EventEmitter } from "events";
|
|
21
|
+
import { startSSEServer } from "mcp-proxy";
|
|
22
|
+
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
23
|
+
|
|
24
|
+
export type SSEServer = {
|
|
25
|
+
close: () => Promise<void>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type FastMCPEvents = {
|
|
29
|
+
connect: (event: { session: FastMCPSession }) => void;
|
|
30
|
+
disconnect: (event: { session: FastMCPSession }) => void;
|
|
31
|
+
};
|
|
20
32
|
|
|
21
33
|
/**
|
|
22
34
|
* Generates an image content object from a URL, file path, or buffer.
|
|
@@ -223,65 +235,89 @@ type LoggingLevel =
|
|
|
223
235
|
| "alert"
|
|
224
236
|
| "emergency";
|
|
225
237
|
|
|
226
|
-
export class
|
|
227
|
-
#
|
|
228
|
-
#resources: Resource[];
|
|
229
|
-
#prompts: Prompt[];
|
|
230
|
-
#server: Server | null = null;
|
|
231
|
-
#options: ServerOptions;
|
|
238
|
+
export class FastMCPSession {
|
|
239
|
+
#capabilities: ServerCapabilities = {};
|
|
232
240
|
#loggingLevel: LoggingLevel = "info";
|
|
241
|
+
#server: Server;
|
|
242
|
+
|
|
243
|
+
constructor({
|
|
244
|
+
name,
|
|
245
|
+
version,
|
|
246
|
+
tools,
|
|
247
|
+
resources,
|
|
248
|
+
prompts,
|
|
249
|
+
}: {
|
|
250
|
+
name: string;
|
|
251
|
+
version: string;
|
|
252
|
+
tools: Tool[];
|
|
253
|
+
resources: Resource[];
|
|
254
|
+
prompts: Prompt[];
|
|
255
|
+
}) {
|
|
256
|
+
if (tools.length) {
|
|
257
|
+
this.#capabilities.tools = {};
|
|
258
|
+
}
|
|
233
259
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
260
|
+
if (resources.length) {
|
|
261
|
+
this.#capabilities.resources = {};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (prompts.length) {
|
|
265
|
+
this.#capabilities.prompts = {};
|
|
266
|
+
}
|
|
240
267
|
|
|
241
|
-
|
|
242
|
-
|
|
268
|
+
this.#capabilities.logging = {};
|
|
269
|
+
|
|
270
|
+
this.#server = new Server(
|
|
271
|
+
{ name: name, version: version },
|
|
272
|
+
{ capabilities: this.#capabilities },
|
|
273
|
+
);
|
|
243
274
|
|
|
244
|
-
|
|
245
|
-
|
|
275
|
+
this.setupErrorHandling();
|
|
276
|
+
this.setupLoggingHandlers();
|
|
277
|
+
|
|
278
|
+
if (tools.length) {
|
|
279
|
+
this.setupToolHandlers(tools);
|
|
246
280
|
}
|
|
247
281
|
|
|
248
|
-
if (
|
|
249
|
-
this.setupResourceHandlers(
|
|
282
|
+
if (resources.length) {
|
|
283
|
+
this.setupResourceHandlers(resources);
|
|
250
284
|
}
|
|
251
285
|
|
|
252
|
-
if (
|
|
253
|
-
this.setupPromptHandlers(
|
|
286
|
+
if (prompts.length) {
|
|
287
|
+
this.setupPromptHandlers(prompts);
|
|
254
288
|
}
|
|
289
|
+
}
|
|
255
290
|
|
|
256
|
-
|
|
257
|
-
|
|
291
|
+
public get server(): Server {
|
|
292
|
+
return this.#server;
|
|
293
|
+
}
|
|
258
294
|
|
|
259
|
-
|
|
260
|
-
|
|
295
|
+
public connect(transport: Transport) {
|
|
296
|
+
this.#server.connect(transport);
|
|
261
297
|
}
|
|
262
298
|
|
|
263
|
-
private setupErrorHandling(
|
|
264
|
-
server.onerror = (error) => {
|
|
299
|
+
private setupErrorHandling() {
|
|
300
|
+
this.#server.onerror = (error) => {
|
|
265
301
|
console.error("[MCP Error]", error);
|
|
266
302
|
};
|
|
267
|
-
|
|
268
|
-
process.on("SIGINT", async () => {
|
|
269
|
-
await server.close();
|
|
270
|
-
process.exit(0);
|
|
271
|
-
});
|
|
272
303
|
}
|
|
273
304
|
|
|
274
|
-
/**
|
|
275
|
-
* Returns the current logging level.
|
|
276
|
-
*/
|
|
277
305
|
public get loggingLevel(): LoggingLevel {
|
|
278
306
|
return this.#loggingLevel;
|
|
279
307
|
}
|
|
280
308
|
|
|
281
|
-
private
|
|
282
|
-
server.setRequestHandler(
|
|
309
|
+
private setupLoggingHandlers() {
|
|
310
|
+
this.#server.setRequestHandler(SetLevelRequestSchema, (request) => {
|
|
311
|
+
this.#loggingLevel = request.params.level;
|
|
312
|
+
|
|
313
|
+
return {};
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private setupToolHandlers(tools: Tool[]) {
|
|
318
|
+
this.#server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
283
319
|
return {
|
|
284
|
-
tools:
|
|
320
|
+
tools: tools.map((tool) => {
|
|
285
321
|
return {
|
|
286
322
|
name: tool.name,
|
|
287
323
|
description: tool.description,
|
|
@@ -293,10 +329,8 @@ export class FastMCP {
|
|
|
293
329
|
};
|
|
294
330
|
});
|
|
295
331
|
|
|
296
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
297
|
-
const tool =
|
|
298
|
-
(tool) => tool.name === request.params.name,
|
|
299
|
-
);
|
|
332
|
+
this.#server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
333
|
+
const tool = tools.find((tool) => tool.name === request.params.name);
|
|
300
334
|
|
|
301
335
|
if (!tool) {
|
|
302
336
|
throw new McpError(
|
|
@@ -326,7 +360,7 @@ export class FastMCP {
|
|
|
326
360
|
|
|
327
361
|
try {
|
|
328
362
|
const reportProgress = async (progress: Progress) => {
|
|
329
|
-
await server.notification({
|
|
363
|
+
await this.#server.notification({
|
|
330
364
|
method: "notifications/progress",
|
|
331
365
|
params: {
|
|
332
366
|
...progress,
|
|
@@ -337,7 +371,7 @@ export class FastMCP {
|
|
|
337
371
|
|
|
338
372
|
const log = {
|
|
339
373
|
debug: (message: string, context?: SerializableValue) => {
|
|
340
|
-
server.sendLoggingMessage({
|
|
374
|
+
this.#server.sendLoggingMessage({
|
|
341
375
|
level: "debug",
|
|
342
376
|
data: {
|
|
343
377
|
message,
|
|
@@ -346,7 +380,7 @@ export class FastMCP {
|
|
|
346
380
|
});
|
|
347
381
|
},
|
|
348
382
|
error: (message: string, context?: SerializableValue) => {
|
|
349
|
-
server.sendLoggingMessage({
|
|
383
|
+
this.#server.sendLoggingMessage({
|
|
350
384
|
level: "error",
|
|
351
385
|
data: {
|
|
352
386
|
message,
|
|
@@ -355,7 +389,7 @@ export class FastMCP {
|
|
|
355
389
|
});
|
|
356
390
|
},
|
|
357
391
|
info: (message: string, context?: SerializableValue) => {
|
|
358
|
-
server.sendLoggingMessage({
|
|
392
|
+
this.#server.sendLoggingMessage({
|
|
359
393
|
level: "info",
|
|
360
394
|
data: {
|
|
361
395
|
message,
|
|
@@ -364,7 +398,7 @@ export class FastMCP {
|
|
|
364
398
|
});
|
|
365
399
|
},
|
|
366
400
|
warn: (message: string, context?: SerializableValue) => {
|
|
367
|
-
server.sendLoggingMessage({
|
|
401
|
+
this.#server.sendLoggingMessage({
|
|
368
402
|
level: "warning",
|
|
369
403
|
data: {
|
|
370
404
|
message,
|
|
@@ -408,10 +442,10 @@ export class FastMCP {
|
|
|
408
442
|
});
|
|
409
443
|
}
|
|
410
444
|
|
|
411
|
-
private setupResourceHandlers(
|
|
412
|
-
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
445
|
+
private setupResourceHandlers(resources: Resource[]) {
|
|
446
|
+
this.#server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
413
447
|
return {
|
|
414
|
-
resources:
|
|
448
|
+
resources: resources.map((resource) => {
|
|
415
449
|
return {
|
|
416
450
|
uri: resource.uri,
|
|
417
451
|
name: resource.name,
|
|
@@ -421,48 +455,51 @@ export class FastMCP {
|
|
|
421
455
|
};
|
|
422
456
|
});
|
|
423
457
|
|
|
424
|
-
server.setRequestHandler(
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
if (!resource) {
|
|
430
|
-
throw new McpError(
|
|
431
|
-
ErrorCode.MethodNotFound,
|
|
432
|
-
`Unknown resource: ${request.params.uri}`,
|
|
458
|
+
this.#server.setRequestHandler(
|
|
459
|
+
ReadResourceRequestSchema,
|
|
460
|
+
async (request) => {
|
|
461
|
+
const resource = resources.find(
|
|
462
|
+
(resource) => resource.uri === request.params.uri,
|
|
433
463
|
);
|
|
434
|
-
}
|
|
435
464
|
|
|
436
|
-
|
|
465
|
+
if (!resource) {
|
|
466
|
+
throw new McpError(
|
|
467
|
+
ErrorCode.MethodNotFound,
|
|
468
|
+
`Unknown resource: ${request.params.uri}`,
|
|
469
|
+
);
|
|
470
|
+
}
|
|
437
471
|
|
|
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
|
-
}
|
|
472
|
+
let result: Awaited<ReturnType<Resource["load"]>>;
|
|
449
473
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
474
|
+
try {
|
|
475
|
+
result = await resource.load();
|
|
476
|
+
} catch (error) {
|
|
477
|
+
throw new McpError(
|
|
478
|
+
ErrorCode.InternalError,
|
|
479
|
+
`Error reading resource: ${error}`,
|
|
480
|
+
{
|
|
481
|
+
uri: resource.uri,
|
|
482
|
+
},
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return {
|
|
487
|
+
contents: [
|
|
488
|
+
{
|
|
489
|
+
uri: resource.uri,
|
|
490
|
+
mimeType: resource.mimeType,
|
|
491
|
+
...result,
|
|
492
|
+
},
|
|
493
|
+
],
|
|
494
|
+
};
|
|
495
|
+
},
|
|
496
|
+
);
|
|
460
497
|
}
|
|
461
498
|
|
|
462
|
-
private setupPromptHandlers(
|
|
463
|
-
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
499
|
+
private setupPromptHandlers(prompts: Prompt[]) {
|
|
500
|
+
this.#server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
464
501
|
return {
|
|
465
|
-
prompts:
|
|
502
|
+
prompts: prompts.map((prompt) => {
|
|
466
503
|
return {
|
|
467
504
|
name: prompt.name,
|
|
468
505
|
description: prompt.description,
|
|
@@ -472,8 +509,8 @@ export class FastMCP {
|
|
|
472
509
|
};
|
|
473
510
|
});
|
|
474
511
|
|
|
475
|
-
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
476
|
-
const prompt =
|
|
512
|
+
this.#server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
513
|
+
const prompt = prompts.find(
|
|
477
514
|
(prompt) => prompt.name === request.params.name,
|
|
478
515
|
);
|
|
479
516
|
|
|
@@ -519,6 +556,31 @@ export class FastMCP {
|
|
|
519
556
|
};
|
|
520
557
|
});
|
|
521
558
|
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const FastMCPEventEmitterBase: {
|
|
562
|
+
new (): StrictEventEmitter<EventEmitter, FastMCPEvents>;
|
|
563
|
+
} = EventEmitter;
|
|
564
|
+
|
|
565
|
+
class FastMCPEventEmitter extends FastMCPEventEmitterBase {}
|
|
566
|
+
|
|
567
|
+
export class FastMCP extends FastMCPEventEmitter {
|
|
568
|
+
#options: ServerOptions;
|
|
569
|
+
#prompts: Prompt[] = [];
|
|
570
|
+
#resources: Resource[] = [];
|
|
571
|
+
#sessions: FastMCPSession[] = [];
|
|
572
|
+
#sseServer: SSEServer | null = null;
|
|
573
|
+
#tools: Tool[] = [];
|
|
574
|
+
|
|
575
|
+
constructor(public options: ServerOptions) {
|
|
576
|
+
super();
|
|
577
|
+
|
|
578
|
+
this.#options = options;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
public get sessions(): FastMCPSession[] {
|
|
582
|
+
return this.#sessions;
|
|
583
|
+
}
|
|
522
584
|
|
|
523
585
|
/**
|
|
524
586
|
* Adds a tool to the server.
|
|
@@ -541,8 +603,6 @@ export class FastMCP {
|
|
|
541
603
|
this.#prompts.push(prompt);
|
|
542
604
|
}
|
|
543
605
|
|
|
544
|
-
#sseServer: SSEServer | null = null;
|
|
545
|
-
|
|
546
606
|
/**
|
|
547
607
|
* Starts the server.
|
|
548
608
|
*/
|
|
@@ -556,41 +616,81 @@ export class FastMCP {
|
|
|
556
616
|
transportType: "stdio",
|
|
557
617
|
},
|
|
558
618
|
) {
|
|
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 = {};
|
|
619
|
+
if (options.transportType === "stdio") {
|
|
620
|
+
const transport = new StdioServerTransport();
|
|
574
621
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
622
|
+
const session = new FastMCPSession({
|
|
623
|
+
name: this.#options.name,
|
|
624
|
+
version: this.#options.version,
|
|
625
|
+
tools: this.#tools,
|
|
626
|
+
resources: this.#resources,
|
|
627
|
+
prompts: this.#prompts,
|
|
628
|
+
});
|
|
579
629
|
|
|
580
|
-
|
|
630
|
+
await session.connect(transport);
|
|
581
631
|
|
|
582
|
-
|
|
583
|
-
const transport = new StdioServerTransport();
|
|
632
|
+
this.#sessions.push(session);
|
|
584
633
|
|
|
585
|
-
|
|
634
|
+
this.emit("connect", {
|
|
635
|
+
session,
|
|
636
|
+
});
|
|
586
637
|
|
|
587
638
|
console.error(`server is running on stdio`);
|
|
588
639
|
} else if (options.transportType === "sse") {
|
|
589
640
|
this.#sseServer = await startSSEServer({
|
|
590
641
|
endpoint: options.sse.endpoint as `/${string}`,
|
|
591
642
|
port: options.sse.port,
|
|
592
|
-
|
|
643
|
+
createServer: async () => {
|
|
644
|
+
const session = new FastMCPSession({
|
|
645
|
+
name: this.#options.name,
|
|
646
|
+
version: this.#options.version,
|
|
647
|
+
tools: this.#tools,
|
|
648
|
+
resources: this.#resources,
|
|
649
|
+
prompts: this.#prompts,
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
this.#sessions.push(session);
|
|
653
|
+
|
|
654
|
+
return session.server;
|
|
655
|
+
},
|
|
656
|
+
onClose: (server) => {
|
|
657
|
+
const session = this.#sessions.find(
|
|
658
|
+
(session) => session.server === server,
|
|
659
|
+
);
|
|
660
|
+
|
|
661
|
+
if (!session) {
|
|
662
|
+
throw new UnexpectedStateError("Server not found");
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
this.#sessions = this.#sessions.filter(
|
|
666
|
+
(maybeOurSession) => maybeOurSession !== session,
|
|
667
|
+
);
|
|
668
|
+
|
|
669
|
+
this.emit("disconnect", {
|
|
670
|
+
session,
|
|
671
|
+
});
|
|
672
|
+
},
|
|
673
|
+
onConnect: async (server) => {
|
|
674
|
+
const session = this.#sessions.find(
|
|
675
|
+
(session) => session.server === server,
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
if (!session) {
|
|
679
|
+
throw new UnexpectedStateError("Server not found");
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// TODO investigate where is the race condition
|
|
683
|
+
setTimeout(() => {
|
|
684
|
+
this.emit("connect", {
|
|
685
|
+
session,
|
|
686
|
+
});
|
|
687
|
+
}, 100);
|
|
688
|
+
},
|
|
593
689
|
});
|
|
690
|
+
|
|
691
|
+
console.error(
|
|
692
|
+
`server is running on SSE at http://localhost:${options.sse.port}${options.sse.endpoint}`,
|
|
693
|
+
);
|
|
594
694
|
} else {
|
|
595
695
|
throw new Error("Invalid transport type");
|
|
596
696
|
}
|