fastmcp 1.2.0 → 1.4.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 +97 -4
- package/dist/FastMCP.d.ts +67 -1
- package/dist/FastMCP.js +87 -11
- package/dist/FastMCP.js.map +1 -1
- package/package.json +1 -1
- package/src/FastMCP.test.ts +161 -7
- package/src/FastMCP.ts +126 -13
package/README.md
CHANGED
|
@@ -9,7 +9,8 @@ A TypeScript framework for building [MCP](https://modelcontextprotocol.io/) serv
|
|
|
9
9
|
|
|
10
10
|
- Simple Tool, Resource, Prompt definition
|
|
11
11
|
- Full TypeScript support
|
|
12
|
-
- Built-in
|
|
12
|
+
- Built-in [logging](#logging)
|
|
13
|
+
- Built-in [error handling](#errors)
|
|
13
14
|
- Built-in CLI for [testing](#test-with-mcp-cli) and [debugging](#inspect-with-mcp-inspector)
|
|
14
15
|
- Built-in support for [SSE](#sse)
|
|
15
16
|
- Built-in [progress notifications](#progress)
|
|
@@ -39,7 +40,7 @@ server.addTool({
|
|
|
39
40
|
b: z.number(),
|
|
40
41
|
}),
|
|
41
42
|
execute: async (args) => {
|
|
42
|
-
return args.a + args.b;
|
|
43
|
+
return String(args.a + args.b);
|
|
43
44
|
},
|
|
44
45
|
});
|
|
45
46
|
|
|
@@ -104,12 +105,104 @@ server.addTool({
|
|
|
104
105
|
url: z.string(),
|
|
105
106
|
}),
|
|
106
107
|
execute: async (args) => {
|
|
107
|
-
|
|
108
|
-
return content;
|
|
108
|
+
return await fetchWebpageContent(args.url);
|
|
109
109
|
},
|
|
110
110
|
});
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
+
#### Returning a string
|
|
114
|
+
|
|
115
|
+
`execute` can return a string:
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
server.addTool({
|
|
119
|
+
name: "download",
|
|
120
|
+
description: "Download a file",
|
|
121
|
+
parameters: z.object({
|
|
122
|
+
url: z.string(),
|
|
123
|
+
}),
|
|
124
|
+
execute: async (args) => {
|
|
125
|
+
return "Hello, world!";
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The latter is equivalent to:
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
server.addTool({
|
|
134
|
+
name: "download",
|
|
135
|
+
description: "Download a file",
|
|
136
|
+
parameters: z.object({
|
|
137
|
+
url: z.string(),
|
|
138
|
+
}),
|
|
139
|
+
execute: async (args) => {
|
|
140
|
+
return {
|
|
141
|
+
content: [
|
|
142
|
+
{
|
|
143
|
+
type: "text",
|
|
144
|
+
text: "Hello, world!",
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
};
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
#### Returning a list
|
|
153
|
+
|
|
154
|
+
If you want to return a list of messages, you can return an object with a `content` property:
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
server.addTool({
|
|
158
|
+
name: "download",
|
|
159
|
+
description: "Download a file",
|
|
160
|
+
parameters: z.object({
|
|
161
|
+
url: z.string(),
|
|
162
|
+
}),
|
|
163
|
+
execute: async (args) => {
|
|
164
|
+
return {
|
|
165
|
+
content: [
|
|
166
|
+
{ type: "text", text: "First message" },
|
|
167
|
+
{ type: "text", text: "Second message" },
|
|
168
|
+
],
|
|
169
|
+
};
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
#### Logging
|
|
175
|
+
|
|
176
|
+
Tools can log messages to the client using the `log` object in the context object:
|
|
177
|
+
|
|
178
|
+
```js
|
|
179
|
+
server.addTool({
|
|
180
|
+
name: "download",
|
|
181
|
+
description: "Download a file",
|
|
182
|
+
parameters: z.object({
|
|
183
|
+
url: z.string(),
|
|
184
|
+
}),
|
|
185
|
+
execute: async (args, { log }) => {
|
|
186
|
+
log.info("Downloading file...", {
|
|
187
|
+
url,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// ...
|
|
191
|
+
|
|
192
|
+
log.info("Downloaded file");
|
|
193
|
+
|
|
194
|
+
return "done";
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The `log` object has the following methods:
|
|
200
|
+
|
|
201
|
+
- `debug(message: string, data?: SerializableValue)`
|
|
202
|
+
- `error(message: string, data?: SerializableValue)`
|
|
203
|
+
- `info(message: string, data?: SerializableValue)`
|
|
204
|
+
- `warn(message: string, data?: SerializableValue)`
|
|
205
|
+
|
|
113
206
|
#### Errors
|
|
114
207
|
|
|
115
208
|
The errors that are meant to be shown to the user should be thrown as `UserError` instances:
|
package/dist/FastMCP.d.ts
CHANGED
|
@@ -12,6 +12,10 @@ declare class UnexpectedStateError extends FastMCPError {
|
|
|
12
12
|
declare class UserError extends UnexpectedStateError {
|
|
13
13
|
}
|
|
14
14
|
type ToolParameters = z.ZodTypeAny;
|
|
15
|
+
type Literal = boolean | null | number | string | undefined;
|
|
16
|
+
type SerializableValue = Literal | SerializableValue[] | {
|
|
17
|
+
[key: string]: SerializableValue;
|
|
18
|
+
};
|
|
15
19
|
type Progress = {
|
|
16
20
|
/**
|
|
17
21
|
* The progress thus far. This should increase every time progress is made, even if the total is unknown.
|
|
@@ -24,12 +28,73 @@ type Progress = {
|
|
|
24
28
|
};
|
|
25
29
|
type Context = {
|
|
26
30
|
reportProgress: (progress: Progress) => Promise<void>;
|
|
31
|
+
log: {
|
|
32
|
+
debug: (message: string, data?: SerializableValue) => void;
|
|
33
|
+
error: (message: string, data?: SerializableValue) => void;
|
|
34
|
+
info: (message: string, data?: SerializableValue) => void;
|
|
35
|
+
warn: (message: string, data?: SerializableValue) => void;
|
|
36
|
+
};
|
|
27
37
|
};
|
|
38
|
+
declare const ContentResultZodSchema: z.ZodObject<{
|
|
39
|
+
content: z.ZodArray<z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
40
|
+
type: z.ZodLiteral<"text">;
|
|
41
|
+
/**
|
|
42
|
+
* The text content of the message.
|
|
43
|
+
*/
|
|
44
|
+
text: z.ZodString;
|
|
45
|
+
}, "strict", z.ZodTypeAny, {
|
|
46
|
+
type: "text";
|
|
47
|
+
text: string;
|
|
48
|
+
}, {
|
|
49
|
+
type: "text";
|
|
50
|
+
text: string;
|
|
51
|
+
}>, z.ZodObject<{
|
|
52
|
+
type: z.ZodLiteral<"image">;
|
|
53
|
+
/**
|
|
54
|
+
* The base64-encoded image data.
|
|
55
|
+
*/
|
|
56
|
+
data: z.ZodString;
|
|
57
|
+
/**
|
|
58
|
+
* The MIME type of the image. Different providers may support different image types.
|
|
59
|
+
*/
|
|
60
|
+
mimeType: z.ZodString;
|
|
61
|
+
}, "strict", z.ZodTypeAny, {
|
|
62
|
+
type: "image";
|
|
63
|
+
data: string;
|
|
64
|
+
mimeType: string;
|
|
65
|
+
}, {
|
|
66
|
+
type: "image";
|
|
67
|
+
data: string;
|
|
68
|
+
mimeType: string;
|
|
69
|
+
}>]>, "many">;
|
|
70
|
+
isError: z.ZodOptional<z.ZodBoolean>;
|
|
71
|
+
}, "strict", z.ZodTypeAny, {
|
|
72
|
+
content: ({
|
|
73
|
+
type: "text";
|
|
74
|
+
text: string;
|
|
75
|
+
} | {
|
|
76
|
+
type: "image";
|
|
77
|
+
data: string;
|
|
78
|
+
mimeType: string;
|
|
79
|
+
})[];
|
|
80
|
+
isError?: boolean | undefined;
|
|
81
|
+
}, {
|
|
82
|
+
content: ({
|
|
83
|
+
type: "text";
|
|
84
|
+
text: string;
|
|
85
|
+
} | {
|
|
86
|
+
type: "image";
|
|
87
|
+
data: string;
|
|
88
|
+
mimeType: string;
|
|
89
|
+
})[];
|
|
90
|
+
isError?: boolean | undefined;
|
|
91
|
+
}>;
|
|
92
|
+
type ContentResult = z.infer<typeof ContentResultZodSchema>;
|
|
28
93
|
type Tool<Params extends ToolParameters = ToolParameters> = {
|
|
29
94
|
name: string;
|
|
30
95
|
description?: string;
|
|
31
96
|
parameters?: Params;
|
|
32
|
-
execute: (args: z.infer<Params>, context: Context) => Promise<
|
|
97
|
+
execute: (args: z.infer<Params>, context: Context) => Promise<string | ContentResult>;
|
|
33
98
|
};
|
|
34
99
|
type Resource = {
|
|
35
100
|
uri: string;
|
|
@@ -68,6 +133,7 @@ declare class FastMCP {
|
|
|
68
133
|
constructor(options: ServerOptions);
|
|
69
134
|
private setupHandlers;
|
|
70
135
|
private setupErrorHandling;
|
|
136
|
+
get loggingLevel(): "debug" | "info" | "notice" | "warning" | "error" | "critical" | "alert" | "emergency";
|
|
71
137
|
private setupToolHandlers;
|
|
72
138
|
private setupResourceHandlers;
|
|
73
139
|
private setupPromptHandlers;
|
package/dist/FastMCP.js
CHANGED
|
@@ -10,9 +10,11 @@ import {
|
|
|
10
10
|
ListResourcesRequestSchema,
|
|
11
11
|
ListToolsRequestSchema,
|
|
12
12
|
McpError,
|
|
13
|
-
ReadResourceRequestSchema
|
|
13
|
+
ReadResourceRequestSchema,
|
|
14
|
+
SetLevelRequestSchema
|
|
14
15
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
15
16
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
17
|
+
import { z } from "zod";
|
|
16
18
|
import http from "http";
|
|
17
19
|
var FastMCPError = class extends Error {
|
|
18
20
|
constructor(message) {
|
|
@@ -30,6 +32,32 @@ var UnexpectedStateError = class extends FastMCPError {
|
|
|
30
32
|
};
|
|
31
33
|
var UserError = class extends UnexpectedStateError {
|
|
32
34
|
};
|
|
35
|
+
var TextContentZodSchema = z.object({
|
|
36
|
+
type: z.literal("text"),
|
|
37
|
+
/**
|
|
38
|
+
* The text content of the message.
|
|
39
|
+
*/
|
|
40
|
+
text: z.string()
|
|
41
|
+
}).strict();
|
|
42
|
+
var ImageContentZodSchema = z.object({
|
|
43
|
+
type: z.literal("image"),
|
|
44
|
+
/**
|
|
45
|
+
* The base64-encoded image data.
|
|
46
|
+
*/
|
|
47
|
+
data: z.string().base64(),
|
|
48
|
+
/**
|
|
49
|
+
* The MIME type of the image. Different providers may support different image types.
|
|
50
|
+
*/
|
|
51
|
+
mimeType: z.string()
|
|
52
|
+
}).strict();
|
|
53
|
+
var ContentZodSchema = z.discriminatedUnion("type", [
|
|
54
|
+
TextContentZodSchema,
|
|
55
|
+
ImageContentZodSchema
|
|
56
|
+
]);
|
|
57
|
+
var ContentResultZodSchema = z.object({
|
|
58
|
+
content: ContentZodSchema.array(),
|
|
59
|
+
isError: z.boolean().optional()
|
|
60
|
+
}).strict();
|
|
33
61
|
var FastMCP = class {
|
|
34
62
|
constructor(options) {
|
|
35
63
|
this.options = options;
|
|
@@ -43,6 +71,7 @@ var FastMCP = class {
|
|
|
43
71
|
#prompts;
|
|
44
72
|
#server = null;
|
|
45
73
|
#options;
|
|
74
|
+
#loggingLevel = "info";
|
|
46
75
|
setupHandlers(server) {
|
|
47
76
|
this.setupErrorHandling(server);
|
|
48
77
|
if (this.#tools.length) {
|
|
@@ -54,6 +83,10 @@ var FastMCP = class {
|
|
|
54
83
|
if (this.#prompts.length) {
|
|
55
84
|
this.setupPromptHandlers(server);
|
|
56
85
|
}
|
|
86
|
+
server.setRequestHandler(SetLevelRequestSchema, (request) => {
|
|
87
|
+
this.#loggingLevel = request.params.level;
|
|
88
|
+
return {};
|
|
89
|
+
});
|
|
57
90
|
}
|
|
58
91
|
setupErrorHandling(server) {
|
|
59
92
|
server.onerror = (error) => {
|
|
@@ -64,6 +97,9 @@ var FastMCP = class {
|
|
|
64
97
|
process.exit(0);
|
|
65
98
|
});
|
|
66
99
|
}
|
|
100
|
+
get loggingLevel() {
|
|
101
|
+
return this.#loggingLevel;
|
|
102
|
+
}
|
|
67
103
|
setupToolHandlers(server) {
|
|
68
104
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
69
105
|
return {
|
|
@@ -111,9 +147,55 @@ var FastMCP = class {
|
|
|
111
147
|
}
|
|
112
148
|
});
|
|
113
149
|
};
|
|
114
|
-
|
|
115
|
-
|
|
150
|
+
const log = {
|
|
151
|
+
debug: (message, context) => {
|
|
152
|
+
server.sendLoggingMessage({
|
|
153
|
+
level: "debug",
|
|
154
|
+
data: {
|
|
155
|
+
message,
|
|
156
|
+
context
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
error: (message, context) => {
|
|
161
|
+
server.sendLoggingMessage({
|
|
162
|
+
level: "error",
|
|
163
|
+
data: {
|
|
164
|
+
message,
|
|
165
|
+
context
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
},
|
|
169
|
+
info: (message, context) => {
|
|
170
|
+
server.sendLoggingMessage({
|
|
171
|
+
level: "info",
|
|
172
|
+
data: {
|
|
173
|
+
message,
|
|
174
|
+
context
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
},
|
|
178
|
+
warn: (message, context) => {
|
|
179
|
+
server.sendLoggingMessage({
|
|
180
|
+
level: "warning",
|
|
181
|
+
data: {
|
|
182
|
+
message,
|
|
183
|
+
context
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
const maybeStringResult = await tool.execute(args, {
|
|
189
|
+
reportProgress,
|
|
190
|
+
log
|
|
116
191
|
});
|
|
192
|
+
if (typeof maybeStringResult === "string") {
|
|
193
|
+
result = ContentResultZodSchema.parse({
|
|
194
|
+
content: [{ type: "text", text: maybeStringResult }]
|
|
195
|
+
});
|
|
196
|
+
} else {
|
|
197
|
+
result = ContentResultZodSchema.parse(maybeStringResult);
|
|
198
|
+
}
|
|
117
199
|
} catch (error) {
|
|
118
200
|
if (error instanceof UserError) {
|
|
119
201
|
return {
|
|
@@ -126,14 +208,7 @@ var FastMCP = class {
|
|
|
126
208
|
isError: true
|
|
127
209
|
};
|
|
128
210
|
}
|
|
129
|
-
|
|
130
|
-
return {
|
|
131
|
-
content: [{ type: "text", text: result }]
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
return {
|
|
135
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
136
|
-
};
|
|
211
|
+
return result;
|
|
137
212
|
}
|
|
138
213
|
);
|
|
139
214
|
}
|
|
@@ -258,6 +333,7 @@ var FastMCP = class {
|
|
|
258
333
|
if (this.#prompts.length) {
|
|
259
334
|
capabilities.prompts = {};
|
|
260
335
|
}
|
|
336
|
+
capabilities.logging = {};
|
|
261
337
|
this.#server = new Server(
|
|
262
338
|
{ name: this.#options.name, version: this.#options.version },
|
|
263
339
|
{ capabilities }
|
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 { SSEServerTransport } from \"@modelcontextprotocol/sdk/server/sse.js\";\nimport {\n CallToolRequestSchema,\n ErrorCode,\n GetPromptRequestSchema,\n ListPromptsRequestSchema,\n ListResourcesRequestSchema,\n ListToolsRequestSchema,\n McpError,\n ReadResourceRequestSchema,\n ServerCapabilities,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { zodToJsonSchema } from \"zod-to-json-schema\";\nimport type { z } from \"zod\";\nimport http from \"http\";\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\nexport class UserError extends UnexpectedStateError {}\n\ntype ToolParameters = z.ZodTypeAny;\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};\n\ntype Tool<Params extends ToolParameters = ToolParameters> = {\n name: string;\n description?: string;\n parameters?: Params;\n execute: (args: z.infer<Params>, context: Context) => Promise<unknown>;\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\nexport class FastMCP {\n #tools: Tool[];\n #resources: Resource[];\n #prompts: Prompt[];\n #server: Server | null = null;\n #options: ServerOptions;\n\n constructor(public options: ServerOptions) {\n this.#options = options;\n this.#tools = [];\n this.#resources = [];\n this.#prompts = [];\n }\n\n private setupHandlers(server: Server) {\n this.setupErrorHandling(server);\n\n if (this.#tools.length) {\n this.setupToolHandlers(server);\n }\n\n if (this.#resources.length) {\n this.setupResourceHandlers(server);\n }\n\n if (this.#prompts.length) {\n this.setupPromptHandlers(server);\n }\n }\n\n private setupErrorHandling(server: Server) {\n server.onerror = (error) => {\n console.error(\"[MCP Error]\", error);\n };\n process.on(\"SIGINT\", async () => {\n await server.close();\n process.exit(0);\n });\n }\n\n private setupToolHandlers(server: Server) {\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n return {\n tools: this.#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 server.setRequestHandler(\n CallToolRequestSchema,\n async (request, ...extra) => {\n const tool = this.#tools.find(\n (tool) => tool.name === request.params.name,\n );\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: any;\n\n try {\n const reportProgress = async (progress: Progress) => {\n await server.notification({\n method: \"notifications/progress\",\n params: {\n ...progress,\n progressToken,\n },\n });\n };\n\n result = await tool.execute(args, {\n reportProgress,\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 if (typeof result === \"string\") {\n return {\n content: [{ type: \"text\", text: result }],\n };\n }\n\n return {\n content: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n };\n },\n );\n }\n\n private setupResourceHandlers(server: Server) {\n server.setRequestHandler(ListResourcesRequestSchema, async () => {\n return {\n resources: this.#resources.map((resource) => {\n return {\n uri: resource.uri,\n name: resource.name,\n mimeType: resource.mimeType,\n };\n }),\n };\n });\n\n server.setRequestHandler(ReadResourceRequestSchema, async (request) => {\n const resource = this.#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 private setupPromptHandlers(server: Server) {\n server.setRequestHandler(ListPromptsRequestSchema, async () => {\n return {\n prompts: this.#prompts.map((prompt) => {\n return {\n name: prompt.name,\n description: prompt.description,\n arguments: prompt.arguments,\n };\n }),\n };\n });\n\n server.setRequestHandler(GetPromptRequestSchema, async (request) => {\n const prompt = this.#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 public addTool<Params extends ToolParameters>(tool: Tool<Params>) {\n this.#tools.push(tool as unknown as Tool);\n }\n\n public addResource(resource: Resource) {\n this.#resources.push(resource);\n }\n\n public addPrompt<const Args extends PromptArgument[]>(prompt: Prompt<Args>) {\n this.#prompts.push(prompt);\n }\n\n #httpServer: http.Server | null = null;\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 const capabilities: ServerCapabilities = {};\n\n if (this.#tools.length) {\n capabilities.tools = {};\n }\n\n if (this.#resources.length) {\n capabilities.resources = {};\n }\n\n if (this.#prompts.length) {\n capabilities.prompts = {};\n }\n\n this.#server = new Server(\n { name: this.#options.name, version: this.#options.version },\n { capabilities },\n );\n\n this.setupHandlers(this.#server);\n\n if (options.transportType === \"stdio\") {\n const transport = new StdioServerTransport();\n\n await this.#server.connect(transport);\n\n console.error(`server is running on stdio`);\n } else if (options.transportType === \"sse\") {\n let activeTransport: SSEServerTransport | null = null;\n\n /**\n * Adopted from https://dev.classmethod.jp/articles/mcp-sse/\n */\n this.#httpServer = http.createServer(async (req, res) => {\n if (req.method === \"GET\" && req.url === options.sse.endpoint) {\n const transport = new SSEServerTransport(\"/messages\", res);\n\n activeTransport = transport;\n\n if (!this.#server) {\n throw new Error(\"Server not initialized\");\n }\n\n await this.#server.connect(transport);\n\n res.on(\"close\", () => {\n console.log(\"SSE connection closed\");\n if (activeTransport === transport) {\n activeTransport = null;\n }\n });\n\n this.startSending(transport);\n return;\n }\n\n if (req.method === \"POST\" && req.url?.startsWith(\"/messages\")) {\n if (!activeTransport) {\n res.writeHead(400).end(\"No active transport\");\n return;\n }\n await activeTransport.handlePostMessage(req, res);\n return;\n }\n\n res.writeHead(404).end();\n });\n\n this.#httpServer.listen(options.sse.port, \"0.0.0.0\");\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 * @see https://dev.classmethod.jp/articles/mcp-sse/\n */\n private async startSending(transport: SSEServerTransport) {\n try {\n await transport.send({\n jsonrpc: \"2.0\",\n method: \"sse/connection\",\n params: { message: \"SSE Connection established\" },\n });\n\n let messageCount = 0;\n const interval = setInterval(async () => {\n messageCount++;\n\n const message = `Message ${messageCount} at ${new Date().toISOString()}`;\n\n try {\n await transport.send({\n jsonrpc: \"2.0\",\n method: \"sse/message\",\n params: { data: message },\n });\n\n console.log(`Sent: ${message}`);\n\n if (messageCount === 10) {\n clearInterval(interval);\n\n await transport.send({\n jsonrpc: \"2.0\",\n method: \"sse/complete\",\n params: { message: \"Stream completed\" },\n });\n console.log(\"Stream completed\");\n }\n } catch (error) {\n console.error(\"Error sending message:\", error);\n clearInterval(interval);\n }\n }, 1000);\n } catch (error) {\n console.error(\"Error in startSending:\", error);\n }\n }\n\n public async stop() {\n if (this.#httpServer) {\n this.#httpServer.close();\n }\n }\n}\n"],"mappings":"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","names":["tool","resource","prompt"]}
|
|
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 { SSEServerTransport } from \"@modelcontextprotocol/sdk/server/sse.js\";\nimport {\n CallToolRequestSchema,\n ErrorCode,\n GetPromptRequestSchema,\n ListPromptsRequestSchema,\n ListResourcesRequestSchema,\n ListToolsRequestSchema,\n LoggingLevel,\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 http from \"http\";\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\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\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();\n\ntype TextContent = z.infer<typeof TextContentZodSchema>;\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();\n\ntype ImageContent = z.infer<typeof ImageContentZodSchema>;\n\nconst ContentZodSchema = z.discriminatedUnion(\"type\", [\n TextContentZodSchema,\n ImageContentZodSchema,\n]);\n\nconst ContentResultZodSchema = z\n .object({\n content: ContentZodSchema.array(),\n isError: z.boolean().optional(),\n })\n .strict();\n\ntype ContentResult = z.infer<typeof ContentResultZodSchema>;\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>;\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\nexport class FastMCP {\n #tools: Tool[];\n #resources: Resource[];\n #prompts: Prompt[];\n #server: Server | null = null;\n #options: ServerOptions;\n #loggingLevel: LoggingLevel = \"info\";\n\n constructor(public options: ServerOptions) {\n this.#options = options;\n this.#tools = [];\n this.#resources = [];\n this.#prompts = [];\n }\n\n private setupHandlers(server: Server) {\n this.setupErrorHandling(server);\n\n if (this.#tools.length) {\n this.setupToolHandlers(server);\n }\n\n if (this.#resources.length) {\n this.setupResourceHandlers(server);\n }\n\n if (this.#prompts.length) {\n this.setupPromptHandlers(server);\n }\n\n server.setRequestHandler(SetLevelRequestSchema, (request) => {\n this.#loggingLevel = request.params.level;\n\n return {};\n });\n }\n\n private setupErrorHandling(server: Server) {\n server.onerror = (error) => {\n console.error(\"[MCP Error]\", error);\n };\n process.on(\"SIGINT\", async () => {\n await server.close();\n process.exit(0);\n });\n }\n\n public get loggingLevel() {\n return this.#loggingLevel;\n }\n\n private setupToolHandlers(server: Server) {\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n return {\n tools: this.#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 server.setRequestHandler(\n CallToolRequestSchema,\n async (request, ...extra) => {\n const tool = this.#tools.find(\n (tool) => tool.name === request.params.name,\n );\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 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 server.sendLoggingMessage({\n level: \"debug\",\n data: {\n message,\n context,\n },\n });\n },\n error: (message: string, context?: SerializableValue) => {\n server.sendLoggingMessage({\n level: \"error\",\n data: {\n message,\n context,\n },\n });\n },\n info: (message: string, context?: SerializableValue) => {\n server.sendLoggingMessage({\n level: \"info\",\n data: {\n message,\n context,\n },\n });\n },\n warn: (message: string, context?: SerializableValue) => {\n 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 {\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\n private setupResourceHandlers(server: Server) {\n server.setRequestHandler(ListResourcesRequestSchema, async () => {\n return {\n resources: this.#resources.map((resource) => {\n return {\n uri: resource.uri,\n name: resource.name,\n mimeType: resource.mimeType,\n };\n }),\n };\n });\n\n server.setRequestHandler(ReadResourceRequestSchema, async (request) => {\n const resource = this.#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 private setupPromptHandlers(server: Server) {\n server.setRequestHandler(ListPromptsRequestSchema, async () => {\n return {\n prompts: this.#prompts.map((prompt) => {\n return {\n name: prompt.name,\n description: prompt.description,\n arguments: prompt.arguments,\n };\n }),\n };\n });\n\n server.setRequestHandler(GetPromptRequestSchema, async (request) => {\n const prompt = this.#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 public addTool<Params extends ToolParameters>(tool: Tool<Params>) {\n this.#tools.push(tool as unknown as Tool);\n }\n\n public addResource(resource: Resource) {\n this.#resources.push(resource);\n }\n\n public addPrompt<const Args extends PromptArgument[]>(prompt: Prompt<Args>) {\n this.#prompts.push(prompt);\n }\n\n #httpServer: http.Server | null = null;\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 const capabilities: ServerCapabilities = {};\n\n if (this.#tools.length) {\n capabilities.tools = {};\n }\n\n if (this.#resources.length) {\n capabilities.resources = {};\n }\n\n if (this.#prompts.length) {\n capabilities.prompts = {};\n }\n\n capabilities.logging = {};\n\n this.#server = new Server(\n { name: this.#options.name, version: this.#options.version },\n { capabilities },\n );\n\n this.setupHandlers(this.#server);\n\n if (options.transportType === \"stdio\") {\n const transport = new StdioServerTransport();\n\n await this.#server.connect(transport);\n\n console.error(`server is running on stdio`);\n } else if (options.transportType === \"sse\") {\n let activeTransport: SSEServerTransport | null = null;\n\n /**\n * Adopted from https://dev.classmethod.jp/articles/mcp-sse/\n */\n this.#httpServer = http.createServer(async (req, res) => {\n if (req.method === \"GET\" && req.url === options.sse.endpoint) {\n const transport = new SSEServerTransport(\"/messages\", res);\n\n activeTransport = transport;\n\n if (!this.#server) {\n throw new Error(\"Server not initialized\");\n }\n\n await this.#server.connect(transport);\n\n res.on(\"close\", () => {\n console.log(\"SSE connection closed\");\n if (activeTransport === transport) {\n activeTransport = null;\n }\n });\n\n this.startSending(transport);\n return;\n }\n\n if (req.method === \"POST\" && req.url?.startsWith(\"/messages\")) {\n if (!activeTransport) {\n res.writeHead(400).end(\"No active transport\");\n return;\n }\n await activeTransport.handlePostMessage(req, res);\n return;\n }\n\n res.writeHead(404).end();\n });\n\n this.#httpServer.listen(options.sse.port, \"0.0.0.0\");\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 * @see https://dev.classmethod.jp/articles/mcp-sse/\n */\n private async startSending(transport: SSEServerTransport) {\n try {\n await transport.send({\n jsonrpc: \"2.0\",\n method: \"sse/connection\",\n params: { message: \"SSE Connection established\" },\n });\n\n let messageCount = 0;\n const interval = setInterval(async () => {\n messageCount++;\n\n const message = `Message ${messageCount} at ${new Date().toISOString()}`;\n\n try {\n await transport.send({\n jsonrpc: \"2.0\",\n method: \"sse/message\",\n params: { data: message },\n });\n\n console.log(`Sent: ${message}`);\n\n if (messageCount === 10) {\n clearInterval(interval);\n\n await transport.send({\n jsonrpc: \"2.0\",\n method: \"sse/complete\",\n params: { message: \"Stream completed\" },\n });\n console.log(\"Stream completed\");\n }\n } catch (error) {\n console.error(\"Error sending message:\", error);\n clearInterval(interval);\n }\n }, 1000);\n } catch (error) {\n console.error(\"Error in startSending:\", error);\n }\n }\n\n public async stop() {\n if (this.#httpServer) {\n this.#httpServer.close();\n }\n }\n}\n"],"mappings":"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","names":["tool","resource","prompt"]}
|
package/package.json
CHANGED
package/src/FastMCP.test.ts
CHANGED
|
@@ -6,7 +6,11 @@ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
|
6
6
|
import { getRandomPort } from "get-port-please";
|
|
7
7
|
import { EventSource } from "eventsource";
|
|
8
8
|
import { setTimeout as delay } from "timers/promises";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
ErrorCode,
|
|
11
|
+
LoggingMessageNotificationSchema,
|
|
12
|
+
McpError,
|
|
13
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
10
14
|
|
|
11
15
|
// @ts-expect-error - figure out how to use --experimental-eventsource with vitest
|
|
12
16
|
global.EventSource = EventSource;
|
|
@@ -16,7 +20,13 @@ const runWithTestServer = async ({
|
|
|
16
20
|
start,
|
|
17
21
|
}: {
|
|
18
22
|
start: () => Promise<FastMCP>;
|
|
19
|
-
run: ({
|
|
23
|
+
run: ({
|
|
24
|
+
client,
|
|
25
|
+
server,
|
|
26
|
+
}: {
|
|
27
|
+
client: Client;
|
|
28
|
+
server: FastMCP;
|
|
29
|
+
}) => Promise<void>;
|
|
20
30
|
}) => {
|
|
21
31
|
const port = await getRandomPort();
|
|
22
32
|
|
|
@@ -47,7 +57,7 @@ const runWithTestServer = async ({
|
|
|
47
57
|
|
|
48
58
|
await client.connect(transport);
|
|
49
59
|
|
|
50
|
-
await run({ client });
|
|
60
|
+
await run({ client, server });
|
|
51
61
|
} finally {
|
|
52
62
|
await server.stop();
|
|
53
63
|
}
|
|
@@ -71,7 +81,7 @@ test("adds tools", async () => {
|
|
|
71
81
|
b: z.number(),
|
|
72
82
|
}),
|
|
73
83
|
execute: async (args) => {
|
|
74
|
-
return args.a + args.b;
|
|
84
|
+
return String(args.a + args.b);
|
|
75
85
|
},
|
|
76
86
|
});
|
|
77
87
|
|
|
@@ -116,7 +126,7 @@ test("calls a tool", async () => {
|
|
|
116
126
|
b: z.number(),
|
|
117
127
|
}),
|
|
118
128
|
execute: async (args) => {
|
|
119
|
-
return args.a + args.b;
|
|
129
|
+
return String(args.a + args.b);
|
|
120
130
|
},
|
|
121
131
|
});
|
|
122
132
|
|
|
@@ -138,7 +148,7 @@ test("calls a tool", async () => {
|
|
|
138
148
|
});
|
|
139
149
|
});
|
|
140
150
|
|
|
141
|
-
test("
|
|
151
|
+
test("returns a list", async () => {
|
|
142
152
|
await runWithTestServer({
|
|
143
153
|
start: async () => {
|
|
144
154
|
const server = new FastMCP({
|
|
@@ -154,6 +164,52 @@ test("handles UserError errors", async () => {
|
|
|
154
164
|
b: z.number(),
|
|
155
165
|
}),
|
|
156
166
|
execute: async (args) => {
|
|
167
|
+
return {
|
|
168
|
+
content: [
|
|
169
|
+
{ type: "text", text: "a" },
|
|
170
|
+
{ type: "text", text: "b" },
|
|
171
|
+
],
|
|
172
|
+
};
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
return server;
|
|
177
|
+
},
|
|
178
|
+
run: async ({ client }) => {
|
|
179
|
+
expect(
|
|
180
|
+
await client.callTool({
|
|
181
|
+
name: "add",
|
|
182
|
+
arguments: {
|
|
183
|
+
a: 1,
|
|
184
|
+
b: 2,
|
|
185
|
+
},
|
|
186
|
+
}),
|
|
187
|
+
).toEqual({
|
|
188
|
+
content: [
|
|
189
|
+
{ type: "text", text: "a" },
|
|
190
|
+
{ type: "text", text: "b" },
|
|
191
|
+
],
|
|
192
|
+
});
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("handles UserError errors", async () => {
|
|
198
|
+
await runWithTestServer({
|
|
199
|
+
start: async () => {
|
|
200
|
+
const server = new FastMCP({
|
|
201
|
+
name: "Test",
|
|
202
|
+
version: "1.0.0",
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
server.addTool({
|
|
206
|
+
name: "add",
|
|
207
|
+
description: "Add two numbers",
|
|
208
|
+
parameters: z.object({
|
|
209
|
+
a: z.number(),
|
|
210
|
+
b: z.number(),
|
|
211
|
+
}),
|
|
212
|
+
execute: async () => {
|
|
157
213
|
throw new UserError("Something went wrong");
|
|
158
214
|
},
|
|
159
215
|
});
|
|
@@ -229,7 +285,7 @@ test("tracks tool progress", async () => {
|
|
|
229
285
|
|
|
230
286
|
await delay(100);
|
|
231
287
|
|
|
232
|
-
return args.a + args.b;
|
|
288
|
+
return String(args.a + args.b);
|
|
233
289
|
},
|
|
234
290
|
});
|
|
235
291
|
|
|
@@ -261,6 +317,104 @@ test("tracks tool progress", async () => {
|
|
|
261
317
|
});
|
|
262
318
|
});
|
|
263
319
|
|
|
320
|
+
test("sets logging levels", async () => {
|
|
321
|
+
await runWithTestServer({
|
|
322
|
+
start: async () => {
|
|
323
|
+
const server = new FastMCP({
|
|
324
|
+
name: "Test",
|
|
325
|
+
version: "1.0.0",
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
return server;
|
|
329
|
+
},
|
|
330
|
+
run: async ({ client, server }) => {
|
|
331
|
+
await client.setLoggingLevel("debug");
|
|
332
|
+
|
|
333
|
+
expect(server.loggingLevel).toBe("debug");
|
|
334
|
+
|
|
335
|
+
await client.setLoggingLevel("info");
|
|
336
|
+
|
|
337
|
+
expect(server.loggingLevel).toBe("info");
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test("sends logging messages to the client", async () => {
|
|
343
|
+
await runWithTestServer({
|
|
344
|
+
start: async () => {
|
|
345
|
+
const server = new FastMCP({
|
|
346
|
+
name: "Test",
|
|
347
|
+
version: "1.0.0",
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
server.addTool({
|
|
351
|
+
name: "add",
|
|
352
|
+
description: "Add two numbers",
|
|
353
|
+
parameters: z.object({
|
|
354
|
+
a: z.number(),
|
|
355
|
+
b: z.number(),
|
|
356
|
+
}),
|
|
357
|
+
execute: async (args, { log }) => {
|
|
358
|
+
log.debug("debug message", {
|
|
359
|
+
foo: "bar",
|
|
360
|
+
});
|
|
361
|
+
log.error("error message");
|
|
362
|
+
log.info("info message");
|
|
363
|
+
log.warn("warn message");
|
|
364
|
+
|
|
365
|
+
return String(args.a + args.b);
|
|
366
|
+
},
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
return server;
|
|
370
|
+
},
|
|
371
|
+
run: async ({ client, server }) => {
|
|
372
|
+
const onLog = vi.fn();
|
|
373
|
+
|
|
374
|
+
client.setNotificationHandler(
|
|
375
|
+
LoggingMessageNotificationSchema,
|
|
376
|
+
(message) => {
|
|
377
|
+
if (message.method === "notifications/message") {
|
|
378
|
+
onLog({
|
|
379
|
+
level: message.params.level,
|
|
380
|
+
...(message.params.data ?? {}),
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
await client.callTool({
|
|
387
|
+
name: "add",
|
|
388
|
+
arguments: {
|
|
389
|
+
a: 1,
|
|
390
|
+
b: 2,
|
|
391
|
+
},
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
expect(onLog).toHaveBeenCalledTimes(4);
|
|
395
|
+
expect(onLog).toHaveBeenNthCalledWith(1, {
|
|
396
|
+
level: "debug",
|
|
397
|
+
message: "debug message",
|
|
398
|
+
context: {
|
|
399
|
+
foo: "bar",
|
|
400
|
+
},
|
|
401
|
+
});
|
|
402
|
+
expect(onLog).toHaveBeenNthCalledWith(2, {
|
|
403
|
+
level: "error",
|
|
404
|
+
message: "error message",
|
|
405
|
+
});
|
|
406
|
+
expect(onLog).toHaveBeenNthCalledWith(3, {
|
|
407
|
+
level: "info",
|
|
408
|
+
message: "info message",
|
|
409
|
+
});
|
|
410
|
+
expect(onLog).toHaveBeenNthCalledWith(4, {
|
|
411
|
+
level: "warning",
|
|
412
|
+
message: "warn message",
|
|
413
|
+
});
|
|
414
|
+
},
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
|
|
264
418
|
test("adds resources", async () => {
|
|
265
419
|
await runWithTestServer({
|
|
266
420
|
start: async () => {
|
package/src/FastMCP.ts
CHANGED
|
@@ -8,12 +8,14 @@ import {
|
|
|
8
8
|
ListPromptsRequestSchema,
|
|
9
9
|
ListResourcesRequestSchema,
|
|
10
10
|
ListToolsRequestSchema,
|
|
11
|
+
LoggingLevel,
|
|
11
12
|
McpError,
|
|
12
13
|
ReadResourceRequestSchema,
|
|
13
14
|
ServerCapabilities,
|
|
15
|
+
SetLevelRequestSchema,
|
|
14
16
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
15
17
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
16
|
-
import
|
|
18
|
+
import { z } from "zod";
|
|
17
19
|
import http from "http";
|
|
18
20
|
|
|
19
21
|
abstract class FastMCPError extends Error {
|
|
@@ -41,6 +43,13 @@ export class UserError extends UnexpectedStateError {}
|
|
|
41
43
|
|
|
42
44
|
type ToolParameters = z.ZodTypeAny;
|
|
43
45
|
|
|
46
|
+
type Literal = boolean | null | number | string | undefined;
|
|
47
|
+
|
|
48
|
+
type SerializableValue =
|
|
49
|
+
| Literal
|
|
50
|
+
| SerializableValue[]
|
|
51
|
+
| { [key: string]: SerializableValue };
|
|
52
|
+
|
|
44
53
|
type Progress = {
|
|
45
54
|
/**
|
|
46
55
|
* The progress thus far. This should increase every time progress is made, even if the total is unknown.
|
|
@@ -54,13 +63,64 @@ type Progress = {
|
|
|
54
63
|
|
|
55
64
|
type Context = {
|
|
56
65
|
reportProgress: (progress: Progress) => Promise<void>;
|
|
66
|
+
log: {
|
|
67
|
+
debug: (message: string, data?: SerializableValue) => void;
|
|
68
|
+
error: (message: string, data?: SerializableValue) => void;
|
|
69
|
+
info: (message: string, data?: SerializableValue) => void;
|
|
70
|
+
warn: (message: string, data?: SerializableValue) => void;
|
|
71
|
+
};
|
|
57
72
|
};
|
|
58
73
|
|
|
74
|
+
const TextContentZodSchema = z
|
|
75
|
+
.object({
|
|
76
|
+
type: z.literal("text"),
|
|
77
|
+
/**
|
|
78
|
+
* The text content of the message.
|
|
79
|
+
*/
|
|
80
|
+
text: z.string(),
|
|
81
|
+
})
|
|
82
|
+
.strict();
|
|
83
|
+
|
|
84
|
+
type TextContent = z.infer<typeof TextContentZodSchema>;
|
|
85
|
+
|
|
86
|
+
const ImageContentZodSchema = z
|
|
87
|
+
.object({
|
|
88
|
+
type: z.literal("image"),
|
|
89
|
+
/**
|
|
90
|
+
* The base64-encoded image data.
|
|
91
|
+
*/
|
|
92
|
+
data: z.string().base64(),
|
|
93
|
+
/**
|
|
94
|
+
* The MIME type of the image. Different providers may support different image types.
|
|
95
|
+
*/
|
|
96
|
+
mimeType: z.string(),
|
|
97
|
+
})
|
|
98
|
+
.strict();
|
|
99
|
+
|
|
100
|
+
type ImageContent = z.infer<typeof ImageContentZodSchema>;
|
|
101
|
+
|
|
102
|
+
const ContentZodSchema = z.discriminatedUnion("type", [
|
|
103
|
+
TextContentZodSchema,
|
|
104
|
+
ImageContentZodSchema,
|
|
105
|
+
]);
|
|
106
|
+
|
|
107
|
+
const ContentResultZodSchema = z
|
|
108
|
+
.object({
|
|
109
|
+
content: ContentZodSchema.array(),
|
|
110
|
+
isError: z.boolean().optional(),
|
|
111
|
+
})
|
|
112
|
+
.strict();
|
|
113
|
+
|
|
114
|
+
type ContentResult = z.infer<typeof ContentResultZodSchema>;
|
|
115
|
+
|
|
59
116
|
type Tool<Params extends ToolParameters = ToolParameters> = {
|
|
60
117
|
name: string;
|
|
61
118
|
description?: string;
|
|
62
119
|
parameters?: Params;
|
|
63
|
-
execute: (
|
|
120
|
+
execute: (
|
|
121
|
+
args: z.infer<Params>,
|
|
122
|
+
context: Context,
|
|
123
|
+
) => Promise<string | ContentResult>;
|
|
64
124
|
};
|
|
65
125
|
|
|
66
126
|
type Resource = {
|
|
@@ -107,6 +167,7 @@ export class FastMCP {
|
|
|
107
167
|
#prompts: Prompt[];
|
|
108
168
|
#server: Server | null = null;
|
|
109
169
|
#options: ServerOptions;
|
|
170
|
+
#loggingLevel: LoggingLevel = "info";
|
|
110
171
|
|
|
111
172
|
constructor(public options: ServerOptions) {
|
|
112
173
|
this.#options = options;
|
|
@@ -129,6 +190,12 @@ export class FastMCP {
|
|
|
129
190
|
if (this.#prompts.length) {
|
|
130
191
|
this.setupPromptHandlers(server);
|
|
131
192
|
}
|
|
193
|
+
|
|
194
|
+
server.setRequestHandler(SetLevelRequestSchema, (request) => {
|
|
195
|
+
this.#loggingLevel = request.params.level;
|
|
196
|
+
|
|
197
|
+
return {};
|
|
198
|
+
});
|
|
132
199
|
}
|
|
133
200
|
|
|
134
201
|
private setupErrorHandling(server: Server) {
|
|
@@ -141,6 +208,10 @@ export class FastMCP {
|
|
|
141
208
|
});
|
|
142
209
|
}
|
|
143
210
|
|
|
211
|
+
public get loggingLevel() {
|
|
212
|
+
return this.#loggingLevel;
|
|
213
|
+
}
|
|
214
|
+
|
|
144
215
|
private setupToolHandlers(server: Server) {
|
|
145
216
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
146
217
|
return {
|
|
@@ -187,7 +258,7 @@ export class FastMCP {
|
|
|
187
258
|
|
|
188
259
|
const progressToken = request.params?._meta?.progressToken;
|
|
189
260
|
|
|
190
|
-
let result:
|
|
261
|
+
let result: ContentResult;
|
|
191
262
|
|
|
192
263
|
try {
|
|
193
264
|
const reportProgress = async (progress: Progress) => {
|
|
@@ -200,9 +271,57 @@ export class FastMCP {
|
|
|
200
271
|
});
|
|
201
272
|
};
|
|
202
273
|
|
|
203
|
-
|
|
274
|
+
const log = {
|
|
275
|
+
debug: (message: string, context?: SerializableValue) => {
|
|
276
|
+
server.sendLoggingMessage({
|
|
277
|
+
level: "debug",
|
|
278
|
+
data: {
|
|
279
|
+
message,
|
|
280
|
+
context,
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
},
|
|
284
|
+
error: (message: string, context?: SerializableValue) => {
|
|
285
|
+
server.sendLoggingMessage({
|
|
286
|
+
level: "error",
|
|
287
|
+
data: {
|
|
288
|
+
message,
|
|
289
|
+
context,
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
},
|
|
293
|
+
info: (message: string, context?: SerializableValue) => {
|
|
294
|
+
server.sendLoggingMessage({
|
|
295
|
+
level: "info",
|
|
296
|
+
data: {
|
|
297
|
+
message,
|
|
298
|
+
context,
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
},
|
|
302
|
+
warn: (message: string, context?: SerializableValue) => {
|
|
303
|
+
server.sendLoggingMessage({
|
|
304
|
+
level: "warning",
|
|
305
|
+
data: {
|
|
306
|
+
message,
|
|
307
|
+
context,
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
},
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const maybeStringResult = await tool.execute(args, {
|
|
204
314
|
reportProgress,
|
|
315
|
+
log,
|
|
205
316
|
});
|
|
317
|
+
|
|
318
|
+
if (typeof maybeStringResult === "string") {
|
|
319
|
+
result = ContentResultZodSchema.parse({
|
|
320
|
+
content: [{ type: "text", text: maybeStringResult }],
|
|
321
|
+
});
|
|
322
|
+
} else {
|
|
323
|
+
result = ContentResultZodSchema.parse(maybeStringResult);
|
|
324
|
+
}
|
|
206
325
|
} catch (error) {
|
|
207
326
|
if (error instanceof UserError) {
|
|
208
327
|
return {
|
|
@@ -217,15 +336,7 @@ export class FastMCP {
|
|
|
217
336
|
};
|
|
218
337
|
}
|
|
219
338
|
|
|
220
|
-
|
|
221
|
-
return {
|
|
222
|
-
content: [{ type: "text", text: result }],
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
return {
|
|
227
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
228
|
-
};
|
|
339
|
+
return result;
|
|
229
340
|
},
|
|
230
341
|
);
|
|
231
342
|
}
|
|
@@ -380,6 +491,8 @@ export class FastMCP {
|
|
|
380
491
|
capabilities.prompts = {};
|
|
381
492
|
}
|
|
382
493
|
|
|
494
|
+
capabilities.logging = {};
|
|
495
|
+
|
|
383
496
|
this.#server = new Server(
|
|
384
497
|
{ name: this.#options.name, version: this.#options.version },
|
|
385
498
|
{ capabilities },
|