fastmcp 1.7.0 → 1.9.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 +53 -2
- package/dist/FastMCP.d.ts +17 -2
- package/dist/FastMCP.js +60 -28
- package/dist/FastMCP.js.map +1 -1
- package/jsr.json +1 -1
- package/package.json +2 -2
- package/src/FastMCP.test.ts +195 -9
- package/src/FastMCP.ts +85 -36
package/README.md
CHANGED
|
@@ -14,7 +14,8 @@ A TypeScript framework for building [MCP](https://modelcontextprotocol.io/) serv
|
|
|
14
14
|
- [Error handling](#errors)
|
|
15
15
|
- [SSE](#sse)
|
|
16
16
|
- [Progress notifications](#progress)
|
|
17
|
-
- [Typed events](#typed-events)
|
|
17
|
+
- [Typed server events](#typed-server-events)
|
|
18
|
+
- Roots
|
|
18
19
|
- CLI for [testing](#test-with-mcp-cli) and [debugging](#inspect-with-mcp-inspector)
|
|
19
20
|
|
|
20
21
|
## Installation
|
|
@@ -51,6 +52,8 @@ server.start({
|
|
|
51
52
|
});
|
|
52
53
|
```
|
|
53
54
|
|
|
55
|
+
_That's it!_ You have a working MCP server.
|
|
56
|
+
|
|
54
57
|
You can test the server in terminal with:
|
|
55
58
|
|
|
56
59
|
```bash
|
|
@@ -399,7 +402,7 @@ server.sessions;
|
|
|
399
402
|
|
|
400
403
|
We allocate a new server instance for each client connection to enable 1:1 communication between a client and the server.
|
|
401
404
|
|
|
402
|
-
### Typed events
|
|
405
|
+
### Typed server events
|
|
403
406
|
|
|
404
407
|
You can listen to events emitted by the server using the `on` method:
|
|
405
408
|
|
|
@@ -413,6 +416,54 @@ server.on("disconnect", (event) => {
|
|
|
413
416
|
});
|
|
414
417
|
```
|
|
415
418
|
|
|
419
|
+
## `FastMCPSession`
|
|
420
|
+
|
|
421
|
+
`FastMCPSession` represents a client session and provides methods to interact with the client.
|
|
422
|
+
|
|
423
|
+
Refer to [Sessions](#sessions) for examples of how to obtain a `FastMCPSession` instance.
|
|
424
|
+
|
|
425
|
+
### `clientCapabilities`
|
|
426
|
+
|
|
427
|
+
The `clientCapabilities` property contains the client capabilities.
|
|
428
|
+
|
|
429
|
+
```ts
|
|
430
|
+
session.clientCapabilities;
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### `loggingLevel`
|
|
434
|
+
|
|
435
|
+
The `loggingLevel` property describes the logging level as set by the client.
|
|
436
|
+
|
|
437
|
+
```ts
|
|
438
|
+
session.loggingLevel;
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### `roots`
|
|
442
|
+
|
|
443
|
+
The `roots` property contains the roots as set by the client.
|
|
444
|
+
|
|
445
|
+
```ts
|
|
446
|
+
session.roots;
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### `server`
|
|
450
|
+
|
|
451
|
+
The `server` property contains an instance of MCP server that is associated with the session.
|
|
452
|
+
|
|
453
|
+
```ts
|
|
454
|
+
session.server;
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### Typed session events
|
|
458
|
+
|
|
459
|
+
You can listen to events emitted by the session using the `on` method:
|
|
460
|
+
|
|
461
|
+
```ts
|
|
462
|
+
session.on("rootsChanged", (event) => {
|
|
463
|
+
console.log("Roots changed:", event.roots);
|
|
464
|
+
});
|
|
465
|
+
```
|
|
466
|
+
|
|
416
467
|
## Running Your Server
|
|
417
468
|
|
|
418
469
|
### Test with `mcp-cli`
|
package/dist/FastMCP.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { ClientCapabilities, Root } from '@modelcontextprotocol/sdk/types.js';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
import { StrictEventEmitter } from 'strict-event-emitter-types';
|
|
4
5
|
import { EventEmitter } from 'events';
|
|
@@ -15,6 +16,11 @@ type FastMCPEvents = {
|
|
|
15
16
|
session: FastMCPSession;
|
|
16
17
|
}) => void;
|
|
17
18
|
};
|
|
19
|
+
type FastMCPSessionEvents = {
|
|
20
|
+
rootsChanged: (event: {
|
|
21
|
+
roots: Root[];
|
|
22
|
+
}) => void;
|
|
23
|
+
};
|
|
18
24
|
/**
|
|
19
25
|
* Generates an image content object from a URL, file path, or buffer.
|
|
20
26
|
*/
|
|
@@ -115,7 +121,12 @@ type ServerOptions = {
|
|
|
115
121
|
version: `${number}.${number}.${number}`;
|
|
116
122
|
};
|
|
117
123
|
type LoggingLevel = "debug" | "info" | "notice" | "warning" | "error" | "critical" | "alert" | "emergency";
|
|
118
|
-
declare
|
|
124
|
+
declare const FastMCPSessionEventEmitterBase: {
|
|
125
|
+
new (): StrictEventEmitter<EventEmitter, FastMCPSessionEvents>;
|
|
126
|
+
};
|
|
127
|
+
declare class FastMCPSessionEventEmitter extends FastMCPSessionEventEmitterBase {
|
|
128
|
+
}
|
|
129
|
+
declare class FastMCPSession extends FastMCPSessionEventEmitter {
|
|
119
130
|
#private;
|
|
120
131
|
constructor({ name, version, tools, resources, prompts, }: {
|
|
121
132
|
name: string;
|
|
@@ -124,10 +135,14 @@ declare class FastMCPSession {
|
|
|
124
135
|
resources: Resource[];
|
|
125
136
|
prompts: Prompt[];
|
|
126
137
|
});
|
|
138
|
+
get clientCapabilities(): ClientCapabilities | null;
|
|
127
139
|
get server(): Server;
|
|
128
|
-
connect(transport: Transport): void
|
|
140
|
+
connect(transport: Transport): Promise<void>;
|
|
141
|
+
get roots(): Root[];
|
|
142
|
+
close(): Promise<void>;
|
|
129
143
|
private setupErrorHandling;
|
|
130
144
|
get loggingLevel(): LoggingLevel;
|
|
145
|
+
private setupRootsHandlers;
|
|
131
146
|
private setupLoggingHandlers;
|
|
132
147
|
private setupToolHandlers;
|
|
133
148
|
private setupResourceHandlers;
|
package/dist/FastMCP.js
CHANGED
|
@@ -10,10 +10,12 @@ import {
|
|
|
10
10
|
ListToolsRequestSchema,
|
|
11
11
|
McpError,
|
|
12
12
|
ReadResourceRequestSchema,
|
|
13
|
+
RootsListChangedNotificationSchema,
|
|
13
14
|
SetLevelRequestSchema
|
|
14
15
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
15
16
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
16
17
|
import { z } from "zod";
|
|
18
|
+
import { setTimeout as delay } from "timers/promises";
|
|
17
19
|
import { readFile } from "fs/promises";
|
|
18
20
|
import { fileTypeFromBuffer } from "file-type";
|
|
19
21
|
import { EventEmitter } from "events";
|
|
@@ -85,10 +87,15 @@ var ContentResultZodSchema = z.object({
|
|
|
85
87
|
content: ContentZodSchema.array(),
|
|
86
88
|
isError: z.boolean().optional()
|
|
87
89
|
}).strict();
|
|
88
|
-
var
|
|
90
|
+
var FastMCPSessionEventEmitterBase = EventEmitter;
|
|
91
|
+
var FastMCPSessionEventEmitter = class extends FastMCPSessionEventEmitterBase {
|
|
92
|
+
};
|
|
93
|
+
var FastMCPSession = class extends FastMCPSessionEventEmitter {
|
|
89
94
|
#capabilities = {};
|
|
90
95
|
#loggingLevel = "info";
|
|
91
96
|
#server;
|
|
97
|
+
#clientCapabilities;
|
|
98
|
+
#roots = [];
|
|
92
99
|
constructor({
|
|
93
100
|
name,
|
|
94
101
|
version,
|
|
@@ -96,6 +103,7 @@ var FastMCPSession = class {
|
|
|
96
103
|
resources,
|
|
97
104
|
prompts
|
|
98
105
|
}) {
|
|
106
|
+
super();
|
|
99
107
|
if (tools.length) {
|
|
100
108
|
this.#capabilities.tools = {};
|
|
101
109
|
}
|
|
@@ -112,6 +120,7 @@ var FastMCPSession = class {
|
|
|
112
120
|
);
|
|
113
121
|
this.setupErrorHandling();
|
|
114
122
|
this.setupLoggingHandlers();
|
|
123
|
+
this.setupRootsHandlers();
|
|
115
124
|
if (tools.length) {
|
|
116
125
|
this.setupToolHandlers(tools);
|
|
117
126
|
}
|
|
@@ -122,11 +131,39 @@ var FastMCPSession = class {
|
|
|
122
131
|
this.setupPromptHandlers(prompts);
|
|
123
132
|
}
|
|
124
133
|
}
|
|
134
|
+
get clientCapabilities() {
|
|
135
|
+
return this.#clientCapabilities ?? null;
|
|
136
|
+
}
|
|
125
137
|
get server() {
|
|
126
138
|
return this.#server;
|
|
127
139
|
}
|
|
128
|
-
connect(transport) {
|
|
129
|
-
this.#server.
|
|
140
|
+
async connect(transport) {
|
|
141
|
+
if (this.#server.transport) {
|
|
142
|
+
throw new UnexpectedStateError("Server is already connected");
|
|
143
|
+
}
|
|
144
|
+
await this.#server.connect(transport);
|
|
145
|
+
let attempt = 0;
|
|
146
|
+
while (attempt++ < 10) {
|
|
147
|
+
const capabilities = await this.#server.getClientCapabilities();
|
|
148
|
+
if (capabilities) {
|
|
149
|
+
this.#clientCapabilities = capabilities;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
await delay(100);
|
|
153
|
+
}
|
|
154
|
+
if (this.#clientCapabilities?.roots) {
|
|
155
|
+
const roots = await this.#server.listRoots();
|
|
156
|
+
this.#roots = roots.roots;
|
|
157
|
+
}
|
|
158
|
+
if (!this.#clientCapabilities) {
|
|
159
|
+
throw new UnexpectedStateError("Server did not connect");
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
get roots() {
|
|
163
|
+
return this.#roots;
|
|
164
|
+
}
|
|
165
|
+
async close() {
|
|
166
|
+
await this.#server.close();
|
|
130
167
|
}
|
|
131
168
|
setupErrorHandling() {
|
|
132
169
|
this.#server.onerror = (error) => {
|
|
@@ -136,6 +173,19 @@ var FastMCPSession = class {
|
|
|
136
173
|
get loggingLevel() {
|
|
137
174
|
return this.#loggingLevel;
|
|
138
175
|
}
|
|
176
|
+
setupRootsHandlers() {
|
|
177
|
+
this.#server.setNotificationHandler(
|
|
178
|
+
RootsListChangedNotificationSchema,
|
|
179
|
+
() => {
|
|
180
|
+
this.#server.listRoots().then((roots) => {
|
|
181
|
+
this.#roots = roots.roots;
|
|
182
|
+
this.emit("rootsChanged", {
|
|
183
|
+
roots: roots.roots
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
);
|
|
188
|
+
}
|
|
139
189
|
setupLoggingHandlers() {
|
|
140
190
|
this.#server.setRequestHandler(SetLevelRequestSchema, (request) => {
|
|
141
191
|
this.#loggingLevel = request.params.level;
|
|
@@ -417,42 +467,24 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
417
467
|
endpoint: options.sse.endpoint,
|
|
418
468
|
port: options.sse.port,
|
|
419
469
|
createServer: async () => {
|
|
420
|
-
|
|
470
|
+
return new FastMCPSession({
|
|
421
471
|
name: this.#options.name,
|
|
422
472
|
version: this.#options.version,
|
|
423
473
|
tools: this.#tools,
|
|
424
474
|
resources: this.#resources,
|
|
425
475
|
prompts: this.#prompts
|
|
426
476
|
});
|
|
427
|
-
this.#sessions.push(session);
|
|
428
|
-
return session.server;
|
|
429
477
|
},
|
|
430
|
-
onClose: (
|
|
431
|
-
const session = this.#sessions.find(
|
|
432
|
-
(session2) => session2.server === server
|
|
433
|
-
);
|
|
434
|
-
if (!session) {
|
|
435
|
-
throw new UnexpectedStateError("Server not found");
|
|
436
|
-
}
|
|
437
|
-
this.#sessions = this.#sessions.filter(
|
|
438
|
-
(maybeOurSession) => maybeOurSession !== session
|
|
439
|
-
);
|
|
478
|
+
onClose: (session) => {
|
|
440
479
|
this.emit("disconnect", {
|
|
441
480
|
session
|
|
442
481
|
});
|
|
443
482
|
},
|
|
444
|
-
onConnect: async (
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
throw new UnexpectedStateError("Server not found");
|
|
450
|
-
}
|
|
451
|
-
setTimeout(() => {
|
|
452
|
-
this.emit("connect", {
|
|
453
|
-
session
|
|
454
|
-
});
|
|
455
|
-
}, 100);
|
|
483
|
+
onConnect: async (session) => {
|
|
484
|
+
this.#sessions.push(session);
|
|
485
|
+
this.emit("connect", {
|
|
486
|
+
session
|
|
487
|
+
});
|
|
456
488
|
}
|
|
457
489
|
});
|
|
458
490
|
console.error(
|
package/dist/FastMCP.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/FastMCP.ts"],"sourcesContent":["import { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport {\n CallToolRequestSchema,\n ErrorCode,\n GetPromptRequestSchema,\n ListPromptsRequestSchema,\n ListResourcesRequestSchema,\n ListToolsRequestSchema,\n McpError,\n ReadResourceRequestSchema,\n ServerCapabilities,\n SetLevelRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { zodToJsonSchema } from \"zod-to-json-schema\";\nimport { z } from \"zod\";\nimport { readFile } from \"fs/promises\";\nimport { fileTypeFromBuffer } from \"file-type\";\nimport { StrictEventEmitter } from \"strict-event-emitter-types\";\nimport { EventEmitter } from \"events\";\nimport { startSSEServer } from \"mcp-proxy\";\nimport { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\n\nexport type SSEServer = {\n close: () => Promise<void>;\n};\n\ntype FastMCPEvents = {\n connect: (event: { session: FastMCPSession }) => void;\n disconnect: (event: { session: FastMCPSession }) => void;\n};\n\n/**\n * Generates an image content object from a URL, file path, or buffer.\n */\nexport const imageContent = async (\n input: { url: string } | { path: string } | { buffer: Buffer },\n): Promise<ImageContent> => {\n let rawData: Buffer;\n\n if (\"url\" in input) {\n const response = await fetch(input.url);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch image from URL: ${response.statusText}`);\n }\n\n rawData = Buffer.from(await response.arrayBuffer());\n } else if (\"path\" in input) {\n rawData = await readFile(input.path);\n } else if (\"buffer\" in input) {\n rawData = input.buffer;\n } else {\n throw new Error(\n \"Invalid input: Provide a valid 'url', 'path', or 'buffer'\",\n );\n }\n\n const mimeType = await fileTypeFromBuffer(rawData);\n\n const base64Data = rawData.toString(\"base64\");\n\n return {\n type: \"image\",\n data: base64Data,\n mimeType: mimeType?.mime ?? \"image/png\",\n } as const;\n};\n\nabstract class FastMCPError extends Error {\n public constructor(message?: string) {\n super(message);\n this.name = new.target.name;\n }\n}\n\ntype Extra = unknown;\n\ntype Extras = Record<string, Extra>;\n\nclass UnexpectedStateError extends FastMCPError {\n public extras?: Extras;\n\n public constructor(message: string, extras?: Extras) {\n super(message);\n this.name = new.target.name;\n this.extras = extras;\n }\n}\n\n/**\n * An error that is meant to be surfaced to the user.\n */\nexport class UserError extends UnexpectedStateError {}\n\ntype ToolParameters = z.ZodTypeAny;\n\ntype Literal = boolean | null | number | string | undefined;\n\ntype SerializableValue =\n | Literal\n | SerializableValue[]\n | { [key: string]: SerializableValue };\n\ntype Progress = {\n /**\n * The progress thus far. This should increase every time progress is made, even if the total is unknown.\n */\n progress: number;\n /**\n * Total number of items to process (or total progress required), if known.\n */\n total?: number;\n};\n\ntype Context = {\n reportProgress: (progress: Progress) => Promise<void>;\n log: {\n debug: (message: string, data?: SerializableValue) => void;\n error: (message: string, data?: SerializableValue) => void;\n info: (message: string, data?: SerializableValue) => void;\n warn: (message: string, data?: SerializableValue) => void;\n };\n};\n\ntype TextContent = {\n type: \"text\";\n text: string;\n};\n\nconst TextContentZodSchema = z\n .object({\n type: z.literal(\"text\"),\n /**\n * The text content of the message.\n */\n text: z.string(),\n })\n .strict() satisfies z.ZodType<TextContent>;\n\ntype ImageContent = {\n type: \"image\";\n data: string;\n mimeType: string;\n};\n\nconst ImageContentZodSchema = z\n .object({\n type: z.literal(\"image\"),\n /**\n * The base64-encoded image data.\n */\n data: z.string().base64(),\n /**\n * The MIME type of the image. Different providers may support different image types.\n */\n mimeType: z.string(),\n })\n .strict() satisfies z.ZodType<ImageContent>;\n\ntype Content = TextContent | ImageContent;\n\nconst ContentZodSchema = z.discriminatedUnion(\"type\", [\n TextContentZodSchema,\n ImageContentZodSchema,\n]) satisfies z.ZodType<Content>;\n\ntype ContentResult = {\n content: Content[];\n isError?: boolean;\n};\n\nconst ContentResultZodSchema = z\n .object({\n content: ContentZodSchema.array(),\n isError: z.boolean().optional(),\n })\n .strict() satisfies z.ZodType<ContentResult>;\n\ntype Tool<Params extends ToolParameters = ToolParameters> = {\n name: string;\n description?: string;\n parameters?: Params;\n execute: (\n args: z.infer<Params>,\n context: Context,\n ) => Promise<string | ContentResult | TextContent | ImageContent>;\n};\n\ntype Resource = {\n uri: string;\n name: string;\n description?: string;\n mimeType?: string;\n load: () => Promise<{ text: string } | { blob: string }>;\n};\n\ntype PromptArgument = Readonly<{\n name: string;\n description?: string;\n required?: boolean;\n}>;\n\ntype ArgumentsToObject<T extends PromptArgument[]> = {\n [K in T[number][\"name\"]]: Extract<\n T[number],\n { name: K }\n >[\"required\"] extends true\n ? string\n : string | undefined;\n};\n\ntype Prompt<\n Arguments extends PromptArgument[] = PromptArgument[],\n Args = ArgumentsToObject<Arguments>,\n> = {\n name: string;\n description?: string;\n arguments?: Arguments;\n load: (args: Args) => Promise<string>;\n};\n\ntype ServerOptions = {\n name: string;\n version: `${number}.${number}.${number}`;\n};\n\ntype LoggingLevel =\n | \"debug\"\n | \"info\"\n | \"notice\"\n | \"warning\"\n | \"error\"\n | \"critical\"\n | \"alert\"\n | \"emergency\";\n\nexport class FastMCPSession {\n #capabilities: ServerCapabilities = {};\n #loggingLevel: LoggingLevel = \"info\";\n #server: Server;\n\n constructor({\n name,\n version,\n tools,\n resources,\n prompts,\n }: {\n name: string;\n version: string;\n tools: Tool[];\n resources: Resource[];\n prompts: Prompt[];\n }) {\n if (tools.length) {\n this.#capabilities.tools = {};\n }\n\n if (resources.length) {\n this.#capabilities.resources = {};\n }\n\n if (prompts.length) {\n this.#capabilities.prompts = {};\n }\n\n this.#capabilities.logging = {};\n\n this.#server = new Server(\n { name: name, version: version },\n { capabilities: this.#capabilities },\n );\n\n this.setupErrorHandling();\n this.setupLoggingHandlers();\n\n if (tools.length) {\n this.setupToolHandlers(tools);\n }\n\n if (resources.length) {\n this.setupResourceHandlers(resources);\n }\n\n if (prompts.length) {\n this.setupPromptHandlers(prompts);\n }\n }\n\n public get server(): Server {\n return this.#server;\n }\n\n public connect(transport: Transport) {\n this.#server.connect(transport);\n }\n\n private setupErrorHandling() {\n this.#server.onerror = (error) => {\n console.error(\"[MCP Error]\", error);\n };\n }\n\n public get loggingLevel(): LoggingLevel {\n return this.#loggingLevel;\n }\n\n private setupLoggingHandlers() {\n this.#server.setRequestHandler(SetLevelRequestSchema, (request) => {\n this.#loggingLevel = request.params.level;\n\n return {};\n });\n }\n\n private setupToolHandlers(tools: Tool[]) {\n this.#server.setRequestHandler(ListToolsRequestSchema, async () => {\n return {\n tools: tools.map((tool) => {\n return {\n name: tool.name,\n description: tool.description,\n inputSchema: tool.parameters\n ? zodToJsonSchema(tool.parameters)\n : undefined,\n };\n }),\n };\n });\n\n this.#server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const tool = tools.find((tool) => tool.name === request.params.name);\n\n if (!tool) {\n throw new McpError(\n ErrorCode.MethodNotFound,\n `Unknown tool: ${request.params.name}`,\n );\n }\n\n let args: any = undefined;\n\n if (tool.parameters) {\n const parsed = tool.parameters.safeParse(request.params.arguments);\n\n if (!parsed.success) {\n throw new McpError(\n ErrorCode.InvalidRequest,\n `Invalid ${request.params.name} arguments`,\n );\n }\n\n args = parsed.data;\n }\n\n const progressToken = request.params?._meta?.progressToken;\n\n let result: ContentResult;\n\n try {\n const reportProgress = async (progress: Progress) => {\n await this.#server.notification({\n method: \"notifications/progress\",\n params: {\n ...progress,\n progressToken,\n },\n });\n };\n\n const log = {\n debug: (message: string, context?: SerializableValue) => {\n this.#server.sendLoggingMessage({\n level: \"debug\",\n data: {\n message,\n context,\n },\n });\n },\n error: (message: string, context?: SerializableValue) => {\n this.#server.sendLoggingMessage({\n level: \"error\",\n data: {\n message,\n context,\n },\n });\n },\n info: (message: string, context?: SerializableValue) => {\n this.#server.sendLoggingMessage({\n level: \"info\",\n data: {\n message,\n context,\n },\n });\n },\n warn: (message: string, context?: SerializableValue) => {\n this.#server.sendLoggingMessage({\n level: \"warning\",\n data: {\n message,\n context,\n },\n });\n },\n };\n\n const maybeStringResult = await tool.execute(args, {\n reportProgress,\n log,\n });\n\n if (typeof maybeStringResult === \"string\") {\n result = ContentResultZodSchema.parse({\n content: [{ type: \"text\", text: maybeStringResult }],\n });\n } else if (\"type\" in maybeStringResult) {\n result = ContentResultZodSchema.parse({\n content: [maybeStringResult],\n });\n } else {\n result = ContentResultZodSchema.parse(maybeStringResult);\n }\n } catch (error) {\n if (error instanceof UserError) {\n return {\n content: [{ type: \"text\", text: error.message }],\n isError: true,\n };\n }\n\n return {\n content: [{ type: \"text\", text: `Error: ${error}` }],\n isError: true,\n };\n }\n\n return result;\n });\n }\n\n private setupResourceHandlers(resources: Resource[]) {\n this.#server.setRequestHandler(ListResourcesRequestSchema, async () => {\n return {\n resources: resources.map((resource) => {\n return {\n uri: resource.uri,\n name: resource.name,\n mimeType: resource.mimeType,\n };\n }),\n };\n });\n\n this.#server.setRequestHandler(\n ReadResourceRequestSchema,\n async (request) => {\n const resource = resources.find(\n (resource) => resource.uri === request.params.uri,\n );\n\n if (!resource) {\n throw new McpError(\n ErrorCode.MethodNotFound,\n `Unknown resource: ${request.params.uri}`,\n );\n }\n\n let result: Awaited<ReturnType<Resource[\"load\"]>>;\n\n try {\n result = await resource.load();\n } catch (error) {\n throw new McpError(\n ErrorCode.InternalError,\n `Error reading resource: ${error}`,\n {\n uri: resource.uri,\n },\n );\n }\n\n return {\n contents: [\n {\n uri: resource.uri,\n mimeType: resource.mimeType,\n ...result,\n },\n ],\n };\n },\n );\n }\n\n private setupPromptHandlers(prompts: Prompt[]) {\n this.#server.setRequestHandler(ListPromptsRequestSchema, async () => {\n return {\n prompts: prompts.map((prompt) => {\n return {\n name: prompt.name,\n description: prompt.description,\n arguments: prompt.arguments,\n };\n }),\n };\n });\n\n this.#server.setRequestHandler(GetPromptRequestSchema, async (request) => {\n const prompt = prompts.find(\n (prompt) => prompt.name === request.params.name,\n );\n\n if (!prompt) {\n throw new McpError(\n ErrorCode.MethodNotFound,\n `Unknown prompt: ${request.params.name}`,\n );\n }\n\n const args = request.params.arguments;\n\n if (prompt.arguments) {\n for (const arg of prompt.arguments) {\n if (arg.required && !(args && arg.name in args)) {\n throw new McpError(\n ErrorCode.InvalidRequest,\n `Missing required argument: ${arg.name}`,\n );\n }\n }\n }\n\n let result: Awaited<ReturnType<Prompt[\"load\"]>>;\n\n try {\n result = await prompt.load(args as Record<string, string | undefined>);\n } catch (error) {\n throw new McpError(\n ErrorCode.InternalError,\n `Error loading prompt: ${error}`,\n );\n }\n\n return {\n description: prompt.description,\n messages: [\n {\n role: \"user\",\n content: { type: \"text\", text: result },\n },\n ],\n };\n });\n }\n}\n\nconst FastMCPEventEmitterBase: {\n new (): StrictEventEmitter<EventEmitter, FastMCPEvents>;\n} = EventEmitter;\n\nclass FastMCPEventEmitter extends FastMCPEventEmitterBase {}\n\nexport class FastMCP extends FastMCPEventEmitter {\n #options: ServerOptions;\n #prompts: Prompt[] = [];\n #resources: Resource[] = [];\n #sessions: FastMCPSession[] = [];\n #sseServer: SSEServer | null = null;\n #tools: Tool[] = [];\n\n constructor(public options: ServerOptions) {\n super();\n\n this.#options = options;\n }\n\n public get sessions(): FastMCPSession[] {\n return this.#sessions;\n }\n\n /**\n * Adds a tool to the server.\n */\n public addTool<Params extends ToolParameters>(tool: Tool<Params>) {\n this.#tools.push(tool as unknown as Tool);\n }\n\n /**\n * Adds a resource to the server.\n */\n public addResource(resource: Resource) {\n this.#resources.push(resource);\n }\n\n /**\n * Adds a prompt to the server.\n */\n public addPrompt<const Args extends PromptArgument[]>(prompt: Prompt<Args>) {\n this.#prompts.push(prompt);\n }\n\n /**\n * Starts the server.\n */\n public async start(\n options:\n | { transportType: \"stdio\" }\n | {\n transportType: \"sse\";\n sse: { endpoint: `/${string}`; port: number };\n } = {\n transportType: \"stdio\",\n },\n ) {\n if (options.transportType === \"stdio\") {\n const transport = new StdioServerTransport();\n\n const session = new FastMCPSession({\n name: this.#options.name,\n version: this.#options.version,\n tools: this.#tools,\n resources: this.#resources,\n prompts: this.#prompts,\n });\n\n await session.connect(transport);\n\n this.#sessions.push(session);\n\n this.emit(\"connect\", {\n session,\n });\n\n console.error(`server is running on stdio`);\n } else if (options.transportType === \"sse\") {\n this.#sseServer = await startSSEServer({\n endpoint: options.sse.endpoint as `/${string}`,\n port: options.sse.port,\n createServer: async () => {\n const session = new FastMCPSession({\n name: this.#options.name,\n version: this.#options.version,\n tools: this.#tools,\n resources: this.#resources,\n prompts: this.#prompts,\n });\n\n this.#sessions.push(session);\n\n return session.server;\n },\n onClose: (server) => {\n const session = this.#sessions.find(\n (session) => session.server === server,\n );\n\n if (!session) {\n throw new UnexpectedStateError(\"Server not found\");\n }\n\n this.#sessions = this.#sessions.filter(\n (maybeOurSession) => maybeOurSession !== session,\n );\n\n this.emit(\"disconnect\", {\n session,\n });\n },\n onConnect: async (server) => {\n const session = this.#sessions.find(\n (session) => session.server === server,\n );\n\n if (!session) {\n throw new UnexpectedStateError(\"Server not found\");\n }\n\n // TODO investigate where is the race condition\n setTimeout(() => {\n this.emit(\"connect\", {\n session,\n });\n }, 100);\n },\n });\n\n console.error(\n `server is running on SSE at http://localhost:${options.sse.port}${options.sse.endpoint}`,\n );\n } else {\n throw new Error(\"Invalid transport type\");\n }\n }\n\n /**\n * Stops the server.\n */\n public async stop() {\n if (this.#sseServer) {\n this.#sseServer.close();\n }\n }\n}\n"],"mappings":"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","names":["tool","resource","prompt","session"]}
|
|
1
|
+
{"version":3,"sources":["../src/FastMCP.ts"],"sourcesContent":["import { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport {\n CallToolRequestSchema,\n ClientCapabilities,\n ErrorCode,\n GetPromptRequestSchema,\n ListPromptsRequestSchema,\n ListResourcesRequestSchema,\n ListToolsRequestSchema,\n McpError,\n ReadResourceRequestSchema,\n Root,\n RootsListChangedNotificationSchema,\n ServerCapabilities,\n SetLevelRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { zodToJsonSchema } from \"zod-to-json-schema\";\nimport { z } from \"zod\";\nimport { setTimeout as delay } from \"timers/promises\";\nimport { readFile } from \"fs/promises\";\nimport { fileTypeFromBuffer } from \"file-type\";\nimport { StrictEventEmitter } from \"strict-event-emitter-types\";\nimport { EventEmitter } from \"events\";\nimport { startSSEServer } from \"mcp-proxy\";\nimport { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\n\nexport type SSEServer = {\n close: () => Promise<void>;\n};\n\ntype FastMCPEvents = {\n connect: (event: { session: FastMCPSession }) => void;\n disconnect: (event: { session: FastMCPSession }) => void;\n};\n\ntype FastMCPSessionEvents = {\n rootsChanged: (event: { roots: Root[] }) => void;\n};\n\n/**\n * Generates an image content object from a URL, file path, or buffer.\n */\nexport const imageContent = async (\n input: { url: string } | { path: string } | { buffer: Buffer },\n): Promise<ImageContent> => {\n let rawData: Buffer;\n\n if (\"url\" in input) {\n const response = await fetch(input.url);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch image from URL: ${response.statusText}`);\n }\n\n rawData = Buffer.from(await response.arrayBuffer());\n } else if (\"path\" in input) {\n rawData = await readFile(input.path);\n } else if (\"buffer\" in input) {\n rawData = input.buffer;\n } else {\n throw new Error(\n \"Invalid input: Provide a valid 'url', 'path', or 'buffer'\",\n );\n }\n\n const mimeType = await fileTypeFromBuffer(rawData);\n\n const base64Data = rawData.toString(\"base64\");\n\n return {\n type: \"image\",\n data: base64Data,\n mimeType: mimeType?.mime ?? \"image/png\",\n } as const;\n};\n\nabstract class FastMCPError extends Error {\n public constructor(message?: string) {\n super(message);\n this.name = new.target.name;\n }\n}\n\ntype Extra = unknown;\n\ntype Extras = Record<string, Extra>;\n\nclass UnexpectedStateError extends FastMCPError {\n public extras?: Extras;\n\n public constructor(message: string, extras?: Extras) {\n super(message);\n this.name = new.target.name;\n this.extras = extras;\n }\n}\n\n/**\n * An error that is meant to be surfaced to the user.\n */\nexport class UserError extends UnexpectedStateError {}\n\ntype ToolParameters = z.ZodTypeAny;\n\ntype Literal = boolean | null | number | string | undefined;\n\ntype SerializableValue =\n | Literal\n | SerializableValue[]\n | { [key: string]: SerializableValue };\n\ntype Progress = {\n /**\n * The progress thus far. This should increase every time progress is made, even if the total is unknown.\n */\n progress: number;\n /**\n * Total number of items to process (or total progress required), if known.\n */\n total?: number;\n};\n\ntype Context = {\n reportProgress: (progress: Progress) => Promise<void>;\n log: {\n debug: (message: string, data?: SerializableValue) => void;\n error: (message: string, data?: SerializableValue) => void;\n info: (message: string, data?: SerializableValue) => void;\n warn: (message: string, data?: SerializableValue) => void;\n };\n};\n\ntype TextContent = {\n type: \"text\";\n text: string;\n};\n\nconst TextContentZodSchema = z\n .object({\n type: z.literal(\"text\"),\n /**\n * The text content of the message.\n */\n text: z.string(),\n })\n .strict() satisfies z.ZodType<TextContent>;\n\ntype ImageContent = {\n type: \"image\";\n data: string;\n mimeType: string;\n};\n\nconst ImageContentZodSchema = z\n .object({\n type: z.literal(\"image\"),\n /**\n * The base64-encoded image data.\n */\n data: z.string().base64(),\n /**\n * The MIME type of the image. Different providers may support different image types.\n */\n mimeType: z.string(),\n })\n .strict() satisfies z.ZodType<ImageContent>;\n\ntype Content = TextContent | ImageContent;\n\nconst ContentZodSchema = z.discriminatedUnion(\"type\", [\n TextContentZodSchema,\n ImageContentZodSchema,\n]) satisfies z.ZodType<Content>;\n\ntype ContentResult = {\n content: Content[];\n isError?: boolean;\n};\n\nconst ContentResultZodSchema = z\n .object({\n content: ContentZodSchema.array(),\n isError: z.boolean().optional(),\n })\n .strict() satisfies z.ZodType<ContentResult>;\n\ntype Tool<Params extends ToolParameters = ToolParameters> = {\n name: string;\n description?: string;\n parameters?: Params;\n execute: (\n args: z.infer<Params>,\n context: Context,\n ) => Promise<string | ContentResult | TextContent | ImageContent>;\n};\n\ntype Resource = {\n uri: string;\n name: string;\n description?: string;\n mimeType?: string;\n load: () => Promise<{ text: string } | { blob: string }>;\n};\n\ntype PromptArgument = Readonly<{\n name: string;\n description?: string;\n required?: boolean;\n}>;\n\ntype ArgumentsToObject<T extends PromptArgument[]> = {\n [K in T[number][\"name\"]]: Extract<\n T[number],\n { name: K }\n >[\"required\"] extends true\n ? string\n : string | undefined;\n};\n\ntype Prompt<\n Arguments extends PromptArgument[] = PromptArgument[],\n Args = ArgumentsToObject<Arguments>,\n> = {\n name: string;\n description?: string;\n arguments?: Arguments;\n load: (args: Args) => Promise<string>;\n};\n\ntype ServerOptions = {\n name: string;\n version: `${number}.${number}.${number}`;\n};\n\ntype LoggingLevel =\n | \"debug\"\n | \"info\"\n | \"notice\"\n | \"warning\"\n | \"error\"\n | \"critical\"\n | \"alert\"\n | \"emergency\";\n\nconst FastMCPSessionEventEmitterBase: {\n new (): StrictEventEmitter<EventEmitter, FastMCPSessionEvents>;\n} = EventEmitter;\n\nclass FastMCPSessionEventEmitter extends FastMCPSessionEventEmitterBase {}\n\nexport class FastMCPSession extends FastMCPSessionEventEmitter {\n #capabilities: ServerCapabilities = {};\n #loggingLevel: LoggingLevel = \"info\";\n #server: Server;\n #clientCapabilities?: ClientCapabilities;\n #roots: Root[] = [];\n\n constructor({\n name,\n version,\n tools,\n resources,\n prompts,\n }: {\n name: string;\n version: string;\n tools: Tool[];\n resources: Resource[];\n prompts: Prompt[];\n }) {\n super();\n\n if (tools.length) {\n this.#capabilities.tools = {};\n }\n\n if (resources.length) {\n this.#capabilities.resources = {};\n }\n\n if (prompts.length) {\n this.#capabilities.prompts = {};\n }\n\n this.#capabilities.logging = {};\n\n this.#server = new Server(\n { name: name, version: version },\n { capabilities: this.#capabilities },\n );\n\n this.setupErrorHandling();\n this.setupLoggingHandlers();\n this.setupRootsHandlers();\n\n if (tools.length) {\n this.setupToolHandlers(tools);\n }\n\n if (resources.length) {\n this.setupResourceHandlers(resources);\n }\n\n if (prompts.length) {\n this.setupPromptHandlers(prompts);\n }\n }\n\n public get clientCapabilities(): ClientCapabilities | null {\n return this.#clientCapabilities ?? null;\n }\n\n public get server(): Server {\n return this.#server;\n }\n\n public async connect(transport: Transport) {\n if (this.#server.transport) {\n throw new UnexpectedStateError(\"Server is already connected\");\n }\n\n await this.#server.connect(transport);\n\n let attempt = 0;\n\n while (attempt++ < 10) {\n const capabilities = await this.#server.getClientCapabilities();\n\n if (capabilities) {\n this.#clientCapabilities = capabilities;\n\n break;\n }\n\n await delay(100);\n }\n\n if (this.#clientCapabilities?.roots) {\n const roots = await this.#server.listRoots();\n\n this.#roots = roots.roots;\n }\n\n if (!this.#clientCapabilities) {\n throw new UnexpectedStateError(\"Server did not connect\");\n }\n }\n\n public get roots(): Root[] {\n return this.#roots;\n }\n\n public async close() {\n await this.#server.close();\n }\n\n private setupErrorHandling() {\n this.#server.onerror = (error) => {\n console.error(\"[MCP Error]\", error);\n };\n }\n\n public get loggingLevel(): LoggingLevel {\n return this.#loggingLevel;\n }\n\n private setupRootsHandlers() {\n this.#server.setNotificationHandler(\n RootsListChangedNotificationSchema,\n () => {\n this.#server.listRoots().then((roots) => {\n this.#roots = roots.roots;\n\n this.emit(\"rootsChanged\", {\n roots: roots.roots,\n });\n });\n },\n );\n }\n\n private setupLoggingHandlers() {\n this.#server.setRequestHandler(SetLevelRequestSchema, (request) => {\n this.#loggingLevel = request.params.level;\n\n return {};\n });\n }\n\n private setupToolHandlers(tools: Tool[]) {\n this.#server.setRequestHandler(ListToolsRequestSchema, async () => {\n return {\n tools: tools.map((tool) => {\n return {\n name: tool.name,\n description: tool.description,\n inputSchema: tool.parameters\n ? zodToJsonSchema(tool.parameters)\n : undefined,\n };\n }),\n };\n });\n\n this.#server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const tool = tools.find((tool) => tool.name === request.params.name);\n\n if (!tool) {\n throw new McpError(\n ErrorCode.MethodNotFound,\n `Unknown tool: ${request.params.name}`,\n );\n }\n\n let args: any = undefined;\n\n if (tool.parameters) {\n const parsed = tool.parameters.safeParse(request.params.arguments);\n\n if (!parsed.success) {\n throw new McpError(\n ErrorCode.InvalidRequest,\n `Invalid ${request.params.name} arguments`,\n );\n }\n\n args = parsed.data;\n }\n\n const progressToken = request.params?._meta?.progressToken;\n\n let result: ContentResult;\n\n try {\n const reportProgress = async (progress: Progress) => {\n await this.#server.notification({\n method: \"notifications/progress\",\n params: {\n ...progress,\n progressToken,\n },\n });\n };\n\n const log = {\n debug: (message: string, context?: SerializableValue) => {\n this.#server.sendLoggingMessage({\n level: \"debug\",\n data: {\n message,\n context,\n },\n });\n },\n error: (message: string, context?: SerializableValue) => {\n this.#server.sendLoggingMessage({\n level: \"error\",\n data: {\n message,\n context,\n },\n });\n },\n info: (message: string, context?: SerializableValue) => {\n this.#server.sendLoggingMessage({\n level: \"info\",\n data: {\n message,\n context,\n },\n });\n },\n warn: (message: string, context?: SerializableValue) => {\n this.#server.sendLoggingMessage({\n level: \"warning\",\n data: {\n message,\n context,\n },\n });\n },\n };\n\n const maybeStringResult = await tool.execute(args, {\n reportProgress,\n log,\n });\n\n if (typeof maybeStringResult === \"string\") {\n result = ContentResultZodSchema.parse({\n content: [{ type: \"text\", text: maybeStringResult }],\n });\n } else if (\"type\" in maybeStringResult) {\n result = ContentResultZodSchema.parse({\n content: [maybeStringResult],\n });\n } else {\n result = ContentResultZodSchema.parse(maybeStringResult);\n }\n } catch (error) {\n if (error instanceof UserError) {\n return {\n content: [{ type: \"text\", text: error.message }],\n isError: true,\n };\n }\n\n return {\n content: [{ type: \"text\", text: `Error: ${error}` }],\n isError: true,\n };\n }\n\n return result;\n });\n }\n\n private setupResourceHandlers(resources: Resource[]) {\n this.#server.setRequestHandler(ListResourcesRequestSchema, async () => {\n return {\n resources: resources.map((resource) => {\n return {\n uri: resource.uri,\n name: resource.name,\n mimeType: resource.mimeType,\n };\n }),\n };\n });\n\n this.#server.setRequestHandler(\n ReadResourceRequestSchema,\n async (request) => {\n const resource = resources.find(\n (resource) => resource.uri === request.params.uri,\n );\n\n if (!resource) {\n throw new McpError(\n ErrorCode.MethodNotFound,\n `Unknown resource: ${request.params.uri}`,\n );\n }\n\n let result: Awaited<ReturnType<Resource[\"load\"]>>;\n\n try {\n result = await resource.load();\n } catch (error) {\n throw new McpError(\n ErrorCode.InternalError,\n `Error reading resource: ${error}`,\n {\n uri: resource.uri,\n },\n );\n }\n\n return {\n contents: [\n {\n uri: resource.uri,\n mimeType: resource.mimeType,\n ...result,\n },\n ],\n };\n },\n );\n }\n\n private setupPromptHandlers(prompts: Prompt[]) {\n this.#server.setRequestHandler(ListPromptsRequestSchema, async () => {\n return {\n prompts: prompts.map((prompt) => {\n return {\n name: prompt.name,\n description: prompt.description,\n arguments: prompt.arguments,\n };\n }),\n };\n });\n\n this.#server.setRequestHandler(GetPromptRequestSchema, async (request) => {\n const prompt = prompts.find(\n (prompt) => prompt.name === request.params.name,\n );\n\n if (!prompt) {\n throw new McpError(\n ErrorCode.MethodNotFound,\n `Unknown prompt: ${request.params.name}`,\n );\n }\n\n const args = request.params.arguments;\n\n if (prompt.arguments) {\n for (const arg of prompt.arguments) {\n if (arg.required && !(args && arg.name in args)) {\n throw new McpError(\n ErrorCode.InvalidRequest,\n `Missing required argument: ${arg.name}`,\n );\n }\n }\n }\n\n let result: Awaited<ReturnType<Prompt[\"load\"]>>;\n\n try {\n result = await prompt.load(args as Record<string, string | undefined>);\n } catch (error) {\n throw new McpError(\n ErrorCode.InternalError,\n `Error loading prompt: ${error}`,\n );\n }\n\n return {\n description: prompt.description,\n messages: [\n {\n role: \"user\",\n content: { type: \"text\", text: result },\n },\n ],\n };\n });\n }\n}\n\nconst FastMCPEventEmitterBase: {\n new (): StrictEventEmitter<EventEmitter, FastMCPEvents>;\n} = EventEmitter;\n\nclass FastMCPEventEmitter extends FastMCPEventEmitterBase {}\n\nexport class FastMCP extends FastMCPEventEmitter {\n #options: ServerOptions;\n #prompts: Prompt[] = [];\n #resources: Resource[] = [];\n #sessions: FastMCPSession[] = [];\n #sseServer: SSEServer | null = null;\n #tools: Tool[] = [];\n\n constructor(public options: ServerOptions) {\n super();\n\n this.#options = options;\n }\n\n public get sessions(): FastMCPSession[] {\n return this.#sessions;\n }\n\n /**\n * Adds a tool to the server.\n */\n public addTool<Params extends ToolParameters>(tool: Tool<Params>) {\n this.#tools.push(tool as unknown as Tool);\n }\n\n /**\n * Adds a resource to the server.\n */\n public addResource(resource: Resource) {\n this.#resources.push(resource);\n }\n\n /**\n * Adds a prompt to the server.\n */\n public addPrompt<const Args extends PromptArgument[]>(prompt: Prompt<Args>) {\n this.#prompts.push(prompt);\n }\n\n /**\n * Starts the server.\n */\n public async start(\n options:\n | { transportType: \"stdio\" }\n | {\n transportType: \"sse\";\n sse: { endpoint: `/${string}`; port: number };\n } = {\n transportType: \"stdio\",\n },\n ) {\n if (options.transportType === \"stdio\") {\n const transport = new StdioServerTransport();\n\n const session = new FastMCPSession({\n name: this.#options.name,\n version: this.#options.version,\n tools: this.#tools,\n resources: this.#resources,\n prompts: this.#prompts,\n });\n\n await session.connect(transport);\n\n this.#sessions.push(session);\n\n this.emit(\"connect\", {\n session,\n });\n\n console.error(`server is running on stdio`);\n } else if (options.transportType === \"sse\") {\n this.#sseServer = await startSSEServer<FastMCPSession>({\n endpoint: options.sse.endpoint as `/${string}`,\n port: options.sse.port,\n createServer: async () => {\n return new FastMCPSession({\n name: this.#options.name,\n version: this.#options.version,\n tools: this.#tools,\n resources: this.#resources,\n prompts: this.#prompts,\n });\n },\n onClose: (session) => {\n this.emit(\"disconnect\", {\n session,\n });\n },\n onConnect: async (session) => {\n this.#sessions.push(session);\n\n this.emit(\"connect\", {\n session,\n });\n },\n });\n\n console.error(\n `server is running on SSE at http://localhost:${options.sse.port}${options.sse.endpoint}`,\n );\n } else {\n throw new Error(\"Invalid transport type\");\n }\n }\n\n /**\n * Stops the server.\n */\n public async stop() {\n if (this.#sseServer) {\n this.#sseServer.close();\n }\n }\n}\n"],"mappings":"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","names":["tool","resource","prompt"]}
|
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastmcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"main": "dist/FastMCP.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "tsup",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
25
25
|
"execa": "^9.5.2",
|
|
26
26
|
"file-type": "^19.6.0",
|
|
27
|
-
"mcp-proxy": "^2.0
|
|
27
|
+
"mcp-proxy": "^2.2.0",
|
|
28
28
|
"strict-event-emitter-types": "^2.0.0",
|
|
29
29
|
"yargs": "^17.7.2",
|
|
30
30
|
"zod": "^3.24.1",
|
package/src/FastMCP.test.ts
CHANGED
|
@@ -7,15 +7,19 @@ import { getRandomPort } from "get-port-please";
|
|
|
7
7
|
import { setTimeout as delay } from "timers/promises";
|
|
8
8
|
import {
|
|
9
9
|
ErrorCode,
|
|
10
|
+
ListRootsRequestSchema,
|
|
10
11
|
LoggingMessageNotificationSchema,
|
|
11
12
|
McpError,
|
|
13
|
+
Root,
|
|
12
14
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
13
15
|
|
|
14
16
|
const runWithTestServer = async ({
|
|
15
17
|
run,
|
|
18
|
+
client: clientOverride,
|
|
16
19
|
start,
|
|
17
20
|
}: {
|
|
18
21
|
start: () => Promise<FastMCP>;
|
|
22
|
+
client?: Client;
|
|
19
23
|
run: ({
|
|
20
24
|
client,
|
|
21
25
|
server,
|
|
@@ -38,15 +42,17 @@ const runWithTestServer = async ({
|
|
|
38
42
|
});
|
|
39
43
|
|
|
40
44
|
try {
|
|
41
|
-
const client =
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
const client =
|
|
46
|
+
clientOverride ??
|
|
47
|
+
new Client(
|
|
48
|
+
{
|
|
49
|
+
name: "example-client",
|
|
50
|
+
version: "1.0.0",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
capabilities: {},
|
|
54
|
+
},
|
|
55
|
+
);
|
|
50
56
|
|
|
51
57
|
const transport = new SSEClientTransport(
|
|
52
58
|
new URL(`http://localhost:${port}/sse`),
|
|
@@ -657,3 +663,183 @@ test("handles multiple clients", async () => {
|
|
|
657
663
|
|
|
658
664
|
await server.stop();
|
|
659
665
|
});
|
|
666
|
+
|
|
667
|
+
test("session knows about client capabilities", async () => {
|
|
668
|
+
const client = new Client(
|
|
669
|
+
{
|
|
670
|
+
name: "example-client",
|
|
671
|
+
version: "1.0.0",
|
|
672
|
+
},
|
|
673
|
+
{
|
|
674
|
+
capabilities: {
|
|
675
|
+
roots: {
|
|
676
|
+
listChanged: true,
|
|
677
|
+
},
|
|
678
|
+
},
|
|
679
|
+
},
|
|
680
|
+
);
|
|
681
|
+
|
|
682
|
+
client.setRequestHandler(ListRootsRequestSchema, () => {
|
|
683
|
+
return {
|
|
684
|
+
roots: [
|
|
685
|
+
{
|
|
686
|
+
uri: "file:///home/user/projects/frontend",
|
|
687
|
+
name: "Frontend Repository",
|
|
688
|
+
},
|
|
689
|
+
],
|
|
690
|
+
};
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
await runWithTestServer({
|
|
694
|
+
client,
|
|
695
|
+
start: async () => {
|
|
696
|
+
const server = new FastMCP({
|
|
697
|
+
name: "Test",
|
|
698
|
+
version: "1.0.0",
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
return server;
|
|
702
|
+
},
|
|
703
|
+
run: async ({ session }) => {
|
|
704
|
+
expect(session.clientCapabilities).toEqual({
|
|
705
|
+
roots: {
|
|
706
|
+
listChanged: true,
|
|
707
|
+
},
|
|
708
|
+
});
|
|
709
|
+
},
|
|
710
|
+
});
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
test("session knows about roots", async () => {
|
|
714
|
+
const client = new Client(
|
|
715
|
+
{
|
|
716
|
+
name: "example-client",
|
|
717
|
+
version: "1.0.0",
|
|
718
|
+
},
|
|
719
|
+
{
|
|
720
|
+
capabilities: {
|
|
721
|
+
roots: {
|
|
722
|
+
listChanged: true,
|
|
723
|
+
},
|
|
724
|
+
},
|
|
725
|
+
},
|
|
726
|
+
);
|
|
727
|
+
|
|
728
|
+
client.setRequestHandler(ListRootsRequestSchema, () => {
|
|
729
|
+
return {
|
|
730
|
+
roots: [
|
|
731
|
+
{
|
|
732
|
+
uri: "file:///home/user/projects/frontend",
|
|
733
|
+
name: "Frontend Repository",
|
|
734
|
+
},
|
|
735
|
+
],
|
|
736
|
+
};
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
await runWithTestServer({
|
|
740
|
+
client,
|
|
741
|
+
start: async () => {
|
|
742
|
+
const server = new FastMCP({
|
|
743
|
+
name: "Test",
|
|
744
|
+
version: "1.0.0",
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
return server;
|
|
748
|
+
},
|
|
749
|
+
run: async ({ session }) => {
|
|
750
|
+
expect(session.roots).toEqual([
|
|
751
|
+
{
|
|
752
|
+
uri: "file:///home/user/projects/frontend",
|
|
753
|
+
name: "Frontend Repository",
|
|
754
|
+
},
|
|
755
|
+
]);
|
|
756
|
+
},
|
|
757
|
+
});
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
test("session listens to roots changes", async () => {
|
|
761
|
+
const client = new Client(
|
|
762
|
+
{
|
|
763
|
+
name: "example-client",
|
|
764
|
+
version: "1.0.0",
|
|
765
|
+
},
|
|
766
|
+
{
|
|
767
|
+
capabilities: {
|
|
768
|
+
roots: {
|
|
769
|
+
listChanged: true,
|
|
770
|
+
},
|
|
771
|
+
},
|
|
772
|
+
},
|
|
773
|
+
);
|
|
774
|
+
|
|
775
|
+
let clientRoots: Root[] = [
|
|
776
|
+
{
|
|
777
|
+
uri: "file:///home/user/projects/frontend",
|
|
778
|
+
name: "Frontend Repository",
|
|
779
|
+
},
|
|
780
|
+
];
|
|
781
|
+
|
|
782
|
+
client.setRequestHandler(ListRootsRequestSchema, () => {
|
|
783
|
+
return {
|
|
784
|
+
roots: clientRoots,
|
|
785
|
+
};
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
await runWithTestServer({
|
|
789
|
+
client,
|
|
790
|
+
start: async () => {
|
|
791
|
+
const server = new FastMCP({
|
|
792
|
+
name: "Test",
|
|
793
|
+
version: "1.0.0",
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
return server;
|
|
797
|
+
},
|
|
798
|
+
run: async ({ session }) => {
|
|
799
|
+
expect(session.roots).toEqual([
|
|
800
|
+
{
|
|
801
|
+
uri: "file:///home/user/projects/frontend",
|
|
802
|
+
name: "Frontend Repository",
|
|
803
|
+
},
|
|
804
|
+
]);
|
|
805
|
+
|
|
806
|
+
clientRoots.push({
|
|
807
|
+
uri: "file:///home/user/projects/backend",
|
|
808
|
+
name: "Backend Repository",
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
await client.sendRootsListChanged();
|
|
812
|
+
|
|
813
|
+
const onRootsChanged = vi.fn();
|
|
814
|
+
|
|
815
|
+
session.on("rootsChanged", onRootsChanged);
|
|
816
|
+
|
|
817
|
+
await delay(100);
|
|
818
|
+
|
|
819
|
+
expect(session.roots).toEqual([
|
|
820
|
+
{
|
|
821
|
+
uri: "file:///home/user/projects/frontend",
|
|
822
|
+
name: "Frontend Repository",
|
|
823
|
+
},
|
|
824
|
+
{
|
|
825
|
+
uri: "file:///home/user/projects/backend",
|
|
826
|
+
name: "Backend Repository",
|
|
827
|
+
},
|
|
828
|
+
]);
|
|
829
|
+
|
|
830
|
+
expect(onRootsChanged).toHaveBeenCalledTimes(1);
|
|
831
|
+
expect(onRootsChanged).toHaveBeenCalledWith({
|
|
832
|
+
roots: [
|
|
833
|
+
{
|
|
834
|
+
uri: "file:///home/user/projects/frontend",
|
|
835
|
+
name: "Frontend Repository",
|
|
836
|
+
},
|
|
837
|
+
{
|
|
838
|
+
uri: "file:///home/user/projects/backend",
|
|
839
|
+
name: "Backend Repository",
|
|
840
|
+
},
|
|
841
|
+
],
|
|
842
|
+
});
|
|
843
|
+
},
|
|
844
|
+
});
|
|
845
|
+
});
|
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,11 +10,14 @@ import {
|
|
|
9
10
|
ListToolsRequestSchema,
|
|
10
11
|
McpError,
|
|
11
12
|
ReadResourceRequestSchema,
|
|
13
|
+
Root,
|
|
14
|
+
RootsListChangedNotificationSchema,
|
|
12
15
|
ServerCapabilities,
|
|
13
16
|
SetLevelRequestSchema,
|
|
14
17
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
15
18
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
16
19
|
import { z } from "zod";
|
|
20
|
+
import { setTimeout as delay } from "timers/promises";
|
|
17
21
|
import { readFile } from "fs/promises";
|
|
18
22
|
import { fileTypeFromBuffer } from "file-type";
|
|
19
23
|
import { StrictEventEmitter } from "strict-event-emitter-types";
|
|
@@ -30,6 +34,10 @@ type FastMCPEvents = {
|
|
|
30
34
|
disconnect: (event: { session: FastMCPSession }) => void;
|
|
31
35
|
};
|
|
32
36
|
|
|
37
|
+
type FastMCPSessionEvents = {
|
|
38
|
+
rootsChanged: (event: { roots: Root[] }) => void;
|
|
39
|
+
};
|
|
40
|
+
|
|
33
41
|
/**
|
|
34
42
|
* Generates an image content object from a URL, file path, or buffer.
|
|
35
43
|
*/
|
|
@@ -235,10 +243,18 @@ type LoggingLevel =
|
|
|
235
243
|
| "alert"
|
|
236
244
|
| "emergency";
|
|
237
245
|
|
|
238
|
-
|
|
246
|
+
const FastMCPSessionEventEmitterBase: {
|
|
247
|
+
new (): StrictEventEmitter<EventEmitter, FastMCPSessionEvents>;
|
|
248
|
+
} = EventEmitter;
|
|
249
|
+
|
|
250
|
+
class FastMCPSessionEventEmitter extends FastMCPSessionEventEmitterBase {}
|
|
251
|
+
|
|
252
|
+
export class FastMCPSession extends FastMCPSessionEventEmitter {
|
|
239
253
|
#capabilities: ServerCapabilities = {};
|
|
240
254
|
#loggingLevel: LoggingLevel = "info";
|
|
241
255
|
#server: Server;
|
|
256
|
+
#clientCapabilities?: ClientCapabilities;
|
|
257
|
+
#roots: Root[] = [];
|
|
242
258
|
|
|
243
259
|
constructor({
|
|
244
260
|
name,
|
|
@@ -253,6 +269,8 @@ export class FastMCPSession {
|
|
|
253
269
|
resources: Resource[];
|
|
254
270
|
prompts: Prompt[];
|
|
255
271
|
}) {
|
|
272
|
+
super();
|
|
273
|
+
|
|
256
274
|
if (tools.length) {
|
|
257
275
|
this.#capabilities.tools = {};
|
|
258
276
|
}
|
|
@@ -274,6 +292,7 @@ export class FastMCPSession {
|
|
|
274
292
|
|
|
275
293
|
this.setupErrorHandling();
|
|
276
294
|
this.setupLoggingHandlers();
|
|
295
|
+
this.setupRootsHandlers();
|
|
277
296
|
|
|
278
297
|
if (tools.length) {
|
|
279
298
|
this.setupToolHandlers(tools);
|
|
@@ -288,12 +307,52 @@ export class FastMCPSession {
|
|
|
288
307
|
}
|
|
289
308
|
}
|
|
290
309
|
|
|
310
|
+
public get clientCapabilities(): ClientCapabilities | null {
|
|
311
|
+
return this.#clientCapabilities ?? null;
|
|
312
|
+
}
|
|
313
|
+
|
|
291
314
|
public get server(): Server {
|
|
292
315
|
return this.#server;
|
|
293
316
|
}
|
|
294
317
|
|
|
295
|
-
public connect(transport: Transport) {
|
|
296
|
-
this.#server.
|
|
318
|
+
public async connect(transport: Transport) {
|
|
319
|
+
if (this.#server.transport) {
|
|
320
|
+
throw new UnexpectedStateError("Server is already connected");
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
await this.#server.connect(transport);
|
|
324
|
+
|
|
325
|
+
let attempt = 0;
|
|
326
|
+
|
|
327
|
+
while (attempt++ < 10) {
|
|
328
|
+
const capabilities = await this.#server.getClientCapabilities();
|
|
329
|
+
|
|
330
|
+
if (capabilities) {
|
|
331
|
+
this.#clientCapabilities = capabilities;
|
|
332
|
+
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
await delay(100);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (this.#clientCapabilities?.roots) {
|
|
340
|
+
const roots = await this.#server.listRoots();
|
|
341
|
+
|
|
342
|
+
this.#roots = roots.roots;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (!this.#clientCapabilities) {
|
|
346
|
+
throw new UnexpectedStateError("Server did not connect");
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
public get roots(): Root[] {
|
|
351
|
+
return this.#roots;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
public async close() {
|
|
355
|
+
await this.#server.close();
|
|
297
356
|
}
|
|
298
357
|
|
|
299
358
|
private setupErrorHandling() {
|
|
@@ -306,6 +365,21 @@ export class FastMCPSession {
|
|
|
306
365
|
return this.#loggingLevel;
|
|
307
366
|
}
|
|
308
367
|
|
|
368
|
+
private setupRootsHandlers() {
|
|
369
|
+
this.#server.setNotificationHandler(
|
|
370
|
+
RootsListChangedNotificationSchema,
|
|
371
|
+
() => {
|
|
372
|
+
this.#server.listRoots().then((roots) => {
|
|
373
|
+
this.#roots = roots.roots;
|
|
374
|
+
|
|
375
|
+
this.emit("rootsChanged", {
|
|
376
|
+
roots: roots.roots,
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
},
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
309
383
|
private setupLoggingHandlers() {
|
|
310
384
|
this.#server.setRequestHandler(SetLevelRequestSchema, (request) => {
|
|
311
385
|
this.#loggingLevel = request.params.level;
|
|
@@ -637,54 +711,29 @@ export class FastMCP extends FastMCPEventEmitter {
|
|
|
637
711
|
|
|
638
712
|
console.error(`server is running on stdio`);
|
|
639
713
|
} else if (options.transportType === "sse") {
|
|
640
|
-
this.#sseServer = await startSSEServer({
|
|
714
|
+
this.#sseServer = await startSSEServer<FastMCPSession>({
|
|
641
715
|
endpoint: options.sse.endpoint as `/${string}`,
|
|
642
716
|
port: options.sse.port,
|
|
643
717
|
createServer: async () => {
|
|
644
|
-
|
|
718
|
+
return new FastMCPSession({
|
|
645
719
|
name: this.#options.name,
|
|
646
720
|
version: this.#options.version,
|
|
647
721
|
tools: this.#tools,
|
|
648
722
|
resources: this.#resources,
|
|
649
723
|
prompts: this.#prompts,
|
|
650
724
|
});
|
|
651
|
-
|
|
652
|
-
this.#sessions.push(session);
|
|
653
|
-
|
|
654
|
-
return session.server;
|
|
655
725
|
},
|
|
656
|
-
onClose: (
|
|
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
|
-
|
|
726
|
+
onClose: (session) => {
|
|
669
727
|
this.emit("disconnect", {
|
|
670
728
|
session,
|
|
671
729
|
});
|
|
672
730
|
},
|
|
673
|
-
onConnect: async (
|
|
674
|
-
|
|
675
|
-
(session) => session.server === server,
|
|
676
|
-
);
|
|
677
|
-
|
|
678
|
-
if (!session) {
|
|
679
|
-
throw new UnexpectedStateError("Server not found");
|
|
680
|
-
}
|
|
731
|
+
onConnect: async (session) => {
|
|
732
|
+
this.#sessions.push(session);
|
|
681
733
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
session,
|
|
686
|
-
});
|
|
687
|
-
}, 100);
|
|
734
|
+
this.emit("connect", {
|
|
735
|
+
session,
|
|
736
|
+
});
|
|
688
737
|
},
|
|
689
738
|
});
|
|
690
739
|
|