fastmcp 1.3.0 → 1.5.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 +132 -3
- package/dist/FastMCP.d.ts +98 -2
- package/dist/FastMCP.js +142 -86
- package/dist/FastMCP.js.map +1 -1
- package/package.json +4 -2
- package/src/FastMCP.test.ts +102 -31
- package/src/FastMCP.ts +174 -95
- package/tsconfig.json +6 -1
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ server.addTool({
|
|
|
40
40
|
b: z.number(),
|
|
41
41
|
}),
|
|
42
42
|
execute: async (args) => {
|
|
43
|
-
return args.a + args.b;
|
|
43
|
+
return String(args.a + args.b);
|
|
44
44
|
},
|
|
45
45
|
});
|
|
46
46
|
|
|
@@ -105,8 +105,137 @@ server.addTool({
|
|
|
105
105
|
url: z.string(),
|
|
106
106
|
}),
|
|
107
107
|
execute: async (args) => {
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
return await fetchWebpageContent(args.url);
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### Returning a string
|
|
114
|
+
|
|
115
|
+
`execute` can return a string:
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
server.addTool({
|
|
119
|
+
name: "download",
|
|
120
|
+
description: "Download a file",
|
|
121
|
+
parameters: z.object({
|
|
122
|
+
url: z.string(),
|
|
123
|
+
}),
|
|
124
|
+
execute: async (args) => {
|
|
125
|
+
return "Hello, world!";
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The latter is equivalent to:
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
server.addTool({
|
|
134
|
+
name: "download",
|
|
135
|
+
description: "Download a file",
|
|
136
|
+
parameters: z.object({
|
|
137
|
+
url: z.string(),
|
|
138
|
+
}),
|
|
139
|
+
execute: async (args) => {
|
|
140
|
+
return {
|
|
141
|
+
content: [
|
|
142
|
+
{
|
|
143
|
+
type: "text",
|
|
144
|
+
text: "Hello, world!",
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
};
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
#### Returning a list
|
|
153
|
+
|
|
154
|
+
If you want to return a list of messages, you can return an object with a `content` property:
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
server.addTool({
|
|
158
|
+
name: "download",
|
|
159
|
+
description: "Download a file",
|
|
160
|
+
parameters: z.object({
|
|
161
|
+
url: z.string(),
|
|
162
|
+
}),
|
|
163
|
+
execute: async (args) => {
|
|
164
|
+
return {
|
|
165
|
+
content: [
|
|
166
|
+
{ type: "text", text: "First message" },
|
|
167
|
+
{ type: "text", text: "Second message" },
|
|
168
|
+
],
|
|
169
|
+
};
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
#### Returning an image
|
|
175
|
+
|
|
176
|
+
Use the `imageContent` to create a content object for an image:
|
|
177
|
+
|
|
178
|
+
```js
|
|
179
|
+
import { imageContent } from "fastmcp";
|
|
180
|
+
|
|
181
|
+
server.addTool({
|
|
182
|
+
name: "download",
|
|
183
|
+
description: "Download a file",
|
|
184
|
+
parameters: z.object({
|
|
185
|
+
url: z.string(),
|
|
186
|
+
}),
|
|
187
|
+
execute: async (args) => {
|
|
188
|
+
return imageContent({
|
|
189
|
+
url: "https://example.com/image.png",
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// or...
|
|
193
|
+
// return imageContent({
|
|
194
|
+
// path: "/path/to/image.png",
|
|
195
|
+
// });
|
|
196
|
+
|
|
197
|
+
// or...
|
|
198
|
+
// return imageContent({
|
|
199
|
+
// buffer: Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", "base64"),
|
|
200
|
+
// });
|
|
201
|
+
|
|
202
|
+
// or...
|
|
203
|
+
// return {
|
|
204
|
+
// content: [
|
|
205
|
+
// imageContent()
|
|
206
|
+
// ],
|
|
207
|
+
// };
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The `imageContent` function takes the following options:
|
|
213
|
+
|
|
214
|
+
- `url`: The URL of the image.
|
|
215
|
+
- `path`: The path to the image file.
|
|
216
|
+
- `buffer`: The image data as a buffer.
|
|
217
|
+
|
|
218
|
+
Only one of `url`, `path`, or `buffer` must be specified.
|
|
219
|
+
|
|
220
|
+
The above example is equivalent to:
|
|
221
|
+
|
|
222
|
+
```js
|
|
223
|
+
server.addTool({
|
|
224
|
+
name: "download",
|
|
225
|
+
description: "Download a file",
|
|
226
|
+
parameters: z.object({
|
|
227
|
+
url: z.string(),
|
|
228
|
+
}),
|
|
229
|
+
execute: async (args) => {
|
|
230
|
+
return {
|
|
231
|
+
content: [
|
|
232
|
+
{
|
|
233
|
+
type: "image",
|
|
234
|
+
data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
|
|
235
|
+
mimeType: "image/png",
|
|
236
|
+
},
|
|
237
|
+
],
|
|
238
|
+
};
|
|
110
239
|
},
|
|
111
240
|
});
|
|
112
241
|
```
|
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,11 +42,100 @@ type Context = {
|
|
|
35
42
|
warn: (message: string, data?: SerializableValue) => void;
|
|
36
43
|
};
|
|
37
44
|
};
|
|
45
|
+
declare const TextContentZodSchema: z.ZodObject<{
|
|
46
|
+
type: z.ZodLiteral<"text">;
|
|
47
|
+
/**
|
|
48
|
+
* The text content of the message.
|
|
49
|
+
*/
|
|
50
|
+
text: z.ZodString;
|
|
51
|
+
}, "strict", z.ZodTypeAny, {
|
|
52
|
+
type: "text";
|
|
53
|
+
text: string;
|
|
54
|
+
}, {
|
|
55
|
+
type: "text";
|
|
56
|
+
text: string;
|
|
57
|
+
}>;
|
|
58
|
+
type TextContent = z.infer<typeof TextContentZodSchema>;
|
|
59
|
+
declare const ImageContentZodSchema: z.ZodObject<{
|
|
60
|
+
type: z.ZodLiteral<"image">;
|
|
61
|
+
/**
|
|
62
|
+
* The base64-encoded image data.
|
|
63
|
+
*/
|
|
64
|
+
data: z.ZodString;
|
|
65
|
+
/**
|
|
66
|
+
* The MIME type of the image. Different providers may support different image types.
|
|
67
|
+
*/
|
|
68
|
+
mimeType: z.ZodString;
|
|
69
|
+
}, "strict", z.ZodTypeAny, {
|
|
70
|
+
type: "image";
|
|
71
|
+
data: string;
|
|
72
|
+
mimeType: string;
|
|
73
|
+
}, {
|
|
74
|
+
type: "image";
|
|
75
|
+
data: string;
|
|
76
|
+
mimeType: string;
|
|
77
|
+
}>;
|
|
78
|
+
type ImageContent = z.infer<typeof ImageContentZodSchema>;
|
|
79
|
+
declare const ContentResultZodSchema: z.ZodObject<{
|
|
80
|
+
content: z.ZodArray<z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
81
|
+
type: z.ZodLiteral<"text">;
|
|
82
|
+
/**
|
|
83
|
+
* The text content of the message.
|
|
84
|
+
*/
|
|
85
|
+
text: z.ZodString;
|
|
86
|
+
}, "strict", z.ZodTypeAny, {
|
|
87
|
+
type: "text";
|
|
88
|
+
text: string;
|
|
89
|
+
}, {
|
|
90
|
+
type: "text";
|
|
91
|
+
text: string;
|
|
92
|
+
}>, z.ZodObject<{
|
|
93
|
+
type: z.ZodLiteral<"image">;
|
|
94
|
+
/**
|
|
95
|
+
* The base64-encoded image data.
|
|
96
|
+
*/
|
|
97
|
+
data: z.ZodString;
|
|
98
|
+
/**
|
|
99
|
+
* The MIME type of the image. Different providers may support different image types.
|
|
100
|
+
*/
|
|
101
|
+
mimeType: z.ZodString;
|
|
102
|
+
}, "strict", z.ZodTypeAny, {
|
|
103
|
+
type: "image";
|
|
104
|
+
data: string;
|
|
105
|
+
mimeType: string;
|
|
106
|
+
}, {
|
|
107
|
+
type: "image";
|
|
108
|
+
data: string;
|
|
109
|
+
mimeType: string;
|
|
110
|
+
}>]>, "many">;
|
|
111
|
+
isError: z.ZodOptional<z.ZodBoolean>;
|
|
112
|
+
}, "strict", z.ZodTypeAny, {
|
|
113
|
+
content: ({
|
|
114
|
+
type: "image";
|
|
115
|
+
data: string;
|
|
116
|
+
mimeType: string;
|
|
117
|
+
} | {
|
|
118
|
+
type: "text";
|
|
119
|
+
text: string;
|
|
120
|
+
})[];
|
|
121
|
+
isError?: boolean | undefined;
|
|
122
|
+
}, {
|
|
123
|
+
content: ({
|
|
124
|
+
type: "image";
|
|
125
|
+
data: string;
|
|
126
|
+
mimeType: string;
|
|
127
|
+
} | {
|
|
128
|
+
type: "text";
|
|
129
|
+
text: string;
|
|
130
|
+
})[];
|
|
131
|
+
isError?: boolean | undefined;
|
|
132
|
+
}>;
|
|
133
|
+
type ContentResult = z.infer<typeof ContentResultZodSchema>;
|
|
38
134
|
type Tool<Params extends ToolParameters = ToolParameters> = {
|
|
39
135
|
name: string;
|
|
40
136
|
description?: string;
|
|
41
137
|
parameters?: Params;
|
|
42
|
-
execute: (args: z.infer<Params>, context: Context) => Promise<
|
|
138
|
+
execute: (args: z.infer<Params>, context: Context) => Promise<string | ContentResult | TextContent | ImageContent>;
|
|
43
139
|
};
|
|
44
140
|
type Resource = {
|
|
45
141
|
uri: string;
|
|
@@ -101,4 +197,4 @@ declare class FastMCP {
|
|
|
101
197
|
stop(): Promise<void>;
|
|
102
198
|
}
|
|
103
199
|
|
|
104
|
-
export { FastMCP, UserError };
|
|
200
|
+
export { FastMCP, UserError, imageContent };
|
package/dist/FastMCP.js
CHANGED
|
@@ -14,7 +14,35 @@ import {
|
|
|
14
14
|
SetLevelRequestSchema
|
|
15
15
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
16
16
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
17
|
+
import { z } from "zod";
|
|
17
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
|
+
};
|
|
18
46
|
var FastMCPError = class extends Error {
|
|
19
47
|
constructor(message) {
|
|
20
48
|
super(message);
|
|
@@ -31,6 +59,32 @@ var UnexpectedStateError = class extends FastMCPError {
|
|
|
31
59
|
};
|
|
32
60
|
var UserError = class extends UnexpectedStateError {
|
|
33
61
|
};
|
|
62
|
+
var TextContentZodSchema = z.object({
|
|
63
|
+
type: z.literal("text"),
|
|
64
|
+
/**
|
|
65
|
+
* The text content of the message.
|
|
66
|
+
*/
|
|
67
|
+
text: z.string()
|
|
68
|
+
}).strict();
|
|
69
|
+
var ImageContentZodSchema = z.object({
|
|
70
|
+
type: z.literal("image"),
|
|
71
|
+
/**
|
|
72
|
+
* The base64-encoded image data.
|
|
73
|
+
*/
|
|
74
|
+
data: z.string().base64(),
|
|
75
|
+
/**
|
|
76
|
+
* The MIME type of the image. Different providers may support different image types.
|
|
77
|
+
*/
|
|
78
|
+
mimeType: z.string()
|
|
79
|
+
}).strict();
|
|
80
|
+
var ContentZodSchema = z.discriminatedUnion("type", [
|
|
81
|
+
TextContentZodSchema,
|
|
82
|
+
ImageContentZodSchema
|
|
83
|
+
]);
|
|
84
|
+
var ContentResultZodSchema = z.object({
|
|
85
|
+
content: ContentZodSchema.array(),
|
|
86
|
+
isError: z.boolean().optional()
|
|
87
|
+
}).strict();
|
|
34
88
|
var FastMCP = class {
|
|
35
89
|
constructor(options) {
|
|
36
90
|
this.options = options;
|
|
@@ -85,105 +139,106 @@ var FastMCP = class {
|
|
|
85
139
|
})
|
|
86
140
|
};
|
|
87
141
|
});
|
|
88
|
-
server.setRequestHandler(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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}`
|
|
93
150
|
);
|
|
94
|
-
|
|
151
|
+
}
|
|
152
|
+
let args = void 0;
|
|
153
|
+
if (tool.parameters) {
|
|
154
|
+
const parsed = tool.parameters.safeParse(request.params.arguments);
|
|
155
|
+
if (!parsed.success) {
|
|
95
156
|
throw new McpError(
|
|
96
|
-
ErrorCode.
|
|
97
|
-
`
|
|
157
|
+
ErrorCode.InvalidRequest,
|
|
158
|
+
`Invalid ${request.params.name} arguments`
|
|
98
159
|
);
|
|
99
160
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const progressToken = request.params?._meta?.progressToken;
|
|
112
|
-
let result;
|
|
113
|
-
try {
|
|
114
|
-
const reportProgress = async (progress) => {
|
|
115
|
-
await server.notification({
|
|
116
|
-
method: "notifications/progress",
|
|
117
|
-
params: {
|
|
118
|
-
...progress,
|
|
119
|
-
progressToken
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
};
|
|
123
|
-
const log = {
|
|
124
|
-
debug: (message, context) => {
|
|
125
|
-
server.sendLoggingMessage({
|
|
126
|
-
level: "debug",
|
|
127
|
-
data: {
|
|
128
|
-
message,
|
|
129
|
-
context
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
},
|
|
133
|
-
error: (message, context) => {
|
|
134
|
-
server.sendLoggingMessage({
|
|
135
|
-
level: "error",
|
|
136
|
-
data: {
|
|
137
|
-
message,
|
|
138
|
-
context
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
},
|
|
142
|
-
info: (message, context) => {
|
|
143
|
-
server.sendLoggingMessage({
|
|
144
|
-
level: "info",
|
|
145
|
-
data: {
|
|
146
|
-
message,
|
|
147
|
-
context
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
},
|
|
151
|
-
warn: (message, context) => {
|
|
152
|
-
server.sendLoggingMessage({
|
|
153
|
-
level: "warning",
|
|
154
|
-
data: {
|
|
155
|
-
message,
|
|
156
|
-
context
|
|
157
|
-
}
|
|
158
|
-
});
|
|
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
|
|
159
172
|
}
|
|
160
|
-
};
|
|
161
|
-
result = await tool.execute(args, {
|
|
162
|
-
reportProgress,
|
|
163
|
-
log
|
|
164
173
|
});
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
+
}
|
|
210
|
+
});
|
|
171
211
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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);
|
|
176
227
|
}
|
|
177
|
-
|
|
228
|
+
} catch (error) {
|
|
229
|
+
if (error instanceof UserError) {
|
|
178
230
|
return {
|
|
179
|
-
content: [{ type: "text", text:
|
|
231
|
+
content: [{ type: "text", text: error.message }],
|
|
232
|
+
isError: true
|
|
180
233
|
};
|
|
181
234
|
}
|
|
182
235
|
return {
|
|
183
|
-
content: [{ type: "text", text:
|
|
236
|
+
content: [{ type: "text", text: `Error: ${error}` }],
|
|
237
|
+
isError: true
|
|
184
238
|
};
|
|
185
239
|
}
|
|
186
|
-
|
|
240
|
+
return result;
|
|
241
|
+
});
|
|
187
242
|
}
|
|
188
243
|
setupResourceHandlers(server) {
|
|
189
244
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
@@ -400,6 +455,7 @@ var FastMCP = class {
|
|
|
400
455
|
};
|
|
401
456
|
export {
|
|
402
457
|
FastMCP,
|
|
403
|
-
UserError
|
|
458
|
+
UserError,
|
|
459
|
+
imageContent
|
|
404
460
|
};
|
|
405
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 LoggingLevelSchema,\n LoggingMessageNotificationSchema,\n McpError,\n NotificationSchema,\n ReadResourceRequestSchema,\n ServerCapabilities,\n SetLevelRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { zodToJsonSchema } from \"zod-to-json-schema\";\nimport type { z } from \"zod\";\nimport http from \"http\";\n\nabstract class FastMCPError extends Error {\n public constructor(message?: string) {\n super(message);\n this.name = new.target.name;\n }\n}\n\ntype Extra = unknown;\n\ntype Extras = Record<string, Extra>;\n\nclass UnexpectedStateError extends FastMCPError {\n public extras?: Extras;\n\n public constructor(message: string, extras?: Extras) {\n super(message);\n this.name = new.target.name;\n this.extras = extras;\n }\n}\n\nexport class UserError extends UnexpectedStateError {}\n\ntype ToolParameters = z.ZodTypeAny;\n\ntype Literal = boolean | null | number | string | undefined;\n\ntype SerializableValue =\n | Literal\n | SerializableValue[]\n | { [key: string]: SerializableValue };\n\ntype Progress = {\n /**\n * The progress thus far. This should increase every time progress is made, even if the total is unknown.\n */\n progress: number;\n /**\n * Total number of items to process (or total progress required), if known.\n */\n total?: number;\n};\n\ntype Context = {\n reportProgress: (progress: Progress) => Promise<void>;\n log: {\n debug: (message: string, data?: SerializableValue) => void;\n error: (message: string, data?: SerializableValue) => void;\n info: (message: string, data?: SerializableValue) => void;\n warn: (message: string, data?: SerializableValue) => void;\n };\n};\n\ntype Tool<Params extends ToolParameters = ToolParameters> = {\n name: string;\n description?: string;\n parameters?: Params;\n execute: (args: z.infer<Params>, context: Context) => Promise<unknown>;\n};\n\ntype Resource = {\n uri: string;\n name: string;\n description?: string;\n mimeType?: string;\n load: () => Promise<{ text: string } | { blob: string }>;\n};\n\ntype PromptArgument = Readonly<{\n name: string;\n description?: string;\n required?: boolean;\n}>;\n\ntype ArgumentsToObject<T extends PromptArgument[]> = {\n [K in T[number][\"name\"]]: Extract<\n T[number],\n { name: K }\n >[\"required\"] extends true\n ? string\n : string | undefined;\n};\n\ntype Prompt<\n Arguments extends PromptArgument[] = PromptArgument[],\n Args = ArgumentsToObject<Arguments>,\n> = {\n name: string;\n description?: string;\n arguments?: Arguments;\n load: (args: Args) => Promise<string>;\n};\n\ntype ServerOptions = {\n name: string;\n version: `${number}.${number}.${number}`;\n};\n\nexport class FastMCP {\n #tools: Tool[];\n #resources: Resource[];\n #prompts: Prompt[];\n #server: Server | null = null;\n #options: ServerOptions;\n #loggingLevel: LoggingLevel = \"info\";\n\n constructor(public options: ServerOptions) {\n this.#options = options;\n this.#tools = [];\n this.#resources = [];\n this.#prompts = [];\n }\n\n private setupHandlers(server: Server) {\n this.setupErrorHandling(server);\n\n if (this.#tools.length) {\n this.setupToolHandlers(server);\n }\n\n if (this.#resources.length) {\n this.setupResourceHandlers(server);\n }\n\n if (this.#prompts.length) {\n this.setupPromptHandlers(server);\n }\n\n server.setRequestHandler(SetLevelRequestSchema, (request) => {\n this.#loggingLevel = request.params.level;\n\n return {};\n });\n }\n\n private setupErrorHandling(server: Server) {\n server.onerror = (error) => {\n console.error(\"[MCP Error]\", error);\n };\n process.on(\"SIGINT\", async () => {\n await server.close();\n process.exit(0);\n });\n }\n\n public get loggingLevel() {\n return this.#loggingLevel;\n }\n\n private setupToolHandlers(server: Server) {\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n return {\n tools: this.#tools.map((tool) => {\n return {\n name: tool.name,\n description: tool.description,\n inputSchema: tool.parameters\n ? zodToJsonSchema(tool.parameters)\n : undefined,\n };\n }),\n };\n });\n\n server.setRequestHandler(\n CallToolRequestSchema,\n async (request, ...extra) => {\n const tool = this.#tools.find(\n (tool) => tool.name === request.params.name,\n );\n\n if (!tool) {\n throw new McpError(\n ErrorCode.MethodNotFound,\n `Unknown tool: ${request.params.name}`,\n );\n }\n\n let args: any = undefined;\n\n if (tool.parameters) {\n const parsed = tool.parameters.safeParse(request.params.arguments);\n\n if (!parsed.success) {\n throw new McpError(\n ErrorCode.InvalidRequest,\n `Invalid ${request.params.name} arguments`,\n );\n }\n\n args = parsed.data;\n }\n\n const progressToken = request.params?._meta?.progressToken;\n\n let result: any;\n\n try {\n const reportProgress = async (progress: Progress) => {\n await server.notification({\n method: \"notifications/progress\",\n params: {\n ...progress,\n progressToken,\n },\n });\n };\n\n const log = {\n debug: (message: string, context?: SerializableValue) => {\n server.sendLoggingMessage({\n level: \"debug\",\n data: {\n message,\n context,\n },\n });\n },\n error: (message: string, context?: SerializableValue) => {\n server.sendLoggingMessage({\n level: \"error\",\n data: {\n message,\n context,\n },\n });\n },\n info: (message: string, context?: SerializableValue) => {\n server.sendLoggingMessage({\n level: \"info\",\n data: {\n message,\n context,\n },\n });\n },\n warn: (message: string, context?: SerializableValue) => {\n server.sendLoggingMessage({\n level: \"warning\",\n data: {\n message,\n context,\n },\n });\n },\n };\n\n result = await tool.execute(args, {\n reportProgress,\n log,\n });\n } catch (error) {\n if (error instanceof UserError) {\n return {\n content: [{ type: \"text\", text: error.message }],\n isError: true,\n };\n }\n\n return {\n content: [{ type: \"text\", text: `Error: ${error}` }],\n isError: true,\n };\n }\n\n if (typeof result === \"string\") {\n return {\n content: [{ type: \"text\", text: result }],\n };\n }\n\n return {\n content: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n };\n },\n );\n }\n\n private setupResourceHandlers(server: Server) {\n server.setRequestHandler(ListResourcesRequestSchema, async () => {\n return {\n resources: this.#resources.map((resource) => {\n return {\n uri: resource.uri,\n name: resource.name,\n mimeType: resource.mimeType,\n };\n }),\n };\n });\n\n server.setRequestHandler(ReadResourceRequestSchema, async (request) => {\n const resource = this.#resources.find(\n (resource) => resource.uri === request.params.uri,\n );\n\n if (!resource) {\n throw new McpError(\n ErrorCode.MethodNotFound,\n `Unknown resource: ${request.params.uri}`,\n );\n }\n\n let result: Awaited<ReturnType<Resource[\"load\"]>>;\n\n try {\n result = await resource.load();\n } catch (error) {\n throw new McpError(\n ErrorCode.InternalError,\n `Error reading resource: ${error}`,\n {\n uri: resource.uri,\n },\n );\n }\n\n return {\n contents: [\n {\n uri: resource.uri,\n mimeType: resource.mimeType,\n ...result,\n },\n ],\n };\n });\n }\n\n private setupPromptHandlers(server: Server) {\n server.setRequestHandler(ListPromptsRequestSchema, async () => {\n return {\n prompts: this.#prompts.map((prompt) => {\n return {\n name: prompt.name,\n description: prompt.description,\n arguments: prompt.arguments,\n };\n }),\n };\n });\n\n server.setRequestHandler(GetPromptRequestSchema, async (request) => {\n const prompt = this.#prompts.find(\n (prompt) => prompt.name === request.params.name,\n );\n\n if (!prompt) {\n throw new McpError(\n ErrorCode.MethodNotFound,\n `Unknown prompt: ${request.params.name}`,\n );\n }\n\n const args = request.params.arguments;\n\n if (prompt.arguments) {\n for (const arg of prompt.arguments) {\n if (arg.required && !(args && arg.name in args)) {\n throw new McpError(\n ErrorCode.InvalidRequest,\n `Missing required argument: ${arg.name}`,\n );\n }\n }\n }\n\n let result: Awaited<ReturnType<Prompt[\"load\"]>>;\n\n try {\n result = await prompt.load(args as Record<string, string | undefined>);\n } catch (error) {\n throw new McpError(\n ErrorCode.InternalError,\n `Error loading prompt: ${error}`,\n );\n }\n\n return {\n description: prompt.description,\n messages: [\n {\n role: \"user\",\n content: { type: \"text\", text: result },\n },\n ],\n };\n });\n }\n\n public addTool<Params extends ToolParameters>(tool: Tool<Params>) {\n this.#tools.push(tool as unknown as Tool);\n }\n\n public addResource(resource: Resource) {\n this.#resources.push(resource);\n }\n\n public addPrompt<const Args extends PromptArgument[]>(prompt: Prompt<Args>) {\n this.#prompts.push(prompt);\n }\n\n #httpServer: http.Server | null = null;\n\n public async start(\n options:\n | { transportType: \"stdio\" }\n | {\n transportType: \"sse\";\n sse: { endpoint: `/${string}`; port: number };\n } = {\n transportType: \"stdio\",\n },\n ) {\n const capabilities: ServerCapabilities = {};\n\n if (this.#tools.length) {\n capabilities.tools = {};\n }\n\n if (this.#resources.length) {\n capabilities.resources = {};\n }\n\n if (this.#prompts.length) {\n capabilities.prompts = {};\n }\n\n capabilities.logging = {};\n\n this.#server = new Server(\n { name: this.#options.name, version: this.#options.version },\n { capabilities },\n );\n\n this.setupHandlers(this.#server);\n\n if (options.transportType === \"stdio\") {\n const transport = new StdioServerTransport();\n\n await this.#server.connect(transport);\n\n console.error(`server is running on stdio`);\n } else if (options.transportType === \"sse\") {\n let activeTransport: SSEServerTransport | null = null;\n\n /**\n * Adopted from https://dev.classmethod.jp/articles/mcp-sse/\n */\n this.#httpServer = http.createServer(async (req, res) => {\n if (req.method === \"GET\" && req.url === options.sse.endpoint) {\n const transport = new SSEServerTransport(\"/messages\", res);\n\n activeTransport = transport;\n\n if (!this.#server) {\n throw new Error(\"Server not initialized\");\n }\n\n await this.#server.connect(transport);\n\n res.on(\"close\", () => {\n console.log(\"SSE connection closed\");\n if (activeTransport === transport) {\n activeTransport = null;\n }\n });\n\n this.startSending(transport);\n return;\n }\n\n if (req.method === \"POST\" && req.url?.startsWith(\"/messages\")) {\n if (!activeTransport) {\n res.writeHead(400).end(\"No active transport\");\n return;\n }\n await activeTransport.handlePostMessage(req, res);\n return;\n }\n\n res.writeHead(404).end();\n });\n\n this.#httpServer.listen(options.sse.port, \"0.0.0.0\");\n\n console.error(\n `server is running on SSE at http://localhost:${options.sse.port}${options.sse.endpoint}`,\n );\n } else {\n throw new Error(\"Invalid transport type\");\n }\n }\n\n /**\n * @see https://dev.classmethod.jp/articles/mcp-sse/\n */\n private async startSending(transport: SSEServerTransport) {\n try {\n await transport.send({\n jsonrpc: \"2.0\",\n method: \"sse/connection\",\n params: { message: \"SSE Connection established\" },\n });\n\n let messageCount = 0;\n const interval = setInterval(async () => {\n messageCount++;\n\n const message = `Message ${messageCount} at ${new Date().toISOString()}`;\n\n try {\n await transport.send({\n jsonrpc: \"2.0\",\n method: \"sse/message\",\n params: { data: message },\n });\n\n console.log(`Sent: ${message}`);\n\n if (messageCount === 10) {\n clearInterval(interval);\n\n await transport.send({\n jsonrpc: \"2.0\",\n method: \"sse/complete\",\n params: { message: \"Stream completed\" },\n });\n console.log(\"Stream completed\");\n }\n } catch (error) {\n console.error(\"Error sending message:\", error);\n clearInterval(interval);\n }\n }, 1000);\n } catch (error) {\n console.error(\"Error in startSending:\", error);\n }\n }\n\n public async stop() {\n if (this.#httpServer) {\n this.#httpServer.close();\n }\n }\n}\n"],"mappings":"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","names":["tool","resource","prompt"]}
|
|
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\";\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\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 | 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\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(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/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastmcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
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"
|
|
@@ -46,6 +47,7 @@
|
|
|
46
47
|
"prettier": "^3.4.2",
|
|
47
48
|
"semantic-release": "^24.2.0",
|
|
48
49
|
"tsup": "^8.3.5",
|
|
50
|
+
"typescript": "^5.7.2",
|
|
49
51
|
"vitest": "^2.1.8"
|
|
50
52
|
},
|
|
51
53
|
"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";
|
|
@@ -8,7 +8,6 @@ import { EventSource } from "eventsource";
|
|
|
8
8
|
import { setTimeout as delay } from "timers/promises";
|
|
9
9
|
import {
|
|
10
10
|
ErrorCode,
|
|
11
|
-
JSONRPCMessage,
|
|
12
11
|
LoggingMessageNotificationSchema,
|
|
13
12
|
McpError,
|
|
14
13
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
@@ -82,7 +81,7 @@ test("adds tools", async () => {
|
|
|
82
81
|
b: z.number(),
|
|
83
82
|
}),
|
|
84
83
|
execute: async (args) => {
|
|
85
|
-
return args.a + args.b;
|
|
84
|
+
return String(args.a + args.b);
|
|
86
85
|
},
|
|
87
86
|
});
|
|
88
87
|
|
|
@@ -127,7 +126,7 @@ test("calls a tool", async () => {
|
|
|
127
126
|
b: z.number(),
|
|
128
127
|
}),
|
|
129
128
|
execute: async (args) => {
|
|
130
|
-
return args.a + args.b;
|
|
129
|
+
return String(args.a + args.b);
|
|
131
130
|
},
|
|
132
131
|
});
|
|
133
132
|
|
|
@@ -149,6 +148,101 @@ test("calls a tool", async () => {
|
|
|
149
148
|
});
|
|
150
149
|
});
|
|
151
150
|
|
|
151
|
+
test("returns a list", async () => {
|
|
152
|
+
await runWithTestServer({
|
|
153
|
+
start: async () => {
|
|
154
|
+
const server = new FastMCP({
|
|
155
|
+
name: "Test",
|
|
156
|
+
version: "1.0.0",
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
server.addTool({
|
|
160
|
+
name: "add",
|
|
161
|
+
description: "Add two numbers",
|
|
162
|
+
parameters: z.object({
|
|
163
|
+
a: z.number(),
|
|
164
|
+
b: z.number(),
|
|
165
|
+
}),
|
|
166
|
+
execute: async () => {
|
|
167
|
+
return {
|
|
168
|
+
content: [
|
|
169
|
+
{ type: "text", text: "a" },
|
|
170
|
+
{ type: "text", text: "b" },
|
|
171
|
+
],
|
|
172
|
+
};
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
return server;
|
|
177
|
+
},
|
|
178
|
+
run: async ({ client }) => {
|
|
179
|
+
expect(
|
|
180
|
+
await client.callTool({
|
|
181
|
+
name: "add",
|
|
182
|
+
arguments: {
|
|
183
|
+
a: 1,
|
|
184
|
+
b: 2,
|
|
185
|
+
},
|
|
186
|
+
}),
|
|
187
|
+
).toEqual({
|
|
188
|
+
content: [
|
|
189
|
+
{ type: "text", text: "a" },
|
|
190
|
+
{ type: "text", text: "b" },
|
|
191
|
+
],
|
|
192
|
+
});
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("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
|
+
|
|
152
246
|
test("handles UserError errors", async () => {
|
|
153
247
|
await runWithTestServer({
|
|
154
248
|
start: async () => {
|
|
@@ -164,7 +258,7 @@ test("handles UserError errors", async () => {
|
|
|
164
258
|
a: z.number(),
|
|
165
259
|
b: z.number(),
|
|
166
260
|
}),
|
|
167
|
-
execute: async (
|
|
261
|
+
execute: async () => {
|
|
168
262
|
throw new UserError("Something went wrong");
|
|
169
263
|
},
|
|
170
264
|
});
|
|
@@ -240,7 +334,7 @@ test("tracks tool progress", async () => {
|
|
|
240
334
|
|
|
241
335
|
await delay(100);
|
|
242
336
|
|
|
243
|
-
return args.a + args.b;
|
|
337
|
+
return String(args.a + args.b);
|
|
244
338
|
},
|
|
245
339
|
});
|
|
246
340
|
|
|
@@ -294,29 +388,6 @@ test("sets logging levels", async () => {
|
|
|
294
388
|
});
|
|
295
389
|
});
|
|
296
390
|
|
|
297
|
-
const onMessage = (
|
|
298
|
-
client: Client,
|
|
299
|
-
callback: (message: JSONRPCMessage) => void,
|
|
300
|
-
) => {
|
|
301
|
-
if (!client.transport) {
|
|
302
|
-
throw new Error("Transport not set");
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const onmessage = client.transport.onmessage;
|
|
306
|
-
|
|
307
|
-
if (!onmessage) {
|
|
308
|
-
throw new Error("onmessage not set");
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
client.transport.onmessage = (message) => {
|
|
312
|
-
console.log("message", message);
|
|
313
|
-
|
|
314
|
-
onmessage(message);
|
|
315
|
-
|
|
316
|
-
callback(message);
|
|
317
|
-
};
|
|
318
|
-
};
|
|
319
|
-
|
|
320
391
|
test("sends logging messages to the client", async () => {
|
|
321
392
|
await runWithTestServer({
|
|
322
393
|
start: async () => {
|
|
@@ -340,13 +411,13 @@ test("sends logging messages to the client", async () => {
|
|
|
340
411
|
log.info("info message");
|
|
341
412
|
log.warn("warn message");
|
|
342
413
|
|
|
343
|
-
return args.a + args.b;
|
|
414
|
+
return String(args.a + args.b);
|
|
344
415
|
},
|
|
345
416
|
});
|
|
346
417
|
|
|
347
418
|
return server;
|
|
348
419
|
},
|
|
349
|
-
run: async ({ client
|
|
420
|
+
run: async ({ client }) => {
|
|
350
421
|
const onLog = vi.fn();
|
|
351
422
|
|
|
352
423
|
client.setNotificationHandler(
|
package/src/FastMCP.ts
CHANGED
|
@@ -9,17 +9,50 @@ import {
|
|
|
9
9
|
ListResourcesRequestSchema,
|
|
10
10
|
ListToolsRequestSchema,
|
|
11
11
|
LoggingLevel,
|
|
12
|
-
LoggingLevelSchema,
|
|
13
|
-
LoggingMessageNotificationSchema,
|
|
14
12
|
McpError,
|
|
15
|
-
NotificationSchema,
|
|
16
13
|
ReadResourceRequestSchema,
|
|
17
14
|
ServerCapabilities,
|
|
18
15
|
SetLevelRequestSchema,
|
|
19
16
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
20
17
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
21
|
-
import
|
|
18
|
+
import { z } from "zod";
|
|
22
19
|
import http from "http";
|
|
20
|
+
import { readFile } from "fs/promises";
|
|
21
|
+
import { fileTypeFromBuffer } from "file-type";
|
|
22
|
+
|
|
23
|
+
export const imageContent = async (
|
|
24
|
+
input: { url: string } | { path: string } | { buffer: Buffer },
|
|
25
|
+
): Promise<ImageContent> => {
|
|
26
|
+
let rawData: Buffer;
|
|
27
|
+
|
|
28
|
+
if ("url" in input) {
|
|
29
|
+
const response = await fetch(input.url);
|
|
30
|
+
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
throw new Error(`Failed to fetch image from URL: ${response.statusText}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
rawData = Buffer.from(await response.arrayBuffer());
|
|
36
|
+
} else if ("path" in input) {
|
|
37
|
+
rawData = await readFile(input.path);
|
|
38
|
+
} else if ("buffer" in input) {
|
|
39
|
+
rawData = input.buffer;
|
|
40
|
+
} else {
|
|
41
|
+
throw new Error(
|
|
42
|
+
"Invalid input: Provide a valid 'url', 'path', or 'buffer'",
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const mimeType = await fileTypeFromBuffer(rawData);
|
|
47
|
+
|
|
48
|
+
const base64Data = rawData.toString("base64");
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
type: "image",
|
|
52
|
+
data: base64Data,
|
|
53
|
+
mimeType: mimeType?.mime ?? "image/png",
|
|
54
|
+
} as const;
|
|
55
|
+
};
|
|
23
56
|
|
|
24
57
|
abstract class FastMCPError extends Error {
|
|
25
58
|
public constructor(message?: string) {
|
|
@@ -74,11 +107,56 @@ type Context = {
|
|
|
74
107
|
};
|
|
75
108
|
};
|
|
76
109
|
|
|
110
|
+
const TextContentZodSchema = z
|
|
111
|
+
.object({
|
|
112
|
+
type: z.literal("text"),
|
|
113
|
+
/**
|
|
114
|
+
* The text content of the message.
|
|
115
|
+
*/
|
|
116
|
+
text: z.string(),
|
|
117
|
+
})
|
|
118
|
+
.strict();
|
|
119
|
+
|
|
120
|
+
type TextContent = z.infer<typeof TextContentZodSchema>;
|
|
121
|
+
|
|
122
|
+
const ImageContentZodSchema = z
|
|
123
|
+
.object({
|
|
124
|
+
type: z.literal("image"),
|
|
125
|
+
/**
|
|
126
|
+
* The base64-encoded image data.
|
|
127
|
+
*/
|
|
128
|
+
data: z.string().base64(),
|
|
129
|
+
/**
|
|
130
|
+
* The MIME type of the image. Different providers may support different image types.
|
|
131
|
+
*/
|
|
132
|
+
mimeType: z.string(),
|
|
133
|
+
})
|
|
134
|
+
.strict();
|
|
135
|
+
|
|
136
|
+
type ImageContent = z.infer<typeof ImageContentZodSchema>;
|
|
137
|
+
|
|
138
|
+
const ContentZodSchema = z.discriminatedUnion("type", [
|
|
139
|
+
TextContentZodSchema,
|
|
140
|
+
ImageContentZodSchema,
|
|
141
|
+
]);
|
|
142
|
+
|
|
143
|
+
const ContentResultZodSchema = z
|
|
144
|
+
.object({
|
|
145
|
+
content: ContentZodSchema.array(),
|
|
146
|
+
isError: z.boolean().optional(),
|
|
147
|
+
})
|
|
148
|
+
.strict();
|
|
149
|
+
|
|
150
|
+
type ContentResult = z.infer<typeof ContentResultZodSchema>;
|
|
151
|
+
|
|
77
152
|
type Tool<Params extends ToolParameters = ToolParameters> = {
|
|
78
153
|
name: string;
|
|
79
154
|
description?: string;
|
|
80
155
|
parameters?: Params;
|
|
81
|
-
execute: (
|
|
156
|
+
execute: (
|
|
157
|
+
args: z.infer<Params>,
|
|
158
|
+
context: Context,
|
|
159
|
+
) => Promise<string | ContentResult | TextContent | ImageContent>;
|
|
82
160
|
};
|
|
83
161
|
|
|
84
162
|
type Resource = {
|
|
@@ -185,118 +263,119 @@ export class FastMCP {
|
|
|
185
263
|
};
|
|
186
264
|
});
|
|
187
265
|
|
|
188
|
-
server.setRequestHandler(
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
266
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
267
|
+
const tool = this.#tools.find(
|
|
268
|
+
(tool) => tool.name === request.params.name,
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
if (!tool) {
|
|
272
|
+
throw new McpError(
|
|
273
|
+
ErrorCode.MethodNotFound,
|
|
274
|
+
`Unknown tool: ${request.params.name}`,
|
|
193
275
|
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
let args: any = undefined;
|
|
194
279
|
|
|
195
|
-
|
|
280
|
+
if (tool.parameters) {
|
|
281
|
+
const parsed = tool.parameters.safeParse(request.params.arguments);
|
|
282
|
+
|
|
283
|
+
if (!parsed.success) {
|
|
196
284
|
throw new McpError(
|
|
197
|
-
ErrorCode.
|
|
198
|
-
`
|
|
285
|
+
ErrorCode.InvalidRequest,
|
|
286
|
+
`Invalid ${request.params.name} arguments`,
|
|
199
287
|
);
|
|
200
288
|
}
|
|
201
289
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (tool.parameters) {
|
|
205
|
-
const parsed = tool.parameters.safeParse(request.params.arguments);
|
|
206
|
-
|
|
207
|
-
if (!parsed.success) {
|
|
208
|
-
throw new McpError(
|
|
209
|
-
ErrorCode.InvalidRequest,
|
|
210
|
-
`Invalid ${request.params.name} arguments`,
|
|
211
|
-
);
|
|
212
|
-
}
|
|
290
|
+
args = parsed.data;
|
|
291
|
+
}
|
|
213
292
|
|
|
214
|
-
|
|
215
|
-
}
|
|
293
|
+
const progressToken = request.params?._meta?.progressToken;
|
|
216
294
|
|
|
217
|
-
|
|
295
|
+
let result: ContentResult;
|
|
218
296
|
|
|
219
|
-
|
|
297
|
+
try {
|
|
298
|
+
const reportProgress = async (progress: Progress) => {
|
|
299
|
+
await server.notification({
|
|
300
|
+
method: "notifications/progress",
|
|
301
|
+
params: {
|
|
302
|
+
...progress,
|
|
303
|
+
progressToken,
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
};
|
|
220
307
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
308
|
+
const log = {
|
|
309
|
+
debug: (message: string, context?: SerializableValue) => {
|
|
310
|
+
server.sendLoggingMessage({
|
|
311
|
+
level: "debug",
|
|
312
|
+
data: {
|
|
313
|
+
message,
|
|
314
|
+
context,
|
|
228
315
|
},
|
|
229
316
|
});
|
|
230
|
-
}
|
|
317
|
+
},
|
|
318
|
+
error: (message: string, context?: SerializableValue) => {
|
|
319
|
+
server.sendLoggingMessage({
|
|
320
|
+
level: "error",
|
|
321
|
+
data: {
|
|
322
|
+
message,
|
|
323
|
+
context,
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
},
|
|
327
|
+
info: (message: string, context?: SerializableValue) => {
|
|
328
|
+
server.sendLoggingMessage({
|
|
329
|
+
level: "info",
|
|
330
|
+
data: {
|
|
331
|
+
message,
|
|
332
|
+
context,
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
},
|
|
336
|
+
warn: (message: string, context?: SerializableValue) => {
|
|
337
|
+
server.sendLoggingMessage({
|
|
338
|
+
level: "warning",
|
|
339
|
+
data: {
|
|
340
|
+
message,
|
|
341
|
+
context,
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
},
|
|
345
|
+
};
|
|
231
346
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
data: {
|
|
237
|
-
message,
|
|
238
|
-
context,
|
|
239
|
-
},
|
|
240
|
-
});
|
|
241
|
-
},
|
|
242
|
-
error: (message: string, context?: SerializableValue) => {
|
|
243
|
-
server.sendLoggingMessage({
|
|
244
|
-
level: "error",
|
|
245
|
-
data: {
|
|
246
|
-
message,
|
|
247
|
-
context,
|
|
248
|
-
},
|
|
249
|
-
});
|
|
250
|
-
},
|
|
251
|
-
info: (message: string, context?: SerializableValue) => {
|
|
252
|
-
server.sendLoggingMessage({
|
|
253
|
-
level: "info",
|
|
254
|
-
data: {
|
|
255
|
-
message,
|
|
256
|
-
context,
|
|
257
|
-
},
|
|
258
|
-
});
|
|
259
|
-
},
|
|
260
|
-
warn: (message: string, context?: SerializableValue) => {
|
|
261
|
-
server.sendLoggingMessage({
|
|
262
|
-
level: "warning",
|
|
263
|
-
data: {
|
|
264
|
-
message,
|
|
265
|
-
context,
|
|
266
|
-
},
|
|
267
|
-
});
|
|
268
|
-
},
|
|
269
|
-
};
|
|
347
|
+
const maybeStringResult = await tool.execute(args, {
|
|
348
|
+
reportProgress,
|
|
349
|
+
log,
|
|
350
|
+
});
|
|
270
351
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
352
|
+
if (typeof maybeStringResult === "string") {
|
|
353
|
+
result = ContentResultZodSchema.parse({
|
|
354
|
+
content: [{ type: "text", text: maybeStringResult }],
|
|
274
355
|
});
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
return {
|
|
284
|
-
content: [{ type: "text", text: `Error: ${error}` }],
|
|
285
|
-
isError: true,
|
|
286
|
-
};
|
|
356
|
+
} else if ("type" in maybeStringResult) {
|
|
357
|
+
result = ContentResultZodSchema.parse({
|
|
358
|
+
content: [maybeStringResult],
|
|
359
|
+
});
|
|
360
|
+
} else {
|
|
361
|
+
result = ContentResultZodSchema.parse(maybeStringResult);
|
|
287
362
|
}
|
|
288
|
-
|
|
289
|
-
if (
|
|
363
|
+
} catch (error) {
|
|
364
|
+
if (error instanceof UserError) {
|
|
290
365
|
return {
|
|
291
|
-
content: [{ type: "text", text:
|
|
366
|
+
content: [{ type: "text", text: error.message }],
|
|
367
|
+
isError: true,
|
|
292
368
|
};
|
|
293
369
|
}
|
|
294
370
|
|
|
295
371
|
return {
|
|
296
|
-
content: [{ type: "text", text:
|
|
372
|
+
content: [{ type: "text", text: `Error: ${error}` }],
|
|
373
|
+
isError: true,
|
|
297
374
|
};
|
|
298
|
-
}
|
|
299
|
-
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return result;
|
|
378
|
+
});
|
|
300
379
|
}
|
|
301
380
|
|
|
302
381
|
private setupResourceHandlers(server: Server) {
|