fastmcp 1.4.0 → 1.5.1
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 +70 -0
- package/dist/FastMCP.d.ts +25 -58
- package/dist/FastMCP.js +116 -87
- package/dist/FastMCP.js.map +1 -1
- package/jsr.json +5 -0
- package/package.json +12 -2
- package/src/FastMCP.test.ts +52 -3
- package/src/FastMCP.ts +163 -104
- package/tsconfig.json +6 -1
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ 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 support for [image content](#returning-an-image)
|
|
12
13
|
- Built-in [logging](#logging)
|
|
13
14
|
- Built-in [error handling](#errors)
|
|
14
15
|
- Built-in CLI for [testing](#test-with-mcp-cli) and [debugging](#inspect-with-mcp-inspector)
|
|
@@ -171,6 +172,75 @@ server.addTool({
|
|
|
171
172
|
});
|
|
172
173
|
```
|
|
173
174
|
|
|
175
|
+
#### Returning an image
|
|
176
|
+
|
|
177
|
+
Use the `imageContent` to create a content object for an image:
|
|
178
|
+
|
|
179
|
+
```js
|
|
180
|
+
import { imageContent } from "fastmcp";
|
|
181
|
+
|
|
182
|
+
server.addTool({
|
|
183
|
+
name: "download",
|
|
184
|
+
description: "Download a file",
|
|
185
|
+
parameters: z.object({
|
|
186
|
+
url: z.string(),
|
|
187
|
+
}),
|
|
188
|
+
execute: async (args) => {
|
|
189
|
+
return imageContent({
|
|
190
|
+
url: "https://example.com/image.png",
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// or...
|
|
194
|
+
// return imageContent({
|
|
195
|
+
// path: "/path/to/image.png",
|
|
196
|
+
// });
|
|
197
|
+
|
|
198
|
+
// or...
|
|
199
|
+
// return imageContent({
|
|
200
|
+
// buffer: Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", "base64"),
|
|
201
|
+
// });
|
|
202
|
+
|
|
203
|
+
// or...
|
|
204
|
+
// return {
|
|
205
|
+
// content: [
|
|
206
|
+
// await imageContent(...)
|
|
207
|
+
// ],
|
|
208
|
+
// };
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
The `imageContent` function takes the following options:
|
|
214
|
+
|
|
215
|
+
- `url`: The URL of the image.
|
|
216
|
+
- `path`: The path to the image file.
|
|
217
|
+
- `buffer`: The image data as a buffer.
|
|
218
|
+
|
|
219
|
+
Only one of `url`, `path`, or `buffer` must be specified.
|
|
220
|
+
|
|
221
|
+
The above example is equivalent to:
|
|
222
|
+
|
|
223
|
+
```js
|
|
224
|
+
server.addTool({
|
|
225
|
+
name: "download",
|
|
226
|
+
description: "Download a file",
|
|
227
|
+
parameters: z.object({
|
|
228
|
+
url: z.string(),
|
|
229
|
+
}),
|
|
230
|
+
execute: async (args) => {
|
|
231
|
+
return {
|
|
232
|
+
content: [
|
|
233
|
+
{
|
|
234
|
+
type: "image",
|
|
235
|
+
data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
|
|
236
|
+
mimeType: "image/png",
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
};
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
```
|
|
243
|
+
|
|
174
244
|
#### Logging
|
|
175
245
|
|
|
176
246
|
Tools can log messages to the client using the `log` object in the context object:
|
package/dist/FastMCP.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
+
declare const imageContent: (input: {
|
|
4
|
+
url: string;
|
|
5
|
+
} | {
|
|
6
|
+
path: string;
|
|
7
|
+
} | {
|
|
8
|
+
buffer: Buffer;
|
|
9
|
+
}) => Promise<ImageContent>;
|
|
3
10
|
declare abstract class FastMCPError extends Error {
|
|
4
11
|
constructor(message?: string);
|
|
5
12
|
}
|
|
@@ -35,66 +42,25 @@ type Context = {
|
|
|
35
42
|
warn: (message: string, data?: SerializableValue) => void;
|
|
36
43
|
};
|
|
37
44
|
};
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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>;
|
|
45
|
+
type TextContent = {
|
|
46
|
+
type: "text";
|
|
47
|
+
text: string;
|
|
48
|
+
};
|
|
49
|
+
type ImageContent = {
|
|
50
|
+
type: "image";
|
|
51
|
+
data: string;
|
|
52
|
+
mimeType: string;
|
|
53
|
+
};
|
|
54
|
+
type Content = TextContent | ImageContent;
|
|
55
|
+
type ContentResult = {
|
|
56
|
+
content: Content[];
|
|
57
|
+
isError?: boolean;
|
|
58
|
+
};
|
|
93
59
|
type Tool<Params extends ToolParameters = ToolParameters> = {
|
|
94
60
|
name: string;
|
|
95
61
|
description?: string;
|
|
96
62
|
parameters?: Params;
|
|
97
|
-
execute: (args: z.infer<Params>, context: Context) => Promise<string | ContentResult>;
|
|
63
|
+
execute: (args: z.infer<Params>, context: Context) => Promise<string | ContentResult | TextContent | ImageContent>;
|
|
98
64
|
};
|
|
99
65
|
type Resource = {
|
|
100
66
|
uri: string;
|
|
@@ -127,13 +93,14 @@ type ServerOptions = {
|
|
|
127
93
|
name: string;
|
|
128
94
|
version: `${number}.${number}.${number}`;
|
|
129
95
|
};
|
|
96
|
+
type LoggingLevel = "debug" | "info" | "notice" | "warning" | "error" | "critical" | "alert" | "emergency";
|
|
130
97
|
declare class FastMCP {
|
|
131
98
|
#private;
|
|
132
99
|
options: ServerOptions;
|
|
133
100
|
constructor(options: ServerOptions);
|
|
134
101
|
private setupHandlers;
|
|
135
102
|
private setupErrorHandling;
|
|
136
|
-
get loggingLevel():
|
|
103
|
+
get loggingLevel(): LoggingLevel;
|
|
137
104
|
private setupToolHandlers;
|
|
138
105
|
private setupResourceHandlers;
|
|
139
106
|
private setupPromptHandlers;
|
|
@@ -156,4 +123,4 @@ declare class FastMCP {
|
|
|
156
123
|
stop(): Promise<void>;
|
|
157
124
|
}
|
|
158
125
|
|
|
159
|
-
export { FastMCP, UserError };
|
|
126
|
+
export { FastMCP, UserError, imageContent };
|
package/dist/FastMCP.js
CHANGED
|
@@ -16,6 +16,33 @@ import {
|
|
|
16
16
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
17
17
|
import { z } from "zod";
|
|
18
18
|
import http from "http";
|
|
19
|
+
import { readFile } from "fs/promises";
|
|
20
|
+
import { fileTypeFromBuffer } from "file-type";
|
|
21
|
+
var imageContent = async (input) => {
|
|
22
|
+
let rawData;
|
|
23
|
+
if ("url" in input) {
|
|
24
|
+
const response = await fetch(input.url);
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
throw new Error(`Failed to fetch image from URL: ${response.statusText}`);
|
|
27
|
+
}
|
|
28
|
+
rawData = Buffer.from(await response.arrayBuffer());
|
|
29
|
+
} else if ("path" in input) {
|
|
30
|
+
rawData = await readFile(input.path);
|
|
31
|
+
} else if ("buffer" in input) {
|
|
32
|
+
rawData = input.buffer;
|
|
33
|
+
} else {
|
|
34
|
+
throw new Error(
|
|
35
|
+
"Invalid input: Provide a valid 'url', 'path', or 'buffer'"
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
const mimeType = await fileTypeFromBuffer(rawData);
|
|
39
|
+
const base64Data = rawData.toString("base64");
|
|
40
|
+
return {
|
|
41
|
+
type: "image",
|
|
42
|
+
data: base64Data,
|
|
43
|
+
mimeType: mimeType?.mime ?? "image/png"
|
|
44
|
+
};
|
|
45
|
+
};
|
|
19
46
|
var FastMCPError = class extends Error {
|
|
20
47
|
constructor(message) {
|
|
21
48
|
super(message);
|
|
@@ -112,105 +139,106 @@ var FastMCP = class {
|
|
|
112
139
|
})
|
|
113
140
|
};
|
|
114
141
|
});
|
|
115
|
-
server.setRequestHandler(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
142
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
143
|
+
const tool = this.#tools.find(
|
|
144
|
+
(tool2) => tool2.name === request.params.name
|
|
145
|
+
);
|
|
146
|
+
if (!tool) {
|
|
147
|
+
throw new McpError(
|
|
148
|
+
ErrorCode.MethodNotFound,
|
|
149
|
+
`Unknown tool: ${request.params.name}`
|
|
120
150
|
);
|
|
121
|
-
|
|
151
|
+
}
|
|
152
|
+
let args = void 0;
|
|
153
|
+
if (tool.parameters) {
|
|
154
|
+
const parsed = tool.parameters.safeParse(request.params.arguments);
|
|
155
|
+
if (!parsed.success) {
|
|
122
156
|
throw new McpError(
|
|
123
|
-
ErrorCode.
|
|
124
|
-
`
|
|
157
|
+
ErrorCode.InvalidRequest,
|
|
158
|
+
`Invalid ${request.params.name} arguments`
|
|
125
159
|
);
|
|
126
160
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const progressToken = request.params?._meta?.progressToken;
|
|
139
|
-
let result;
|
|
140
|
-
try {
|
|
141
|
-
const reportProgress = async (progress) => {
|
|
142
|
-
await server.notification({
|
|
143
|
-
method: "notifications/progress",
|
|
144
|
-
params: {
|
|
145
|
-
...progress,
|
|
146
|
-
progressToken
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
};
|
|
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
|
-
});
|
|
161
|
+
args = parsed.data;
|
|
162
|
+
}
|
|
163
|
+
const progressToken = request.params?._meta?.progressToken;
|
|
164
|
+
let result;
|
|
165
|
+
try {
|
|
166
|
+
const reportProgress = async (progress) => {
|
|
167
|
+
await server.notification({
|
|
168
|
+
method: "notifications/progress",
|
|
169
|
+
params: {
|
|
170
|
+
...progress,
|
|
171
|
+
progressToken
|
|
186
172
|
}
|
|
187
|
-
};
|
|
188
|
-
const maybeStringResult = await tool.execute(args, {
|
|
189
|
-
reportProgress,
|
|
190
|
-
log
|
|
191
173
|
});
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
174
|
+
};
|
|
175
|
+
const log = {
|
|
176
|
+
debug: (message, context) => {
|
|
177
|
+
server.sendLoggingMessage({
|
|
178
|
+
level: "debug",
|
|
179
|
+
data: {
|
|
180
|
+
message,
|
|
181
|
+
context
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
},
|
|
185
|
+
error: (message, context) => {
|
|
186
|
+
server.sendLoggingMessage({
|
|
187
|
+
level: "error",
|
|
188
|
+
data: {
|
|
189
|
+
message,
|
|
190
|
+
context
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
},
|
|
194
|
+
info: (message, context) => {
|
|
195
|
+
server.sendLoggingMessage({
|
|
196
|
+
level: "info",
|
|
197
|
+
data: {
|
|
198
|
+
message,
|
|
199
|
+
context
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
},
|
|
203
|
+
warn: (message, context) => {
|
|
204
|
+
server.sendLoggingMessage({
|
|
205
|
+
level: "warning",
|
|
206
|
+
data: {
|
|
207
|
+
message,
|
|
208
|
+
context
|
|
209
|
+
}
|
|
195
210
|
});
|
|
196
|
-
} else {
|
|
197
|
-
result = ContentResultZodSchema.parse(maybeStringResult);
|
|
198
|
-
}
|
|
199
|
-
} catch (error) {
|
|
200
|
-
if (error instanceof UserError) {
|
|
201
|
-
return {
|
|
202
|
-
content: [{ type: "text", text: error.message }],
|
|
203
|
-
isError: true
|
|
204
|
-
};
|
|
205
211
|
}
|
|
212
|
+
};
|
|
213
|
+
const maybeStringResult = await tool.execute(args, {
|
|
214
|
+
reportProgress,
|
|
215
|
+
log
|
|
216
|
+
});
|
|
217
|
+
if (typeof maybeStringResult === "string") {
|
|
218
|
+
result = ContentResultZodSchema.parse({
|
|
219
|
+
content: [{ type: "text", text: maybeStringResult }]
|
|
220
|
+
});
|
|
221
|
+
} else if ("type" in maybeStringResult) {
|
|
222
|
+
result = ContentResultZodSchema.parse({
|
|
223
|
+
content: [maybeStringResult]
|
|
224
|
+
});
|
|
225
|
+
} else {
|
|
226
|
+
result = ContentResultZodSchema.parse(maybeStringResult);
|
|
227
|
+
}
|
|
228
|
+
} catch (error) {
|
|
229
|
+
if (error instanceof UserError) {
|
|
206
230
|
return {
|
|
207
|
-
content: [{ type: "text", text:
|
|
231
|
+
content: [{ type: "text", text: error.message }],
|
|
208
232
|
isError: true
|
|
209
233
|
};
|
|
210
234
|
}
|
|
211
|
-
return
|
|
235
|
+
return {
|
|
236
|
+
content: [{ type: "text", text: `Error: ${error}` }],
|
|
237
|
+
isError: true
|
|
238
|
+
};
|
|
212
239
|
}
|
|
213
|
-
|
|
240
|
+
return result;
|
|
241
|
+
});
|
|
214
242
|
}
|
|
215
243
|
setupResourceHandlers(server) {
|
|
216
244
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
@@ -427,6 +455,7 @@ var FastMCP = class {
|
|
|
427
455
|
};
|
|
428
456
|
export {
|
|
429
457
|
FastMCP,
|
|
430
|
-
UserError
|
|
458
|
+
UserError,
|
|
459
|
+
imageContent
|
|
431
460
|
};
|
|
432
461
|
//# 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 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"]}
|
|
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 SetLevelRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { zodToJsonSchema } from \"zod-to-json-schema\";\nimport { z } from \"zod\";\nimport http from \"http\";\nimport { readFile } from \"fs/promises\";\nimport { fileTypeFromBuffer } from \"file-type\";\n\nexport const imageContent = async (\n input: { url: string } | { path: string } | { buffer: Buffer },\n): Promise<ImageContent> => {\n let rawData: Buffer;\n\n if (\"url\" in input) {\n const response = await fetch(input.url);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch image from URL: ${response.statusText}`);\n }\n\n rawData = Buffer.from(await response.arrayBuffer());\n } else if (\"path\" in input) {\n rawData = await readFile(input.path);\n } else if (\"buffer\" in input) {\n rawData = input.buffer;\n } else {\n throw new Error(\n \"Invalid input: Provide a valid 'url', 'path', or 'buffer'\",\n );\n }\n\n const mimeType = await fileTypeFromBuffer(rawData);\n\n const base64Data = rawData.toString(\"base64\");\n\n return {\n type: \"image\",\n data: base64Data,\n mimeType: mimeType?.mime ?? \"image/png\",\n } as const;\n};\n\nabstract class FastMCPError extends Error {\n public constructor(message?: string) {\n super(message);\n this.name = new.target.name;\n }\n}\n\ntype Extra = unknown;\n\ntype Extras = Record<string, Extra>;\n\nclass UnexpectedStateError extends FastMCPError {\n public extras?: Extras;\n\n public constructor(message: string, extras?: Extras) {\n super(message);\n this.name = new.target.name;\n this.extras = extras;\n }\n}\n\nexport class UserError extends UnexpectedStateError {}\n\ntype ToolParameters = z.ZodTypeAny;\n\ntype Literal = boolean | null | number | string | undefined;\n\ntype SerializableValue =\n | Literal\n | SerializableValue[]\n | { [key: string]: SerializableValue };\n\ntype Progress = {\n /**\n * The progress thus far. This should increase every time progress is made, even if the total is unknown.\n */\n progress: number;\n /**\n * Total number of items to process (or total progress required), if known.\n */\n total?: number;\n};\n\ntype Context = {\n reportProgress: (progress: Progress) => Promise<void>;\n log: {\n debug: (message: string, data?: SerializableValue) => void;\n error: (message: string, data?: SerializableValue) => void;\n info: (message: string, data?: SerializableValue) => void;\n warn: (message: string, data?: SerializableValue) => void;\n };\n};\n\ntype TextContent = {\n type: \"text\";\n text: string;\n};\n\nconst TextContentZodSchema = z\n .object({\n type: z.literal(\"text\"),\n /**\n * The text content of the message.\n */\n text: z.string(),\n })\n .strict() satisfies z.ZodType<TextContent>;\n\ntype ImageContent = {\n type: \"image\";\n data: string;\n mimeType: string;\n};\n\nconst ImageContentZodSchema = z\n .object({\n type: z.literal(\"image\"),\n /**\n * The base64-encoded image data.\n */\n data: z.string().base64(),\n /**\n * The MIME type of the image. Different providers may support different image types.\n */\n mimeType: z.string(),\n })\n .strict() satisfies z.ZodType<ImageContent>;\n\ntype Content = TextContent | ImageContent;\n\nconst ContentZodSchema = z.discriminatedUnion(\"type\", [\n TextContentZodSchema,\n ImageContentZodSchema,\n]) satisfies z.ZodType<Content>;\n\ntype ContentResult = {\n content: Content[];\n isError?: boolean;\n};\n\nconst ContentResultZodSchema = z\n .object({\n content: ContentZodSchema.array(),\n isError: z.boolean().optional(),\n })\n .strict() satisfies z.ZodType<ContentResult>;\n\ntype Tool<Params extends ToolParameters = ToolParameters> = {\n name: string;\n description?: string;\n parameters?: Params;\n execute: (\n args: z.infer<Params>,\n context: Context,\n ) => Promise<string | ContentResult | TextContent | ImageContent>;\n};\n\ntype Resource = {\n uri: string;\n name: string;\n description?: string;\n mimeType?: string;\n load: () => Promise<{ text: string } | { blob: string }>;\n};\n\ntype PromptArgument = Readonly<{\n name: string;\n description?: string;\n required?: boolean;\n}>;\n\ntype ArgumentsToObject<T extends PromptArgument[]> = {\n [K in T[number][\"name\"]]: Extract<\n T[number],\n { name: K }\n >[\"required\"] extends true\n ? string\n : string | undefined;\n};\n\ntype Prompt<\n Arguments extends PromptArgument[] = PromptArgument[],\n Args = ArgumentsToObject<Arguments>,\n> = {\n name: string;\n description?: string;\n arguments?: Arguments;\n load: (args: Args) => Promise<string>;\n};\n\ntype ServerOptions = {\n name: string;\n version: `${number}.${number}.${number}`;\n};\n\ntype LoggingLevel =\n | \"debug\"\n | \"info\"\n | \"notice\"\n | \"warning\"\n | \"error\"\n | \"critical\"\n | \"alert\"\n | \"emergency\";\n\nexport class 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\n process.on(\"SIGINT\", async () => {\n await server.close();\n process.exit(0);\n });\n }\n\n public get loggingLevel(): 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(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 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 if (\"type\" in maybeStringResult) {\n result = ContentResultZodSchema.parse({\n content: [maybeStringResult],\n });\n } else {\n result = ContentResultZodSchema.parse(maybeStringResult);\n }\n } catch (error) {\n if (error instanceof UserError) {\n return {\n content: [{ type: \"text\", text: error.message }],\n isError: true,\n };\n }\n\n return {\n content: [{ type: \"text\", text: `Error: ${error}` }],\n isError: true,\n };\n }\n\n return result;\n });\n }\n\n private setupResourceHandlers(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/jsr.json
ADDED
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastmcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"main": "dist/fastmcp.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "tsup",
|
|
7
|
-
"test": "vitest run",
|
|
7
|
+
"test": "vitest run && tsc",
|
|
8
8
|
"format": "prettier --write . && eslint --fix ."
|
|
9
9
|
},
|
|
10
10
|
"bin": {
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
25
25
|
"execa": "^9.5.2",
|
|
26
|
+
"file-type": "^19.6.0",
|
|
26
27
|
"yargs": "^17.7.2",
|
|
27
28
|
"zod": "^3.24.1",
|
|
28
29
|
"zod-to-json-schema": "^3.24.1"
|
|
@@ -33,9 +34,17 @@
|
|
|
33
34
|
"release": {
|
|
34
35
|
"branches": [
|
|
35
36
|
"main"
|
|
37
|
+
],
|
|
38
|
+
"plugins": [
|
|
39
|
+
"@semantic-release/commit-analyzer",
|
|
40
|
+
"@semantic-release/release-notes-generator",
|
|
41
|
+
"@semantic-release/npm",
|
|
42
|
+
"@semantic-release/github",
|
|
43
|
+
"@sebbo2002/semantic-release-jsr"
|
|
36
44
|
]
|
|
37
45
|
},
|
|
38
46
|
"devDependencies": {
|
|
47
|
+
"@sebbo2002/semantic-release-jsr": "^2.0.2",
|
|
39
48
|
"@tsconfig/node22": "^22.0.0",
|
|
40
49
|
"@types/node": "^22.10.2",
|
|
41
50
|
"@types/yargs": "^17.0.33",
|
|
@@ -46,6 +55,7 @@
|
|
|
46
55
|
"prettier": "^3.4.2",
|
|
47
56
|
"semantic-release": "^24.2.0",
|
|
48
57
|
"tsup": "^8.3.5",
|
|
58
|
+
"typescript": "^5.7.2",
|
|
49
59
|
"vitest": "^2.1.8"
|
|
50
60
|
},
|
|
51
61
|
"tsup": {
|
package/src/FastMCP.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FastMCP, UserError } from "./FastMCP.js";
|
|
1
|
+
import { FastMCP, UserError, imageContent } from "./FastMCP.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { test, expect, vi } from "vitest";
|
|
4
4
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
@@ -163,7 +163,7 @@ test("returns a list", async () => {
|
|
|
163
163
|
a: z.number(),
|
|
164
164
|
b: z.number(),
|
|
165
165
|
}),
|
|
166
|
-
execute: async (
|
|
166
|
+
execute: async () => {
|
|
167
167
|
return {
|
|
168
168
|
content: [
|
|
169
169
|
{ type: "text", text: "a" },
|
|
@@ -194,6 +194,55 @@ test("returns a list", async () => {
|
|
|
194
194
|
});
|
|
195
195
|
});
|
|
196
196
|
|
|
197
|
+
test("returns an image", 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 () => {
|
|
213
|
+
return imageContent({
|
|
214
|
+
buffer: Buffer.from(
|
|
215
|
+
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
|
|
216
|
+
"base64",
|
|
217
|
+
),
|
|
218
|
+
});
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
return server;
|
|
223
|
+
},
|
|
224
|
+
run: async ({ client }) => {
|
|
225
|
+
expect(
|
|
226
|
+
await client.callTool({
|
|
227
|
+
name: "add",
|
|
228
|
+
arguments: {
|
|
229
|
+
a: 1,
|
|
230
|
+
b: 2,
|
|
231
|
+
},
|
|
232
|
+
}),
|
|
233
|
+
).toEqual({
|
|
234
|
+
content: [
|
|
235
|
+
{
|
|
236
|
+
type: "image",
|
|
237
|
+
data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
|
|
238
|
+
mimeType: "image/png",
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
});
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
197
246
|
test("handles UserError errors", async () => {
|
|
198
247
|
await runWithTestServer({
|
|
199
248
|
start: async () => {
|
|
@@ -368,7 +417,7 @@ test("sends logging messages to the client", async () => {
|
|
|
368
417
|
|
|
369
418
|
return server;
|
|
370
419
|
},
|
|
371
|
-
run: async ({ client
|
|
420
|
+
run: async ({ client }) => {
|
|
372
421
|
const onLog = vi.fn();
|
|
373
422
|
|
|
374
423
|
client.setNotificationHandler(
|
package/src/FastMCP.ts
CHANGED
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
ListPromptsRequestSchema,
|
|
9
9
|
ListResourcesRequestSchema,
|
|
10
10
|
ListToolsRequestSchema,
|
|
11
|
-
LoggingLevel,
|
|
12
11
|
McpError,
|
|
13
12
|
ReadResourceRequestSchema,
|
|
14
13
|
ServerCapabilities,
|
|
@@ -17,6 +16,42 @@ import {
|
|
|
17
16
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
18
17
|
import { z } from "zod";
|
|
19
18
|
import http from "http";
|
|
19
|
+
import { readFile } from "fs/promises";
|
|
20
|
+
import { fileTypeFromBuffer } from "file-type";
|
|
21
|
+
|
|
22
|
+
export const imageContent = async (
|
|
23
|
+
input: { url: string } | { path: string } | { buffer: Buffer },
|
|
24
|
+
): Promise<ImageContent> => {
|
|
25
|
+
let rawData: Buffer;
|
|
26
|
+
|
|
27
|
+
if ("url" in input) {
|
|
28
|
+
const response = await fetch(input.url);
|
|
29
|
+
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
throw new Error(`Failed to fetch image from URL: ${response.statusText}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
rawData = Buffer.from(await response.arrayBuffer());
|
|
35
|
+
} else if ("path" in input) {
|
|
36
|
+
rawData = await readFile(input.path);
|
|
37
|
+
} else if ("buffer" in input) {
|
|
38
|
+
rawData = input.buffer;
|
|
39
|
+
} else {
|
|
40
|
+
throw new Error(
|
|
41
|
+
"Invalid input: Provide a valid 'url', 'path', or 'buffer'",
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const mimeType = await fileTypeFromBuffer(rawData);
|
|
46
|
+
|
|
47
|
+
const base64Data = rawData.toString("base64");
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
type: "image",
|
|
51
|
+
data: base64Data,
|
|
52
|
+
mimeType: mimeType?.mime ?? "image/png",
|
|
53
|
+
} as const;
|
|
54
|
+
};
|
|
20
55
|
|
|
21
56
|
abstract class FastMCPError extends Error {
|
|
22
57
|
public constructor(message?: string) {
|
|
@@ -71,6 +106,11 @@ type Context = {
|
|
|
71
106
|
};
|
|
72
107
|
};
|
|
73
108
|
|
|
109
|
+
type TextContent = {
|
|
110
|
+
type: "text";
|
|
111
|
+
text: string;
|
|
112
|
+
};
|
|
113
|
+
|
|
74
114
|
const TextContentZodSchema = z
|
|
75
115
|
.object({
|
|
76
116
|
type: z.literal("text"),
|
|
@@ -79,9 +119,13 @@ const TextContentZodSchema = z
|
|
|
79
119
|
*/
|
|
80
120
|
text: z.string(),
|
|
81
121
|
})
|
|
82
|
-
.strict()
|
|
122
|
+
.strict() satisfies z.ZodType<TextContent>;
|
|
83
123
|
|
|
84
|
-
type
|
|
124
|
+
type ImageContent = {
|
|
125
|
+
type: "image";
|
|
126
|
+
data: string;
|
|
127
|
+
mimeType: string;
|
|
128
|
+
};
|
|
85
129
|
|
|
86
130
|
const ImageContentZodSchema = z
|
|
87
131
|
.object({
|
|
@@ -95,23 +139,26 @@ const ImageContentZodSchema = z
|
|
|
95
139
|
*/
|
|
96
140
|
mimeType: z.string(),
|
|
97
141
|
})
|
|
98
|
-
.strict()
|
|
142
|
+
.strict() satisfies z.ZodType<ImageContent>;
|
|
99
143
|
|
|
100
|
-
type
|
|
144
|
+
type Content = TextContent | ImageContent;
|
|
101
145
|
|
|
102
146
|
const ContentZodSchema = z.discriminatedUnion("type", [
|
|
103
147
|
TextContentZodSchema,
|
|
104
148
|
ImageContentZodSchema,
|
|
105
|
-
])
|
|
149
|
+
]) satisfies z.ZodType<Content>;
|
|
150
|
+
|
|
151
|
+
type ContentResult = {
|
|
152
|
+
content: Content[];
|
|
153
|
+
isError?: boolean;
|
|
154
|
+
};
|
|
106
155
|
|
|
107
156
|
const ContentResultZodSchema = z
|
|
108
157
|
.object({
|
|
109
158
|
content: ContentZodSchema.array(),
|
|
110
159
|
isError: z.boolean().optional(),
|
|
111
160
|
})
|
|
112
|
-
.strict()
|
|
113
|
-
|
|
114
|
-
type ContentResult = z.infer<typeof ContentResultZodSchema>;
|
|
161
|
+
.strict() satisfies z.ZodType<ContentResult>;
|
|
115
162
|
|
|
116
163
|
type Tool<Params extends ToolParameters = ToolParameters> = {
|
|
117
164
|
name: string;
|
|
@@ -120,7 +167,7 @@ type Tool<Params extends ToolParameters = ToolParameters> = {
|
|
|
120
167
|
execute: (
|
|
121
168
|
args: z.infer<Params>,
|
|
122
169
|
context: Context,
|
|
123
|
-
) => Promise<string | ContentResult>;
|
|
170
|
+
) => Promise<string | ContentResult | TextContent | ImageContent>;
|
|
124
171
|
};
|
|
125
172
|
|
|
126
173
|
type Resource = {
|
|
@@ -161,6 +208,16 @@ type ServerOptions = {
|
|
|
161
208
|
version: `${number}.${number}.${number}`;
|
|
162
209
|
};
|
|
163
210
|
|
|
211
|
+
type LoggingLevel =
|
|
212
|
+
| "debug"
|
|
213
|
+
| "info"
|
|
214
|
+
| "notice"
|
|
215
|
+
| "warning"
|
|
216
|
+
| "error"
|
|
217
|
+
| "critical"
|
|
218
|
+
| "alert"
|
|
219
|
+
| "emergency";
|
|
220
|
+
|
|
164
221
|
export class FastMCP {
|
|
165
222
|
#tools: Tool[];
|
|
166
223
|
#resources: Resource[];
|
|
@@ -202,13 +259,14 @@ export class FastMCP {
|
|
|
202
259
|
server.onerror = (error) => {
|
|
203
260
|
console.error("[MCP Error]", error);
|
|
204
261
|
};
|
|
262
|
+
|
|
205
263
|
process.on("SIGINT", async () => {
|
|
206
264
|
await server.close();
|
|
207
265
|
process.exit(0);
|
|
208
266
|
});
|
|
209
267
|
}
|
|
210
268
|
|
|
211
|
-
public get loggingLevel() {
|
|
269
|
+
public get loggingLevel(): LoggingLevel {
|
|
212
270
|
return this.#loggingLevel;
|
|
213
271
|
}
|
|
214
272
|
|
|
@@ -227,118 +285,119 @@ export class FastMCP {
|
|
|
227
285
|
};
|
|
228
286
|
});
|
|
229
287
|
|
|
230
|
-
server.setRequestHandler(
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
288
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
289
|
+
const tool = this.#tools.find(
|
|
290
|
+
(tool) => tool.name === request.params.name,
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
if (!tool) {
|
|
294
|
+
throw new McpError(
|
|
295
|
+
ErrorCode.MethodNotFound,
|
|
296
|
+
`Unknown tool: ${request.params.name}`,
|
|
235
297
|
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
let args: any = undefined;
|
|
301
|
+
|
|
302
|
+
if (tool.parameters) {
|
|
303
|
+
const parsed = tool.parameters.safeParse(request.params.arguments);
|
|
236
304
|
|
|
237
|
-
if (!
|
|
305
|
+
if (!parsed.success) {
|
|
238
306
|
throw new McpError(
|
|
239
|
-
ErrorCode.
|
|
240
|
-
`
|
|
307
|
+
ErrorCode.InvalidRequest,
|
|
308
|
+
`Invalid ${request.params.name} arguments`,
|
|
241
309
|
);
|
|
242
310
|
}
|
|
243
311
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (tool.parameters) {
|
|
247
|
-
const parsed = tool.parameters.safeParse(request.params.arguments);
|
|
248
|
-
|
|
249
|
-
if (!parsed.success) {
|
|
250
|
-
throw new McpError(
|
|
251
|
-
ErrorCode.InvalidRequest,
|
|
252
|
-
`Invalid ${request.params.name} arguments`,
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
args = parsed.data;
|
|
257
|
-
}
|
|
312
|
+
args = parsed.data;
|
|
313
|
+
}
|
|
258
314
|
|
|
259
|
-
|
|
315
|
+
const progressToken = request.params?._meta?.progressToken;
|
|
260
316
|
|
|
261
|
-
|
|
317
|
+
let result: ContentResult;
|
|
262
318
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
319
|
+
try {
|
|
320
|
+
const reportProgress = async (progress: Progress) => {
|
|
321
|
+
await server.notification({
|
|
322
|
+
method: "notifications/progress",
|
|
323
|
+
params: {
|
|
324
|
+
...progress,
|
|
325
|
+
progressToken,
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const log = {
|
|
331
|
+
debug: (message: string, context?: SerializableValue) => {
|
|
332
|
+
server.sendLoggingMessage({
|
|
333
|
+
level: "debug",
|
|
334
|
+
data: {
|
|
335
|
+
message,
|
|
336
|
+
context,
|
|
270
337
|
},
|
|
271
338
|
});
|
|
272
|
-
}
|
|
339
|
+
},
|
|
340
|
+
error: (message: string, context?: SerializableValue) => {
|
|
341
|
+
server.sendLoggingMessage({
|
|
342
|
+
level: "error",
|
|
343
|
+
data: {
|
|
344
|
+
message,
|
|
345
|
+
context,
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
},
|
|
349
|
+
info: (message: string, context?: SerializableValue) => {
|
|
350
|
+
server.sendLoggingMessage({
|
|
351
|
+
level: "info",
|
|
352
|
+
data: {
|
|
353
|
+
message,
|
|
354
|
+
context,
|
|
355
|
+
},
|
|
356
|
+
});
|
|
357
|
+
},
|
|
358
|
+
warn: (message: string, context?: SerializableValue) => {
|
|
359
|
+
server.sendLoggingMessage({
|
|
360
|
+
level: "warning",
|
|
361
|
+
data: {
|
|
362
|
+
message,
|
|
363
|
+
context,
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
},
|
|
367
|
+
};
|
|
273
368
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
};
|
|
369
|
+
const maybeStringResult = await tool.execute(args, {
|
|
370
|
+
reportProgress,
|
|
371
|
+
log,
|
|
372
|
+
});
|
|
312
373
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
374
|
+
if (typeof maybeStringResult === "string") {
|
|
375
|
+
result = ContentResultZodSchema.parse({
|
|
376
|
+
content: [{ type: "text", text: maybeStringResult }],
|
|
316
377
|
});
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
if (error instanceof UserError) {
|
|
327
|
-
return {
|
|
328
|
-
content: [{ type: "text", text: error.message }],
|
|
329
|
-
isError: true,
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
|
|
378
|
+
} else if ("type" in maybeStringResult) {
|
|
379
|
+
result = ContentResultZodSchema.parse({
|
|
380
|
+
content: [maybeStringResult],
|
|
381
|
+
});
|
|
382
|
+
} else {
|
|
383
|
+
result = ContentResultZodSchema.parse(maybeStringResult);
|
|
384
|
+
}
|
|
385
|
+
} catch (error) {
|
|
386
|
+
if (error instanceof UserError) {
|
|
333
387
|
return {
|
|
334
|
-
content: [{ type: "text", text:
|
|
388
|
+
content: [{ type: "text", text: error.message }],
|
|
335
389
|
isError: true,
|
|
336
390
|
};
|
|
337
391
|
}
|
|
338
392
|
|
|
339
|
-
return
|
|
340
|
-
|
|
341
|
-
|
|
393
|
+
return {
|
|
394
|
+
content: [{ type: "text", text: `Error: ${error}` }],
|
|
395
|
+
isError: true,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return result;
|
|
400
|
+
});
|
|
342
401
|
}
|
|
343
402
|
|
|
344
403
|
private setupResourceHandlers(server: Server) {
|