fastmcp 1.0.2 → 1.2.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 +55 -2
- package/dist/FastMCP.d.ts +26 -2
- package/dist/FastMCP.js +71 -33
- package/dist/FastMCP.js.map +1 -1
- package/package.json +1 -1
- package/src/FastMCP.test.ts +165 -2
- package/src/FastMCP.ts +100 -38
package/README.md
CHANGED
|
@@ -10,8 +10,9 @@ A TypeScript framework for building [MCP](https://modelcontextprotocol.io/) serv
|
|
|
10
10
|
- Simple Tool, Resource, Prompt definition
|
|
11
11
|
- Full TypeScript support
|
|
12
12
|
- Built-in error handling
|
|
13
|
-
- Built-in CLI for testing and debugging
|
|
14
|
-
- Built-in support for SSE
|
|
13
|
+
- Built-in CLI for [testing](#test-with-mcp-cli) and [debugging](#inspect-with-mcp-inspector)
|
|
14
|
+
- Built-in support for [SSE](#sse)
|
|
15
|
+
- Built-in [progress notifications](#progress)
|
|
15
16
|
|
|
16
17
|
## Installation
|
|
17
18
|
|
|
@@ -109,6 +110,58 @@ server.addTool({
|
|
|
109
110
|
});
|
|
110
111
|
```
|
|
111
112
|
|
|
113
|
+
#### Errors
|
|
114
|
+
|
|
115
|
+
The errors that are meant to be shown to the user should be thrown as `UserError` instances:
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
import { UserError } from "fastmcp";
|
|
119
|
+
|
|
120
|
+
server.addTool({
|
|
121
|
+
name: "download",
|
|
122
|
+
description: "Download a file",
|
|
123
|
+
parameters: z.object({
|
|
124
|
+
url: z.string(),
|
|
125
|
+
}),
|
|
126
|
+
execute: async (args) => {
|
|
127
|
+
if (args.url.startsWith("https://example.com")) {
|
|
128
|
+
throw new UserError("This URL is not allowed");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return "done";
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### Progress
|
|
137
|
+
|
|
138
|
+
Tools can report progress by calling `reportProgress` in the context object:
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
server.addTool({
|
|
142
|
+
name: "download",
|
|
143
|
+
description: "Download a file",
|
|
144
|
+
parameters: z.object({
|
|
145
|
+
url: z.string(),
|
|
146
|
+
}),
|
|
147
|
+
execute: async (args, { reportProgress }) => {
|
|
148
|
+
reportProgress({
|
|
149
|
+
progress: 0,
|
|
150
|
+
total: 100,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// ...
|
|
154
|
+
|
|
155
|
+
reportProgress({
|
|
156
|
+
progress: 100,
|
|
157
|
+
total: 100,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return "done";
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
112
165
|
### Resources
|
|
113
166
|
|
|
114
167
|
[Resources](https://modelcontextprotocol.io/docs/concepts/resources) represent any kind of data that an MCP server wants to make available to clients. This can include:
|
package/dist/FastMCP.d.ts
CHANGED
|
@@ -1,11 +1,35 @@
|
|
|
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 Progress = {
|
|
16
|
+
/**
|
|
17
|
+
* The progress thus far. This should increase every time progress is made, even if the total is unknown.
|
|
18
|
+
*/
|
|
19
|
+
progress: number;
|
|
20
|
+
/**
|
|
21
|
+
* Total number of items to process (or total progress required), if known.
|
|
22
|
+
*/
|
|
23
|
+
total?: number;
|
|
24
|
+
};
|
|
25
|
+
type Context = {
|
|
26
|
+
reportProgress: (progress: Progress) => Promise<void>;
|
|
27
|
+
};
|
|
4
28
|
type Tool<Params extends ToolParameters = ToolParameters> = {
|
|
5
29
|
name: string;
|
|
6
30
|
description?: string;
|
|
7
31
|
parameters?: Params;
|
|
8
|
-
execute: (args: z.infer<Params
|
|
32
|
+
execute: (args: z.infer<Params>, context: Context) => Promise<unknown>;
|
|
9
33
|
};
|
|
10
34
|
type Resource = {
|
|
11
35
|
uri: string;
|
|
@@ -66,4 +90,4 @@ declare class FastMCP {
|
|
|
66
90
|
stop(): Promise<void>;
|
|
67
91
|
}
|
|
68
92
|
|
|
69
|
-
export { FastMCP };
|
|
93
|
+
export { FastMCP, UserError };
|
package/dist/FastMCP.js
CHANGED
|
@@ -14,6 +14,22 @@ import {
|
|
|
14
14
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
15
15
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
16
16
|
import http from "http";
|
|
17
|
+
var FastMCPError = class extends Error {
|
|
18
|
+
constructor(message) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = new.target.name;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
var UnexpectedStateError = class extends FastMCPError {
|
|
24
|
+
extras;
|
|
25
|
+
constructor(message, extras) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = new.target.name;
|
|
28
|
+
this.extras = extras;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var UserError = class extends UnexpectedStateError {
|
|
32
|
+
};
|
|
17
33
|
var FastMCP = class {
|
|
18
34
|
constructor(options) {
|
|
19
35
|
this.options = options;
|
|
@@ -60,45 +76,66 @@ var FastMCP = class {
|
|
|
60
76
|
})
|
|
61
77
|
};
|
|
62
78
|
});
|
|
63
|
-
server.setRequestHandler(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
throw new McpError(
|
|
69
|
-
ErrorCode.MethodNotFound,
|
|
70
|
-
`Unknown tool: ${request.params.name}`
|
|
79
|
+
server.setRequestHandler(
|
|
80
|
+
CallToolRequestSchema,
|
|
81
|
+
async (request, ...extra) => {
|
|
82
|
+
const tool = this.#tools.find(
|
|
83
|
+
(tool2) => tool2.name === request.params.name
|
|
71
84
|
);
|
|
72
|
-
|
|
73
|
-
let args = void 0;
|
|
74
|
-
if (tool.parameters) {
|
|
75
|
-
const parsed = tool.parameters.safeParse(request.params.arguments);
|
|
76
|
-
if (!parsed.success) {
|
|
85
|
+
if (!tool) {
|
|
77
86
|
throw new McpError(
|
|
78
|
-
ErrorCode.
|
|
79
|
-
`
|
|
87
|
+
ErrorCode.MethodNotFound,
|
|
88
|
+
`Unknown tool: ${request.params.name}`
|
|
80
89
|
);
|
|
81
90
|
}
|
|
82
|
-
args =
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
let args = void 0;
|
|
92
|
+
if (tool.parameters) {
|
|
93
|
+
const parsed = tool.parameters.safeParse(request.params.arguments);
|
|
94
|
+
if (!parsed.success) {
|
|
95
|
+
throw new McpError(
|
|
96
|
+
ErrorCode.InvalidRequest,
|
|
97
|
+
`Invalid ${request.params.name} arguments`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
args = parsed.data;
|
|
101
|
+
}
|
|
102
|
+
const progressToken = request.params?._meta?.progressToken;
|
|
103
|
+
let result;
|
|
104
|
+
try {
|
|
105
|
+
const reportProgress = async (progress) => {
|
|
106
|
+
await server.notification({
|
|
107
|
+
method: "notifications/progress",
|
|
108
|
+
params: {
|
|
109
|
+
...progress,
|
|
110
|
+
progressToken
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
result = await tool.execute(args, {
|
|
115
|
+
reportProgress
|
|
116
|
+
});
|
|
117
|
+
} catch (error) {
|
|
118
|
+
if (error instanceof UserError) {
|
|
119
|
+
return {
|
|
120
|
+
content: [{ type: "text", text: error.message }],
|
|
121
|
+
isError: true
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
content: [{ type: "text", text: `Error: ${error}` }],
|
|
126
|
+
isError: true
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (typeof result === "string") {
|
|
130
|
+
return {
|
|
131
|
+
content: [{ type: "text", text: result }]
|
|
132
|
+
};
|
|
133
|
+
}
|
|
94
134
|
return {
|
|
95
|
-
content: [{ type: "text", text: result }]
|
|
135
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
96
136
|
};
|
|
97
137
|
}
|
|
98
|
-
|
|
99
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
100
|
-
};
|
|
101
|
-
});
|
|
138
|
+
);
|
|
102
139
|
}
|
|
103
140
|
setupResourceHandlers(server) {
|
|
104
141
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
@@ -313,6 +350,7 @@ var FastMCP = class {
|
|
|
313
350
|
}
|
|
314
351
|
};
|
|
315
352
|
export {
|
|
316
|
-
FastMCP
|
|
353
|
+
FastMCP,
|
|
354
|
+
UserError
|
|
317
355
|
};
|
|
318
356
|
//# 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 Tool<Params extends ToolParameters = ToolParameters> = {\n name: string;\n description?: string;\n parameters?: Params;\n execute: (args: z.infer<Params>) => 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(CallToolRequestSchema, async (request) => {\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 let result: any;\n\n try {\n result = await tool.execute(args);\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 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 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"]}
|
package/package.json
CHANGED
package/src/FastMCP.test.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { FastMCP } from "./FastMCP.js";
|
|
1
|
+
import { FastMCP, UserError } from "./FastMCP.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { test, expect } from "vitest";
|
|
3
|
+
import { test, expect, vi } from "vitest";
|
|
4
4
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
5
5
|
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
6
6
|
import { getRandomPort } from "get-port-please";
|
|
7
7
|
import { EventSource } from "eventsource";
|
|
8
|
+
import { setTimeout as delay } from "timers/promises";
|
|
9
|
+
import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
8
10
|
|
|
9
11
|
// @ts-expect-error - figure out how to use --experimental-eventsource with vitest
|
|
10
12
|
global.EventSource = EventSource;
|
|
@@ -98,6 +100,167 @@ test("adds tools", async () => {
|
|
|
98
100
|
});
|
|
99
101
|
});
|
|
100
102
|
|
|
103
|
+
test("calls a tool", async () => {
|
|
104
|
+
await runWithTestServer({
|
|
105
|
+
start: async () => {
|
|
106
|
+
const server = new FastMCP({
|
|
107
|
+
name: "Test",
|
|
108
|
+
version: "1.0.0",
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
server.addTool({
|
|
112
|
+
name: "add",
|
|
113
|
+
description: "Add two numbers",
|
|
114
|
+
parameters: z.object({
|
|
115
|
+
a: z.number(),
|
|
116
|
+
b: z.number(),
|
|
117
|
+
}),
|
|
118
|
+
execute: async (args) => {
|
|
119
|
+
return args.a + args.b;
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
return server;
|
|
124
|
+
},
|
|
125
|
+
run: async ({ client }) => {
|
|
126
|
+
expect(
|
|
127
|
+
await client.callTool({
|
|
128
|
+
name: "add",
|
|
129
|
+
arguments: {
|
|
130
|
+
a: 1,
|
|
131
|
+
b: 2,
|
|
132
|
+
},
|
|
133
|
+
}),
|
|
134
|
+
).toEqual({
|
|
135
|
+
content: [{ type: "text", text: "3" }],
|
|
136
|
+
});
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("handles UserError errors", async () => {
|
|
142
|
+
await runWithTestServer({
|
|
143
|
+
start: async () => {
|
|
144
|
+
const server = new FastMCP({
|
|
145
|
+
name: "Test",
|
|
146
|
+
version: "1.0.0",
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
server.addTool({
|
|
150
|
+
name: "add",
|
|
151
|
+
description: "Add two numbers",
|
|
152
|
+
parameters: z.object({
|
|
153
|
+
a: z.number(),
|
|
154
|
+
b: z.number(),
|
|
155
|
+
}),
|
|
156
|
+
execute: async (args) => {
|
|
157
|
+
throw new UserError("Something went wrong");
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return server;
|
|
162
|
+
},
|
|
163
|
+
run: async ({ client }) => {
|
|
164
|
+
expect(
|
|
165
|
+
await client.callTool({
|
|
166
|
+
name: "add",
|
|
167
|
+
arguments: {
|
|
168
|
+
a: 1,
|
|
169
|
+
b: 2,
|
|
170
|
+
},
|
|
171
|
+
}),
|
|
172
|
+
).toEqual({
|
|
173
|
+
content: [{ type: "text", text: "Something went wrong" }],
|
|
174
|
+
isError: true,
|
|
175
|
+
});
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("calling an unknown tool throws McpError with MethodNotFound code", async () => {
|
|
181
|
+
await runWithTestServer({
|
|
182
|
+
start: async () => {
|
|
183
|
+
const server = new FastMCP({
|
|
184
|
+
name: "Test",
|
|
185
|
+
version: "1.0.0",
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
return server;
|
|
189
|
+
},
|
|
190
|
+
run: async ({ client }) => {
|
|
191
|
+
try {
|
|
192
|
+
await client.callTool({
|
|
193
|
+
name: "add",
|
|
194
|
+
arguments: {
|
|
195
|
+
a: 1,
|
|
196
|
+
b: 2,
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
} catch (error) {
|
|
200
|
+
expect(error).toBeInstanceOf(McpError);
|
|
201
|
+
|
|
202
|
+
// @ts-expect-error - we know that error is an McpError
|
|
203
|
+
expect(error.code).toBe(ErrorCode.MethodNotFound);
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("tracks tool progress", async () => {
|
|
210
|
+
await runWithTestServer({
|
|
211
|
+
start: async () => {
|
|
212
|
+
const server = new FastMCP({
|
|
213
|
+
name: "Test",
|
|
214
|
+
version: "1.0.0",
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
server.addTool({
|
|
218
|
+
name: "add",
|
|
219
|
+
description: "Add two numbers",
|
|
220
|
+
parameters: z.object({
|
|
221
|
+
a: z.number(),
|
|
222
|
+
b: z.number(),
|
|
223
|
+
}),
|
|
224
|
+
execute: async (args, { reportProgress }) => {
|
|
225
|
+
reportProgress({
|
|
226
|
+
progress: 0,
|
|
227
|
+
total: 10,
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
await delay(100);
|
|
231
|
+
|
|
232
|
+
return args.a + args.b;
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
return server;
|
|
237
|
+
},
|
|
238
|
+
run: async ({ client }) => {
|
|
239
|
+
const onProgress = vi.fn();
|
|
240
|
+
|
|
241
|
+
await client.callTool(
|
|
242
|
+
{
|
|
243
|
+
name: "add",
|
|
244
|
+
arguments: {
|
|
245
|
+
a: 1,
|
|
246
|
+
b: 2,
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
undefined,
|
|
250
|
+
{
|
|
251
|
+
onprogress: onProgress,
|
|
252
|
+
},
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
expect(onProgress).toHaveBeenCalledTimes(1);
|
|
256
|
+
expect(onProgress).toHaveBeenCalledWith({
|
|
257
|
+
progress: 0,
|
|
258
|
+
total: 10,
|
|
259
|
+
});
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
101
264
|
test("adds resources", async () => {
|
|
102
265
|
await runWithTestServer({
|
|
103
266
|
start: async () => {
|
package/src/FastMCP.ts
CHANGED
|
@@ -16,13 +16,51 @@ import { zodToJsonSchema } from "zod-to-json-schema";
|
|
|
16
16
|
import type { z } from "zod";
|
|
17
17
|
import http from "http";
|
|
18
18
|
|
|
19
|
+
abstract class FastMCPError extends Error {
|
|
20
|
+
public constructor(message?: string) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = new.target.name;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type Extra = unknown;
|
|
27
|
+
|
|
28
|
+
type Extras = Record<string, Extra>;
|
|
29
|
+
|
|
30
|
+
class UnexpectedStateError extends FastMCPError {
|
|
31
|
+
public extras?: Extras;
|
|
32
|
+
|
|
33
|
+
public constructor(message: string, extras?: Extras) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = new.target.name;
|
|
36
|
+
this.extras = extras;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class UserError extends UnexpectedStateError {}
|
|
41
|
+
|
|
19
42
|
type ToolParameters = z.ZodTypeAny;
|
|
20
43
|
|
|
44
|
+
type Progress = {
|
|
45
|
+
/**
|
|
46
|
+
* The progress thus far. This should increase every time progress is made, even if the total is unknown.
|
|
47
|
+
*/
|
|
48
|
+
progress: number;
|
|
49
|
+
/**
|
|
50
|
+
* Total number of items to process (or total progress required), if known.
|
|
51
|
+
*/
|
|
52
|
+
total?: number;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type Context = {
|
|
56
|
+
reportProgress: (progress: Progress) => Promise<void>;
|
|
57
|
+
};
|
|
58
|
+
|
|
21
59
|
type Tool<Params extends ToolParameters = ToolParameters> = {
|
|
22
60
|
name: string;
|
|
23
61
|
description?: string;
|
|
24
62
|
parameters?: Params;
|
|
25
|
-
execute: (args: z.infer<Params
|
|
63
|
+
execute: (args: z.infer<Params>, context: Context) => Promise<unknown>;
|
|
26
64
|
};
|
|
27
65
|
|
|
28
66
|
type Resource = {
|
|
@@ -118,54 +156,78 @@ export class FastMCP {
|
|
|
118
156
|
};
|
|
119
157
|
});
|
|
120
158
|
|
|
121
|
-
server.setRequestHandler(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (!tool) {
|
|
127
|
-
throw new McpError(
|
|
128
|
-
ErrorCode.MethodNotFound,
|
|
129
|
-
`Unknown tool: ${request.params.name}`,
|
|
159
|
+
server.setRequestHandler(
|
|
160
|
+
CallToolRequestSchema,
|
|
161
|
+
async (request, ...extra) => {
|
|
162
|
+
const tool = this.#tools.find(
|
|
163
|
+
(tool) => tool.name === request.params.name,
|
|
130
164
|
);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
let args: any = undefined;
|
|
134
165
|
|
|
135
|
-
|
|
136
|
-
const parsed = tool.parameters.safeParse(request.params.arguments);
|
|
137
|
-
|
|
138
|
-
if (!parsed.success) {
|
|
166
|
+
if (!tool) {
|
|
139
167
|
throw new McpError(
|
|
140
|
-
ErrorCode.
|
|
141
|
-
`
|
|
168
|
+
ErrorCode.MethodNotFound,
|
|
169
|
+
`Unknown tool: ${request.params.name}`,
|
|
142
170
|
);
|
|
143
171
|
}
|
|
144
172
|
|
|
145
|
-
args =
|
|
146
|
-
}
|
|
173
|
+
let args: any = undefined;
|
|
147
174
|
|
|
148
|
-
|
|
175
|
+
if (tool.parameters) {
|
|
176
|
+
const parsed = tool.parameters.safeParse(request.params.arguments);
|
|
149
177
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
178
|
+
if (!parsed.success) {
|
|
179
|
+
throw new McpError(
|
|
180
|
+
ErrorCode.InvalidRequest,
|
|
181
|
+
`Invalid ${request.params.name} arguments`,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
args = parsed.data;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const progressToken = request.params?._meta?.progressToken;
|
|
189
|
+
|
|
190
|
+
let result: any;
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
const reportProgress = async (progress: Progress) => {
|
|
194
|
+
await server.notification({
|
|
195
|
+
method: "notifications/progress",
|
|
196
|
+
params: {
|
|
197
|
+
...progress,
|
|
198
|
+
progressToken,
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
result = await tool.execute(args, {
|
|
204
|
+
reportProgress,
|
|
205
|
+
});
|
|
206
|
+
} catch (error) {
|
|
207
|
+
if (error instanceof UserError) {
|
|
208
|
+
return {
|
|
209
|
+
content: [{ type: "text", text: error.message }],
|
|
210
|
+
isError: true,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
content: [{ type: "text", text: `Error: ${error}` }],
|
|
216
|
+
isError: true,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (typeof result === "string") {
|
|
221
|
+
return {
|
|
222
|
+
content: [{ type: "text", text: result }],
|
|
223
|
+
};
|
|
224
|
+
}
|
|
158
225
|
|
|
159
|
-
if (typeof result === "string") {
|
|
160
226
|
return {
|
|
161
|
-
content: [{ type: "text", text: result }],
|
|
227
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
162
228
|
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return {
|
|
166
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
167
|
-
};
|
|
168
|
-
});
|
|
229
|
+
},
|
|
230
|
+
);
|
|
169
231
|
}
|
|
170
232
|
|
|
171
233
|
private setupResourceHandlers(server: Server) {
|