fastmcp 1.1.0 → 1.3.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 +61 -5
- package/dist/FastMCP.d.ts +23 -1
- package/dist/FastMCP.js +75 -3
- package/dist/FastMCP.js.map +1 -1
- package/package.json +1 -1
- package/src/FastMCP.test.ts +204 -3
- package/src/FastMCP.ts +101 -0
package/README.md
CHANGED
|
@@ -9,10 +9,11 @@ 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
|
|
13
|
-
- Built-in
|
|
14
|
-
- Built-in
|
|
15
|
-
- Built-in
|
|
12
|
+
- Built-in [logging](#logging)
|
|
13
|
+
- Built-in [error handling](#errors)
|
|
14
|
+
- Built-in CLI for [testing](#test-with-mcp-cli) and [debugging](#inspect-with-mcp-inspector)
|
|
15
|
+
- Built-in support for [SSE](#sse)
|
|
16
|
+
- Built-in [progress notifications](#progress)
|
|
16
17
|
|
|
17
18
|
## Installation
|
|
18
19
|
|
|
@@ -110,6 +111,61 @@ server.addTool({
|
|
|
110
111
|
});
|
|
111
112
|
```
|
|
112
113
|
|
|
114
|
+
#### Logging
|
|
115
|
+
|
|
116
|
+
Tools can log messages to the client using the `log` object in the context object:
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
server.addTool({
|
|
120
|
+
name: "download",
|
|
121
|
+
description: "Download a file",
|
|
122
|
+
parameters: z.object({
|
|
123
|
+
url: z.string(),
|
|
124
|
+
}),
|
|
125
|
+
execute: async (args, { log }) => {
|
|
126
|
+
log.info("Downloading file...", {
|
|
127
|
+
url,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// ...
|
|
131
|
+
|
|
132
|
+
log.info("Downloaded file");
|
|
133
|
+
|
|
134
|
+
return "done";
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The `log` object has the following methods:
|
|
140
|
+
|
|
141
|
+
- `debug(message: string, data?: SerializableValue)`
|
|
142
|
+
- `error(message: string, data?: SerializableValue)`
|
|
143
|
+
- `info(message: string, data?: SerializableValue)`
|
|
144
|
+
- `warn(message: string, data?: SerializableValue)`
|
|
145
|
+
|
|
146
|
+
#### Errors
|
|
147
|
+
|
|
148
|
+
The errors that are meant to be shown to the user should be thrown as `UserError` instances:
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
import { UserError } from "fastmcp";
|
|
152
|
+
|
|
153
|
+
server.addTool({
|
|
154
|
+
name: "download",
|
|
155
|
+
description: "Download a file",
|
|
156
|
+
parameters: z.object({
|
|
157
|
+
url: z.string(),
|
|
158
|
+
}),
|
|
159
|
+
execute: async (args) => {
|
|
160
|
+
if (args.url.startsWith("https://example.com")) {
|
|
161
|
+
throw new UserError("This URL is not allowed");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return "done";
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
113
169
|
#### Progress
|
|
114
170
|
|
|
115
171
|
Tools can report progress by calling `reportProgress` in the context object:
|
|
@@ -134,7 +190,7 @@ server.addTool({
|
|
|
134
190
|
total: 100,
|
|
135
191
|
});
|
|
136
192
|
|
|
137
|
-
return
|
|
193
|
+
return "done";
|
|
138
194
|
},
|
|
139
195
|
});
|
|
140
196
|
```
|
package/dist/FastMCP.d.ts
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
+
declare abstract class FastMCPError extends Error {
|
|
4
|
+
constructor(message?: string);
|
|
5
|
+
}
|
|
6
|
+
type Extra = unknown;
|
|
7
|
+
type Extras = Record<string, Extra>;
|
|
8
|
+
declare class UnexpectedStateError extends FastMCPError {
|
|
9
|
+
extras?: Extras;
|
|
10
|
+
constructor(message: string, extras?: Extras);
|
|
11
|
+
}
|
|
12
|
+
declare class UserError extends UnexpectedStateError {
|
|
13
|
+
}
|
|
3
14
|
type ToolParameters = z.ZodTypeAny;
|
|
15
|
+
type Literal = boolean | null | number | string | undefined;
|
|
16
|
+
type SerializableValue = Literal | SerializableValue[] | {
|
|
17
|
+
[key: string]: SerializableValue;
|
|
18
|
+
};
|
|
4
19
|
type Progress = {
|
|
5
20
|
/**
|
|
6
21
|
* The progress thus far. This should increase every time progress is made, even if the total is unknown.
|
|
@@ -13,6 +28,12 @@ type Progress = {
|
|
|
13
28
|
};
|
|
14
29
|
type Context = {
|
|
15
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
|
+
};
|
|
16
37
|
};
|
|
17
38
|
type Tool<Params extends ToolParameters = ToolParameters> = {
|
|
18
39
|
name: string;
|
|
@@ -57,6 +78,7 @@ declare class FastMCP {
|
|
|
57
78
|
constructor(options: ServerOptions);
|
|
58
79
|
private setupHandlers;
|
|
59
80
|
private setupErrorHandling;
|
|
81
|
+
get loggingLevel(): "debug" | "info" | "notice" | "warning" | "error" | "critical" | "alert" | "emergency";
|
|
60
82
|
private setupToolHandlers;
|
|
61
83
|
private setupResourceHandlers;
|
|
62
84
|
private setupPromptHandlers;
|
|
@@ -79,4 +101,4 @@ declare class FastMCP {
|
|
|
79
101
|
stop(): Promise<void>;
|
|
80
102
|
}
|
|
81
103
|
|
|
82
|
-
export { FastMCP };
|
|
104
|
+
export { FastMCP, UserError };
|
package/dist/FastMCP.js
CHANGED
|
@@ -10,10 +10,27 @@ 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";
|
|
16
17
|
import http from "http";
|
|
18
|
+
var FastMCPError = class extends Error {
|
|
19
|
+
constructor(message) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = new.target.name;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var UnexpectedStateError = class extends FastMCPError {
|
|
25
|
+
extras;
|
|
26
|
+
constructor(message, extras) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = new.target.name;
|
|
29
|
+
this.extras = extras;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var UserError = class extends UnexpectedStateError {
|
|
33
|
+
};
|
|
17
34
|
var FastMCP = class {
|
|
18
35
|
constructor(options) {
|
|
19
36
|
this.options = options;
|
|
@@ -27,6 +44,7 @@ var FastMCP = class {
|
|
|
27
44
|
#prompts;
|
|
28
45
|
#server = null;
|
|
29
46
|
#options;
|
|
47
|
+
#loggingLevel = "info";
|
|
30
48
|
setupHandlers(server) {
|
|
31
49
|
this.setupErrorHandling(server);
|
|
32
50
|
if (this.#tools.length) {
|
|
@@ -38,6 +56,10 @@ var FastMCP = class {
|
|
|
38
56
|
if (this.#prompts.length) {
|
|
39
57
|
this.setupPromptHandlers(server);
|
|
40
58
|
}
|
|
59
|
+
server.setRequestHandler(SetLevelRequestSchema, (request) => {
|
|
60
|
+
this.#loggingLevel = request.params.level;
|
|
61
|
+
return {};
|
|
62
|
+
});
|
|
41
63
|
}
|
|
42
64
|
setupErrorHandling(server) {
|
|
43
65
|
server.onerror = (error) => {
|
|
@@ -48,6 +70,9 @@ var FastMCP = class {
|
|
|
48
70
|
process.exit(0);
|
|
49
71
|
});
|
|
50
72
|
}
|
|
73
|
+
get loggingLevel() {
|
|
74
|
+
return this.#loggingLevel;
|
|
75
|
+
}
|
|
51
76
|
setupToolHandlers(server) {
|
|
52
77
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
53
78
|
return {
|
|
@@ -95,10 +120,55 @@ var FastMCP = class {
|
|
|
95
120
|
}
|
|
96
121
|
});
|
|
97
122
|
};
|
|
123
|
+
const log = {
|
|
124
|
+
debug: (message, context) => {
|
|
125
|
+
server.sendLoggingMessage({
|
|
126
|
+
level: "debug",
|
|
127
|
+
data: {
|
|
128
|
+
message,
|
|
129
|
+
context
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
error: (message, context) => {
|
|
134
|
+
server.sendLoggingMessage({
|
|
135
|
+
level: "error",
|
|
136
|
+
data: {
|
|
137
|
+
message,
|
|
138
|
+
context
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
info: (message, context) => {
|
|
143
|
+
server.sendLoggingMessage({
|
|
144
|
+
level: "info",
|
|
145
|
+
data: {
|
|
146
|
+
message,
|
|
147
|
+
context
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
},
|
|
151
|
+
warn: (message, context) => {
|
|
152
|
+
server.sendLoggingMessage({
|
|
153
|
+
level: "warning",
|
|
154
|
+
data: {
|
|
155
|
+
message,
|
|
156
|
+
context
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
};
|
|
98
161
|
result = await tool.execute(args, {
|
|
99
|
-
reportProgress
|
|
162
|
+
reportProgress,
|
|
163
|
+
log
|
|
100
164
|
});
|
|
101
165
|
} catch (error) {
|
|
166
|
+
if (error instanceof UserError) {
|
|
167
|
+
return {
|
|
168
|
+
content: [{ type: "text", text: error.message }],
|
|
169
|
+
isError: true
|
|
170
|
+
};
|
|
171
|
+
}
|
|
102
172
|
return {
|
|
103
173
|
content: [{ type: "text", text: `Error: ${error}` }],
|
|
104
174
|
isError: true
|
|
@@ -236,6 +306,7 @@ var FastMCP = class {
|
|
|
236
306
|
if (this.#prompts.length) {
|
|
237
307
|
capabilities.prompts = {};
|
|
238
308
|
}
|
|
309
|
+
capabilities.logging = {};
|
|
239
310
|
this.#server = new Server(
|
|
240
311
|
{ name: this.#options.name, version: this.#options.version },
|
|
241
312
|
{ capabilities }
|
|
@@ -328,6 +399,7 @@ var FastMCP = class {
|
|
|
328
399
|
}
|
|
329
400
|
};
|
|
330
401
|
export {
|
|
331
|
-
FastMCP
|
|
402
|
+
FastMCP,
|
|
403
|
+
UserError
|
|
332
404
|
};
|
|
333
405
|
//# sourceMappingURL=FastMCP.js.map
|
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\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 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 LoggingLevelSchema,\n LoggingMessageNotificationSchema,\n McpError,\n NotificationSchema,\n ReadResourceRequestSchema,\n ServerCapabilities,\n SetLevelRequestSchema,\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 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 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 #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: 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 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 result = await tool.execute(args, {\n reportProgress,\n log,\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 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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FastMCP } from "./FastMCP.js";
|
|
1
|
+
import { FastMCP, UserError } from "./FastMCP.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { test, expect, vi } from "vitest";
|
|
4
4
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
@@ -6,6 +6,12 @@ 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 {
|
|
10
|
+
ErrorCode,
|
|
11
|
+
JSONRPCMessage,
|
|
12
|
+
LoggingMessageNotificationSchema,
|
|
13
|
+
McpError,
|
|
14
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
9
15
|
|
|
10
16
|
// @ts-expect-error - figure out how to use --experimental-eventsource with vitest
|
|
11
17
|
global.EventSource = EventSource;
|
|
@@ -15,7 +21,13 @@ const runWithTestServer = async ({
|
|
|
15
21
|
start,
|
|
16
22
|
}: {
|
|
17
23
|
start: () => Promise<FastMCP>;
|
|
18
|
-
run: ({
|
|
24
|
+
run: ({
|
|
25
|
+
client,
|
|
26
|
+
server,
|
|
27
|
+
}: {
|
|
28
|
+
client: Client;
|
|
29
|
+
server: FastMCP;
|
|
30
|
+
}) => Promise<void>;
|
|
19
31
|
}) => {
|
|
20
32
|
const port = await getRandomPort();
|
|
21
33
|
|
|
@@ -46,7 +58,7 @@ const runWithTestServer = async ({
|
|
|
46
58
|
|
|
47
59
|
await client.connect(transport);
|
|
48
60
|
|
|
49
|
-
await run({ client });
|
|
61
|
+
await run({ client, server });
|
|
50
62
|
} finally {
|
|
51
63
|
await server.stop();
|
|
52
64
|
}
|
|
@@ -137,6 +149,74 @@ test("calls a tool", async () => {
|
|
|
137
149
|
});
|
|
138
150
|
});
|
|
139
151
|
|
|
152
|
+
test("handles UserError errors", async () => {
|
|
153
|
+
await runWithTestServer({
|
|
154
|
+
start: async () => {
|
|
155
|
+
const server = new FastMCP({
|
|
156
|
+
name: "Test",
|
|
157
|
+
version: "1.0.0",
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
server.addTool({
|
|
161
|
+
name: "add",
|
|
162
|
+
description: "Add two numbers",
|
|
163
|
+
parameters: z.object({
|
|
164
|
+
a: z.number(),
|
|
165
|
+
b: z.number(),
|
|
166
|
+
}),
|
|
167
|
+
execute: async (args) => {
|
|
168
|
+
throw new UserError("Something went wrong");
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return server;
|
|
173
|
+
},
|
|
174
|
+
run: async ({ client }) => {
|
|
175
|
+
expect(
|
|
176
|
+
await client.callTool({
|
|
177
|
+
name: "add",
|
|
178
|
+
arguments: {
|
|
179
|
+
a: 1,
|
|
180
|
+
b: 2,
|
|
181
|
+
},
|
|
182
|
+
}),
|
|
183
|
+
).toEqual({
|
|
184
|
+
content: [{ type: "text", text: "Something went wrong" }],
|
|
185
|
+
isError: true,
|
|
186
|
+
});
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("calling an unknown tool throws McpError with MethodNotFound code", async () => {
|
|
192
|
+
await runWithTestServer({
|
|
193
|
+
start: async () => {
|
|
194
|
+
const server = new FastMCP({
|
|
195
|
+
name: "Test",
|
|
196
|
+
version: "1.0.0",
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
return server;
|
|
200
|
+
},
|
|
201
|
+
run: async ({ client }) => {
|
|
202
|
+
try {
|
|
203
|
+
await client.callTool({
|
|
204
|
+
name: "add",
|
|
205
|
+
arguments: {
|
|
206
|
+
a: 1,
|
|
207
|
+
b: 2,
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
} catch (error) {
|
|
211
|
+
expect(error).toBeInstanceOf(McpError);
|
|
212
|
+
|
|
213
|
+
// @ts-expect-error - we know that error is an McpError
|
|
214
|
+
expect(error.code).toBe(ErrorCode.MethodNotFound);
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
140
220
|
test("tracks tool progress", async () => {
|
|
141
221
|
await runWithTestServer({
|
|
142
222
|
start: async () => {
|
|
@@ -192,6 +272,127 @@ test("tracks tool progress", async () => {
|
|
|
192
272
|
});
|
|
193
273
|
});
|
|
194
274
|
|
|
275
|
+
test("sets logging levels", async () => {
|
|
276
|
+
await runWithTestServer({
|
|
277
|
+
start: async () => {
|
|
278
|
+
const server = new FastMCP({
|
|
279
|
+
name: "Test",
|
|
280
|
+
version: "1.0.0",
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
return server;
|
|
284
|
+
},
|
|
285
|
+
run: async ({ client, server }) => {
|
|
286
|
+
await client.setLoggingLevel("debug");
|
|
287
|
+
|
|
288
|
+
expect(server.loggingLevel).toBe("debug");
|
|
289
|
+
|
|
290
|
+
await client.setLoggingLevel("info");
|
|
291
|
+
|
|
292
|
+
expect(server.loggingLevel).toBe("info");
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const onMessage = (
|
|
298
|
+
client: Client,
|
|
299
|
+
callback: (message: JSONRPCMessage) => void,
|
|
300
|
+
) => {
|
|
301
|
+
if (!client.transport) {
|
|
302
|
+
throw new Error("Transport not set");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const onmessage = client.transport.onmessage;
|
|
306
|
+
|
|
307
|
+
if (!onmessage) {
|
|
308
|
+
throw new Error("onmessage not set");
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
client.transport.onmessage = (message) => {
|
|
312
|
+
console.log("message", message);
|
|
313
|
+
|
|
314
|
+
onmessage(message);
|
|
315
|
+
|
|
316
|
+
callback(message);
|
|
317
|
+
};
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
test("sends logging messages to the client", async () => {
|
|
321
|
+
await runWithTestServer({
|
|
322
|
+
start: async () => {
|
|
323
|
+
const server = new FastMCP({
|
|
324
|
+
name: "Test",
|
|
325
|
+
version: "1.0.0",
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
server.addTool({
|
|
329
|
+
name: "add",
|
|
330
|
+
description: "Add two numbers",
|
|
331
|
+
parameters: z.object({
|
|
332
|
+
a: z.number(),
|
|
333
|
+
b: z.number(),
|
|
334
|
+
}),
|
|
335
|
+
execute: async (args, { log }) => {
|
|
336
|
+
log.debug("debug message", {
|
|
337
|
+
foo: "bar",
|
|
338
|
+
});
|
|
339
|
+
log.error("error message");
|
|
340
|
+
log.info("info message");
|
|
341
|
+
log.warn("warn message");
|
|
342
|
+
|
|
343
|
+
return args.a + args.b;
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
return server;
|
|
348
|
+
},
|
|
349
|
+
run: async ({ client, server }) => {
|
|
350
|
+
const onLog = vi.fn();
|
|
351
|
+
|
|
352
|
+
client.setNotificationHandler(
|
|
353
|
+
LoggingMessageNotificationSchema,
|
|
354
|
+
(message) => {
|
|
355
|
+
if (message.method === "notifications/message") {
|
|
356
|
+
onLog({
|
|
357
|
+
level: message.params.level,
|
|
358
|
+
...(message.params.data ?? {}),
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
await client.callTool({
|
|
365
|
+
name: "add",
|
|
366
|
+
arguments: {
|
|
367
|
+
a: 1,
|
|
368
|
+
b: 2,
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
expect(onLog).toHaveBeenCalledTimes(4);
|
|
373
|
+
expect(onLog).toHaveBeenNthCalledWith(1, {
|
|
374
|
+
level: "debug",
|
|
375
|
+
message: "debug message",
|
|
376
|
+
context: {
|
|
377
|
+
foo: "bar",
|
|
378
|
+
},
|
|
379
|
+
});
|
|
380
|
+
expect(onLog).toHaveBeenNthCalledWith(2, {
|
|
381
|
+
level: "error",
|
|
382
|
+
message: "error message",
|
|
383
|
+
});
|
|
384
|
+
expect(onLog).toHaveBeenNthCalledWith(3, {
|
|
385
|
+
level: "info",
|
|
386
|
+
message: "info message",
|
|
387
|
+
});
|
|
388
|
+
expect(onLog).toHaveBeenNthCalledWith(4, {
|
|
389
|
+
level: "warning",
|
|
390
|
+
message: "warn message",
|
|
391
|
+
});
|
|
392
|
+
},
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
|
|
195
396
|
test("adds resources", async () => {
|
|
196
397
|
await runWithTestServer({
|
|
197
398
|
start: async () => {
|
package/src/FastMCP.ts
CHANGED
|
@@ -8,16 +8,51 @@ import {
|
|
|
8
8
|
ListPromptsRequestSchema,
|
|
9
9
|
ListResourcesRequestSchema,
|
|
10
10
|
ListToolsRequestSchema,
|
|
11
|
+
LoggingLevel,
|
|
12
|
+
LoggingLevelSchema,
|
|
13
|
+
LoggingMessageNotificationSchema,
|
|
11
14
|
McpError,
|
|
15
|
+
NotificationSchema,
|
|
12
16
|
ReadResourceRequestSchema,
|
|
13
17
|
ServerCapabilities,
|
|
18
|
+
SetLevelRequestSchema,
|
|
14
19
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
15
20
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
16
21
|
import type { z } from "zod";
|
|
17
22
|
import http from "http";
|
|
18
23
|
|
|
24
|
+
abstract class FastMCPError extends Error {
|
|
25
|
+
public constructor(message?: string) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = new.target.name;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type Extra = unknown;
|
|
32
|
+
|
|
33
|
+
type Extras = Record<string, Extra>;
|
|
34
|
+
|
|
35
|
+
class UnexpectedStateError extends FastMCPError {
|
|
36
|
+
public extras?: Extras;
|
|
37
|
+
|
|
38
|
+
public constructor(message: string, extras?: Extras) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.name = new.target.name;
|
|
41
|
+
this.extras = extras;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export class UserError extends UnexpectedStateError {}
|
|
46
|
+
|
|
19
47
|
type ToolParameters = z.ZodTypeAny;
|
|
20
48
|
|
|
49
|
+
type Literal = boolean | null | number | string | undefined;
|
|
50
|
+
|
|
51
|
+
type SerializableValue =
|
|
52
|
+
| Literal
|
|
53
|
+
| SerializableValue[]
|
|
54
|
+
| { [key: string]: SerializableValue };
|
|
55
|
+
|
|
21
56
|
type Progress = {
|
|
22
57
|
/**
|
|
23
58
|
* The progress thus far. This should increase every time progress is made, even if the total is unknown.
|
|
@@ -31,6 +66,12 @@ type Progress = {
|
|
|
31
66
|
|
|
32
67
|
type Context = {
|
|
33
68
|
reportProgress: (progress: Progress) => Promise<void>;
|
|
69
|
+
log: {
|
|
70
|
+
debug: (message: string, data?: SerializableValue) => void;
|
|
71
|
+
error: (message: string, data?: SerializableValue) => void;
|
|
72
|
+
info: (message: string, data?: SerializableValue) => void;
|
|
73
|
+
warn: (message: string, data?: SerializableValue) => void;
|
|
74
|
+
};
|
|
34
75
|
};
|
|
35
76
|
|
|
36
77
|
type Tool<Params extends ToolParameters = ToolParameters> = {
|
|
@@ -84,6 +125,7 @@ export class FastMCP {
|
|
|
84
125
|
#prompts: Prompt[];
|
|
85
126
|
#server: Server | null = null;
|
|
86
127
|
#options: ServerOptions;
|
|
128
|
+
#loggingLevel: LoggingLevel = "info";
|
|
87
129
|
|
|
88
130
|
constructor(public options: ServerOptions) {
|
|
89
131
|
this.#options = options;
|
|
@@ -106,6 +148,12 @@ export class FastMCP {
|
|
|
106
148
|
if (this.#prompts.length) {
|
|
107
149
|
this.setupPromptHandlers(server);
|
|
108
150
|
}
|
|
151
|
+
|
|
152
|
+
server.setRequestHandler(SetLevelRequestSchema, (request) => {
|
|
153
|
+
this.#loggingLevel = request.params.level;
|
|
154
|
+
|
|
155
|
+
return {};
|
|
156
|
+
});
|
|
109
157
|
}
|
|
110
158
|
|
|
111
159
|
private setupErrorHandling(server: Server) {
|
|
@@ -118,6 +166,10 @@ export class FastMCP {
|
|
|
118
166
|
});
|
|
119
167
|
}
|
|
120
168
|
|
|
169
|
+
public get loggingLevel() {
|
|
170
|
+
return this.#loggingLevel;
|
|
171
|
+
}
|
|
172
|
+
|
|
121
173
|
private setupToolHandlers(server: Server) {
|
|
122
174
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
123
175
|
return {
|
|
@@ -177,10 +229,57 @@ export class FastMCP {
|
|
|
177
229
|
});
|
|
178
230
|
};
|
|
179
231
|
|
|
232
|
+
const log = {
|
|
233
|
+
debug: (message: string, context?: SerializableValue) => {
|
|
234
|
+
server.sendLoggingMessage({
|
|
235
|
+
level: "debug",
|
|
236
|
+
data: {
|
|
237
|
+
message,
|
|
238
|
+
context,
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
},
|
|
242
|
+
error: (message: string, context?: SerializableValue) => {
|
|
243
|
+
server.sendLoggingMessage({
|
|
244
|
+
level: "error",
|
|
245
|
+
data: {
|
|
246
|
+
message,
|
|
247
|
+
context,
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
},
|
|
251
|
+
info: (message: string, context?: SerializableValue) => {
|
|
252
|
+
server.sendLoggingMessage({
|
|
253
|
+
level: "info",
|
|
254
|
+
data: {
|
|
255
|
+
message,
|
|
256
|
+
context,
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
},
|
|
260
|
+
warn: (message: string, context?: SerializableValue) => {
|
|
261
|
+
server.sendLoggingMessage({
|
|
262
|
+
level: "warning",
|
|
263
|
+
data: {
|
|
264
|
+
message,
|
|
265
|
+
context,
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
|
|
180
271
|
result = await tool.execute(args, {
|
|
181
272
|
reportProgress,
|
|
273
|
+
log,
|
|
182
274
|
});
|
|
183
275
|
} catch (error) {
|
|
276
|
+
if (error instanceof UserError) {
|
|
277
|
+
return {
|
|
278
|
+
content: [{ type: "text", text: error.message }],
|
|
279
|
+
isError: true,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
184
283
|
return {
|
|
185
284
|
content: [{ type: "text", text: `Error: ${error}` }],
|
|
186
285
|
isError: true,
|
|
@@ -350,6 +449,8 @@ export class FastMCP {
|
|
|
350
449
|
capabilities.prompts = {};
|
|
351
450
|
}
|
|
352
451
|
|
|
452
|
+
capabilities.logging = {};
|
|
453
|
+
|
|
353
454
|
this.#server = new Server(
|
|
354
455
|
{ name: this.#options.name, version: this.#options.version },
|
|
355
456
|
{ capabilities },
|