fastmcp 1.22.4 → 1.23.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 +51 -8
- package/dist/FastMCP.d.ts +165 -130
- package/dist/FastMCP.js +281 -274
- package/dist/FastMCP.js.map +1 -1
- package/dist/bin/fastmcp.js +9 -9
- package/dist/bin/fastmcp.js.map +1 -1
- package/eslint.config.ts +14 -0
- package/jsr.json +1 -1
- package/package.json +6 -1
- package/src/FastMCP.test.ts +435 -424
- package/src/FastMCP.ts +517 -460
- package/src/bin/fastmcp.ts +7 -7
- package/src/examples/addition.ts +33 -15
- package/eslint.config.js +0 -3
package/src/FastMCP.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
3
4
|
import {
|
|
4
5
|
AudioContent,
|
|
5
6
|
CallToolRequestSchema,
|
|
@@ -20,21 +21,18 @@ import {
|
|
|
20
21
|
SetLevelRequestSchema,
|
|
21
22
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
22
23
|
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
23
|
-
import { toJsonSchema } from "xsschema";
|
|
24
|
-
import { z } from "zod";
|
|
25
|
-
import { setTimeout as delay } from "timers/promises";
|
|
26
|
-
import { readFile } from "fs/promises";
|
|
27
|
-
import { fileTypeFromBuffer } from "file-type";
|
|
28
|
-
import { StrictEventEmitter } from "strict-event-emitter-types";
|
|
29
24
|
import { EventEmitter } from "events";
|
|
25
|
+
import { fileTypeFromBuffer } from "file-type";
|
|
26
|
+
import { readFile } from "fs/promises";
|
|
30
27
|
import Fuse from "fuse.js";
|
|
28
|
+
import http from "http";
|
|
31
29
|
import { startSSEServer } from "mcp-proxy";
|
|
32
|
-
import {
|
|
30
|
+
import { StrictEventEmitter } from "strict-event-emitter-types";
|
|
31
|
+
import { setTimeout as delay } from "timers/promises";
|
|
32
|
+
import { fetch } from "undici";
|
|
33
33
|
import parseURITemplate from "uri-templates";
|
|
34
|
-
import
|
|
35
|
-
import {
|
|
36
|
-
fetch
|
|
37
|
-
} from "undici";
|
|
34
|
+
import { toJsonSchema } from "xsschema";
|
|
35
|
+
import { z } from "zod";
|
|
38
36
|
|
|
39
37
|
export type SSEServer = {
|
|
40
38
|
close: () => Promise<void>;
|
|
@@ -46,15 +44,15 @@ type FastMCPEvents<T extends FastMCPSessionAuth> = {
|
|
|
46
44
|
};
|
|
47
45
|
|
|
48
46
|
type FastMCPSessionEvents = {
|
|
49
|
-
rootsChanged: (event: { roots: Root[] }) => void;
|
|
50
47
|
error: (event: { error: Error }) => void;
|
|
48
|
+
rootsChanged: (event: { roots: Root[] }) => void;
|
|
51
49
|
};
|
|
52
50
|
|
|
53
51
|
/**
|
|
54
52
|
* Generates an image content object from a URL, file path, or buffer.
|
|
55
53
|
*/
|
|
56
54
|
export const imageContent = async (
|
|
57
|
-
input: {
|
|
55
|
+
input: { buffer: Buffer } | { path: string } | { url: string },
|
|
58
56
|
): Promise<ImageContent> => {
|
|
59
57
|
let rawData: Buffer;
|
|
60
58
|
|
|
@@ -81,13 +79,15 @@ export const imageContent = async (
|
|
|
81
79
|
const base64Data = rawData.toString("base64");
|
|
82
80
|
|
|
83
81
|
return {
|
|
84
|
-
type: "image",
|
|
85
82
|
data: base64Data,
|
|
86
83
|
mimeType: mimeType?.mime ?? "image/png",
|
|
84
|
+
type: "image",
|
|
87
85
|
} as const;
|
|
88
86
|
};
|
|
89
87
|
|
|
90
|
-
export const audioContent = async (
|
|
88
|
+
export const audioContent = async (
|
|
89
|
+
input: { buffer: Buffer } | { path: string } | { url: string },
|
|
90
|
+
): Promise<AudioContent> => {
|
|
91
91
|
let rawData: Buffer;
|
|
92
92
|
|
|
93
93
|
if ("url" in input) {
|
|
@@ -103,7 +103,9 @@ export const audioContent = async (input: { url: string } | { path: string } | {
|
|
|
103
103
|
} else if ("buffer" in input) {
|
|
104
104
|
rawData = input.buffer;
|
|
105
105
|
} else {
|
|
106
|
-
throw new Error(
|
|
106
|
+
throw new Error(
|
|
107
|
+
"Invalid input: Provide a valid 'url', 'path', or 'buffer'",
|
|
108
|
+
);
|
|
107
109
|
}
|
|
108
110
|
|
|
109
111
|
const mimeType = await fileTypeFromBuffer(rawData);
|
|
@@ -111,47 +113,29 @@ export const audioContent = async (input: { url: string } | { path: string } | {
|
|
|
111
113
|
const base64Data = rawData.toString("base64");
|
|
112
114
|
|
|
113
115
|
return {
|
|
114
|
-
type: "audio",
|
|
115
116
|
data: base64Data,
|
|
116
117
|
mimeType: mimeType?.mime ?? "audio/mpeg",
|
|
118
|
+
type: "audio",
|
|
117
119
|
} as const;
|
|
118
120
|
};
|
|
119
121
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
type Context<T extends FastMCPSessionAuth> = {
|
|
123
|
+
log: {
|
|
124
|
+
debug: (message: string, data?: SerializableValue) => void;
|
|
125
|
+
error: (message: string, data?: SerializableValue) => void;
|
|
126
|
+
info: (message: string, data?: SerializableValue) => void;
|
|
127
|
+
warn: (message: string, data?: SerializableValue) => void;
|
|
128
|
+
};
|
|
129
|
+
reportProgress: (progress: Progress) => Promise<void>;
|
|
130
|
+
session: T | undefined;
|
|
131
|
+
};
|
|
126
132
|
|
|
127
133
|
type Extra = unknown;
|
|
128
134
|
|
|
129
135
|
type Extras = Record<string, Extra>;
|
|
130
136
|
|
|
131
|
-
export class UnexpectedStateError extends FastMCPError {
|
|
132
|
-
public extras?: Extras;
|
|
133
|
-
|
|
134
|
-
public constructor(message: string, extras?: Extras) {
|
|
135
|
-
super(message);
|
|
136
|
-
this.name = new.target.name;
|
|
137
|
-
this.extras = extras;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* An error that is meant to be surfaced to the user.
|
|
143
|
-
*/
|
|
144
|
-
export class UserError extends UnexpectedStateError {}
|
|
145
|
-
|
|
146
|
-
type ToolParameters = StandardSchemaV1;
|
|
147
|
-
|
|
148
137
|
type Literal = boolean | null | number | string | undefined;
|
|
149
138
|
|
|
150
|
-
type SerializableValue =
|
|
151
|
-
| Literal
|
|
152
|
-
| SerializableValue[]
|
|
153
|
-
| { [key: string]: SerializableValue };
|
|
154
|
-
|
|
155
139
|
type Progress = {
|
|
156
140
|
/**
|
|
157
141
|
* The progress thus far. This should increase every time progress is made, even if the total is unknown.
|
|
@@ -163,41 +147,58 @@ type Progress = {
|
|
|
163
147
|
total?: number;
|
|
164
148
|
};
|
|
165
149
|
|
|
166
|
-
type
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
debug: (message: string, data?: SerializableValue) => void;
|
|
171
|
-
error: (message: string, data?: SerializableValue) => void;
|
|
172
|
-
info: (message: string, data?: SerializableValue) => void;
|
|
173
|
-
warn: (message: string, data?: SerializableValue) => void;
|
|
174
|
-
};
|
|
175
|
-
};
|
|
150
|
+
type SerializableValue =
|
|
151
|
+
| { [key: string]: SerializableValue }
|
|
152
|
+
| Literal
|
|
153
|
+
| SerializableValue[];
|
|
176
154
|
|
|
177
155
|
type TextContent = {
|
|
178
|
-
type: "text";
|
|
179
156
|
text: string;
|
|
157
|
+
type: "text";
|
|
180
158
|
};
|
|
181
159
|
|
|
160
|
+
type ToolParameters = StandardSchemaV1;
|
|
161
|
+
|
|
162
|
+
abstract class FastMCPError extends Error {
|
|
163
|
+
public constructor(message?: string) {
|
|
164
|
+
super(message);
|
|
165
|
+
this.name = new.target.name;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export class UnexpectedStateError extends FastMCPError {
|
|
170
|
+
public extras?: Extras;
|
|
171
|
+
|
|
172
|
+
public constructor(message: string, extras?: Extras) {
|
|
173
|
+
super(message);
|
|
174
|
+
this.name = new.target.name;
|
|
175
|
+
this.extras = extras;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* An error that is meant to be surfaced to the user.
|
|
181
|
+
*/
|
|
182
|
+
export class UserError extends UnexpectedStateError {}
|
|
183
|
+
|
|
182
184
|
const TextContentZodSchema = z
|
|
183
185
|
.object({
|
|
184
|
-
type: z.literal("text"),
|
|
185
186
|
/**
|
|
186
187
|
* The text content of the message.
|
|
187
188
|
*/
|
|
188
189
|
text: z.string(),
|
|
190
|
+
type: z.literal("text"),
|
|
189
191
|
})
|
|
190
192
|
.strict() satisfies z.ZodType<TextContent>;
|
|
191
193
|
|
|
192
194
|
type ImageContent = {
|
|
193
|
-
type: "image";
|
|
194
195
|
data: string;
|
|
195
196
|
mimeType: string;
|
|
197
|
+
type: "image";
|
|
196
198
|
};
|
|
197
199
|
|
|
198
200
|
const ImageContentZodSchema = z
|
|
199
201
|
.object({
|
|
200
|
-
type: z.literal("image"),
|
|
201
202
|
/**
|
|
202
203
|
* The base64-encoded image data.
|
|
203
204
|
*/
|
|
@@ -206,10 +207,11 @@ const ImageContentZodSchema = z
|
|
|
206
207
|
* The MIME type of the image. Different providers may support different image types.
|
|
207
208
|
*/
|
|
208
209
|
mimeType: z.string(),
|
|
210
|
+
type: z.literal("image"),
|
|
209
211
|
})
|
|
210
212
|
.strict() satisfies z.ZodType<ImageContent>;
|
|
211
213
|
|
|
212
|
-
type Content =
|
|
214
|
+
type Content = ImageContent | TextContent;
|
|
213
215
|
|
|
214
216
|
const ContentZodSchema = z.discriminatedUnion("type", [
|
|
215
217
|
TextContentZodSchema,
|
|
@@ -229,9 +231,9 @@ const ContentResultZodSchema = z
|
|
|
229
231
|
.strict() satisfies z.ZodType<ContentResult>;
|
|
230
232
|
|
|
231
233
|
type Completion = {
|
|
232
|
-
values: string[];
|
|
233
|
-
total?: number;
|
|
234
234
|
hasMore?: boolean;
|
|
235
|
+
total?: number;
|
|
236
|
+
values: string[];
|
|
235
237
|
};
|
|
236
238
|
|
|
237
239
|
/**
|
|
@@ -239,97 +241,85 @@ type Completion = {
|
|
|
239
241
|
*/
|
|
240
242
|
const CompletionZodSchema = z.object({
|
|
241
243
|
/**
|
|
242
|
-
*
|
|
244
|
+
* Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown.
|
|
243
245
|
*/
|
|
244
|
-
|
|
246
|
+
hasMore: z.optional(z.boolean()),
|
|
245
247
|
/**
|
|
246
248
|
* The total number of completion options available. This can exceed the number of values actually sent in the response.
|
|
247
249
|
*/
|
|
248
250
|
total: z.optional(z.number().int()),
|
|
249
251
|
/**
|
|
250
|
-
*
|
|
252
|
+
* An array of completion values. Must not exceed 100 items.
|
|
251
253
|
*/
|
|
252
|
-
|
|
254
|
+
values: z.array(z.string()).max(100),
|
|
253
255
|
}) satisfies z.ZodType<Completion>;
|
|
254
256
|
|
|
255
|
-
type
|
|
256
|
-
|
|
257
|
+
type ArgumentValueCompleter = (value: string) => Promise<Completion>;
|
|
258
|
+
|
|
259
|
+
type InputPrompt<
|
|
260
|
+
Arguments extends InputPromptArgument[] = InputPromptArgument[],
|
|
261
|
+
Args = PromptArgumentsToObject<Arguments>,
|
|
262
|
+
> = {
|
|
263
|
+
arguments?: InputPromptArgument[];
|
|
257
264
|
description?: string;
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
args: StandardSchemaV1.InferOutput<Params>,
|
|
261
|
-
context: Context<T>,
|
|
262
|
-
) => Promise<string | ContentResult | TextContent | ImageContent | AudioContent>;
|
|
265
|
+
load: (args: Args) => Promise<string>;
|
|
266
|
+
name: string;
|
|
263
267
|
};
|
|
264
268
|
|
|
265
|
-
type
|
|
266
|
-
| {
|
|
267
|
-
text: string;
|
|
268
|
-
}
|
|
269
|
-
| {
|
|
270
|
-
blob: string;
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
type InputResourceTemplateArgument = Readonly<{
|
|
274
|
-
name: string;
|
|
275
|
-
description?: string;
|
|
269
|
+
type InputPromptArgument = Readonly<{
|
|
276
270
|
complete?: ArgumentValueCompleter;
|
|
277
|
-
}>;
|
|
278
|
-
|
|
279
|
-
type ResourceTemplateArgument = Readonly<{
|
|
280
|
-
name: string;
|
|
281
271
|
description?: string;
|
|
282
|
-
|
|
272
|
+
enum?: string[];
|
|
273
|
+
name: string;
|
|
274
|
+
required?: boolean;
|
|
283
275
|
}>;
|
|
284
276
|
|
|
285
|
-
type
|
|
277
|
+
type InputResourceTemplate<
|
|
286
278
|
Arguments extends ResourceTemplateArgument[] = ResourceTemplateArgument[],
|
|
287
279
|
> = {
|
|
288
|
-
uriTemplate: string;
|
|
289
|
-
name: string;
|
|
290
|
-
description?: string;
|
|
291
|
-
mimeType?: string;
|
|
292
280
|
arguments: Arguments;
|
|
293
|
-
|
|
281
|
+
description?: string;
|
|
294
282
|
load: (
|
|
295
283
|
args: ResourceTemplateArgumentsToObject<Arguments>,
|
|
296
284
|
) => Promise<ResourceResult>;
|
|
285
|
+
mimeType?: string;
|
|
286
|
+
name: string;
|
|
287
|
+
uriTemplate: string;
|
|
297
288
|
};
|
|
298
289
|
|
|
299
|
-
type
|
|
300
|
-
|
|
301
|
-
|
|
290
|
+
type InputResourceTemplateArgument = Readonly<{
|
|
291
|
+
complete?: ArgumentValueCompleter;
|
|
292
|
+
description?: string;
|
|
293
|
+
name: string;
|
|
294
|
+
}>;
|
|
302
295
|
|
|
303
|
-
type
|
|
304
|
-
|
|
296
|
+
type LoggingLevel =
|
|
297
|
+
| "alert"
|
|
298
|
+
| "critical"
|
|
299
|
+
| "debug"
|
|
300
|
+
| "emergency"
|
|
301
|
+
| "error"
|
|
302
|
+
| "info"
|
|
303
|
+
| "notice"
|
|
304
|
+
| "warning";
|
|
305
|
+
|
|
306
|
+
type Prompt<
|
|
307
|
+
Arguments extends PromptArgument[] = PromptArgument[],
|
|
308
|
+
Args = PromptArgumentsToObject<Arguments>,
|
|
305
309
|
> = {
|
|
306
|
-
|
|
307
|
-
name: string
|
|
310
|
+
arguments?: PromptArgument[];
|
|
311
|
+
complete?: (name: string, value: string) => Promise<Completion>;
|
|
308
312
|
description?: string;
|
|
309
|
-
|
|
310
|
-
arguments: Arguments;
|
|
311
|
-
load: (
|
|
312
|
-
args: ResourceTemplateArgumentsToObject<Arguments>,
|
|
313
|
-
) => Promise<ResourceResult>;
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
type Resource = {
|
|
317
|
-
uri: string;
|
|
313
|
+
load: (args: Args) => Promise<string>;
|
|
318
314
|
name: string;
|
|
319
|
-
description?: string;
|
|
320
|
-
mimeType?: string;
|
|
321
|
-
load: () => Promise<ResourceResult | ResourceResult[]>;
|
|
322
|
-
complete?: (name: string, value: string) => Promise<Completion>;
|
|
323
315
|
};
|
|
324
316
|
|
|
325
|
-
type
|
|
326
|
-
|
|
327
|
-
type InputPromptArgument = Readonly<{
|
|
328
|
-
name: string;
|
|
329
|
-
description?: string;
|
|
330
|
-
required?: boolean;
|
|
317
|
+
type PromptArgument = Readonly<{
|
|
331
318
|
complete?: ArgumentValueCompleter;
|
|
319
|
+
description?: string;
|
|
332
320
|
enum?: string[];
|
|
321
|
+
name: string;
|
|
322
|
+
required?: boolean;
|
|
333
323
|
}>;
|
|
334
324
|
|
|
335
325
|
type PromptArgumentsToObject<T extends { name: string; required?: boolean }[]> =
|
|
@@ -342,93 +332,171 @@ type PromptArgumentsToObject<T extends { name: string; required?: boolean }[]> =
|
|
|
342
332
|
: string | undefined;
|
|
343
333
|
};
|
|
344
334
|
|
|
345
|
-
type
|
|
346
|
-
|
|
347
|
-
Args = PromptArgumentsToObject<Arguments>,
|
|
348
|
-
> = {
|
|
349
|
-
name: string;
|
|
335
|
+
type Resource = {
|
|
336
|
+
complete?: (name: string, value: string) => Promise<Completion>;
|
|
350
337
|
description?: string;
|
|
351
|
-
|
|
352
|
-
|
|
338
|
+
load: () => Promise<ResourceResult | ResourceResult[]>;
|
|
339
|
+
mimeType?: string;
|
|
340
|
+
name: string;
|
|
341
|
+
uri: string;
|
|
353
342
|
};
|
|
354
343
|
|
|
355
|
-
type
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}
|
|
344
|
+
type ResourceResult =
|
|
345
|
+
| {
|
|
346
|
+
blob: string;
|
|
347
|
+
}
|
|
348
|
+
| {
|
|
349
|
+
text: string;
|
|
350
|
+
};
|
|
362
351
|
|
|
363
|
-
type
|
|
364
|
-
Arguments extends
|
|
365
|
-
Args = PromptArgumentsToObject<Arguments>,
|
|
352
|
+
type ResourceTemplate<
|
|
353
|
+
Arguments extends ResourceTemplateArgument[] = ResourceTemplateArgument[],
|
|
366
354
|
> = {
|
|
367
|
-
arguments
|
|
355
|
+
arguments: Arguments;
|
|
368
356
|
complete?: (name: string, value: string) => Promise<Completion>;
|
|
369
357
|
description?: string;
|
|
370
|
-
load: (
|
|
358
|
+
load: (
|
|
359
|
+
args: ResourceTemplateArgumentsToObject<Arguments>,
|
|
360
|
+
) => Promise<ResourceResult>;
|
|
361
|
+
mimeType?: string;
|
|
371
362
|
name: string;
|
|
363
|
+
uriTemplate: string;
|
|
372
364
|
};
|
|
373
365
|
|
|
374
|
-
type
|
|
366
|
+
type ResourceTemplateArgument = Readonly<{
|
|
367
|
+
complete?: ArgumentValueCompleter;
|
|
368
|
+
description?: string;
|
|
375
369
|
name: string;
|
|
376
|
-
|
|
370
|
+
}>;
|
|
371
|
+
|
|
372
|
+
type ResourceTemplateArgumentsToObject<T extends { name: string }[]> = {
|
|
373
|
+
[K in T[number]["name"]]: string;
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
type ServerOptions<T extends FastMCPSessionAuth> = {
|
|
377
377
|
authenticate?: Authenticate<T>;
|
|
378
|
+
instructions?: string;
|
|
379
|
+
name: string;
|
|
380
|
+
version: `${number}.${number}.${number}`;
|
|
378
381
|
};
|
|
379
382
|
|
|
380
|
-
type
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
383
|
+
type Tool<
|
|
384
|
+
T extends FastMCPSessionAuth,
|
|
385
|
+
Params extends ToolParameters = ToolParameters,
|
|
386
|
+
> = {
|
|
387
|
+
annotations?: ToolAnnotations;
|
|
388
|
+
description?: string;
|
|
389
|
+
execute: (
|
|
390
|
+
args: StandardSchemaV1.InferOutput<Params>,
|
|
391
|
+
context: Context<T>,
|
|
392
|
+
) => Promise<
|
|
393
|
+
AudioContent | ContentResult | ImageContent | string | TextContent
|
|
394
|
+
>;
|
|
395
|
+
name: string;
|
|
396
|
+
parameters?: Params;
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Tool annotations as defined in MCP Specification (2025-03-26)
|
|
401
|
+
* These provide hints about a tool's behavior.
|
|
402
|
+
*/
|
|
403
|
+
type ToolAnnotations = {
|
|
404
|
+
/**
|
|
405
|
+
* If true, the tool may perform destructive updates
|
|
406
|
+
* Only meaningful when readOnlyHint is false
|
|
407
|
+
* @default true
|
|
408
|
+
*/
|
|
409
|
+
destructiveHint?: boolean;
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* If true, calling the tool repeatedly with the same arguments has no additional effect
|
|
413
|
+
* Only meaningful when readOnlyHint is false
|
|
414
|
+
* @default false
|
|
415
|
+
*/
|
|
416
|
+
idempotentHint?: boolean;
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* If true, the tool may interact with an "open world" of external entities
|
|
420
|
+
* @default true
|
|
421
|
+
*/
|
|
422
|
+
openWorldHint?: boolean;
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* If true, indicates the tool does not modify its environment
|
|
426
|
+
* @default false
|
|
427
|
+
*/
|
|
428
|
+
readOnlyHint?: boolean;
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* A human-readable title for the tool, useful for UI display
|
|
432
|
+
*/
|
|
433
|
+
title?: string;
|
|
434
|
+
};
|
|
389
435
|
|
|
390
436
|
const FastMCPSessionEventEmitterBase: {
|
|
391
437
|
new (): StrictEventEmitter<EventEmitter, FastMCPSessionEvents>;
|
|
392
438
|
} = EventEmitter;
|
|
393
439
|
|
|
394
|
-
|
|
440
|
+
type FastMCPSessionAuth = Record<string, unknown> | undefined;
|
|
395
441
|
|
|
396
442
|
type SamplingResponse = {
|
|
443
|
+
content: AudioContent | ImageContent | TextContent;
|
|
397
444
|
model: string;
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
content: TextContent | ImageContent | AudioContent;
|
|
445
|
+
role: "assistant" | "user";
|
|
446
|
+
stopReason?: "endTurn" | "maxTokens" | "stopSequence" | string;
|
|
401
447
|
};
|
|
402
448
|
|
|
403
|
-
|
|
449
|
+
class FastMCPSessionEventEmitter extends FastMCPSessionEventEmitterBase {}
|
|
404
450
|
|
|
405
|
-
export class FastMCPSession<
|
|
451
|
+
export class FastMCPSession<
|
|
452
|
+
T extends FastMCPSessionAuth = FastMCPSessionAuth,
|
|
453
|
+
> extends FastMCPSessionEventEmitter {
|
|
454
|
+
public get clientCapabilities(): ClientCapabilities | null {
|
|
455
|
+
return this.#clientCapabilities ?? null;
|
|
456
|
+
}
|
|
457
|
+
public get loggingLevel(): LoggingLevel {
|
|
458
|
+
return this.#loggingLevel;
|
|
459
|
+
}
|
|
460
|
+
public get roots(): Root[] {
|
|
461
|
+
return this.#roots;
|
|
462
|
+
}
|
|
463
|
+
public get server(): Server {
|
|
464
|
+
return this.#server;
|
|
465
|
+
}
|
|
466
|
+
#auth: T | undefined;
|
|
406
467
|
#capabilities: ServerCapabilities = {};
|
|
407
468
|
#clientCapabilities?: ClientCapabilities;
|
|
408
469
|
#loggingLevel: LoggingLevel = "info";
|
|
470
|
+
#pingInterval: null | ReturnType<typeof setInterval> = null;
|
|
471
|
+
|
|
409
472
|
#prompts: Prompt[] = [];
|
|
473
|
+
|
|
410
474
|
#resources: Resource[] = [];
|
|
475
|
+
|
|
411
476
|
#resourceTemplates: ResourceTemplate[] = [];
|
|
477
|
+
|
|
412
478
|
#roots: Root[] = [];
|
|
479
|
+
|
|
413
480
|
#server: Server;
|
|
414
|
-
#auth: T | undefined;
|
|
415
481
|
|
|
416
482
|
constructor({
|
|
417
483
|
auth,
|
|
484
|
+
instructions,
|
|
418
485
|
name,
|
|
419
|
-
|
|
420
|
-
tools,
|
|
486
|
+
prompts,
|
|
421
487
|
resources,
|
|
422
488
|
resourcesTemplates,
|
|
423
|
-
|
|
489
|
+
tools,
|
|
490
|
+
version,
|
|
424
491
|
}: {
|
|
425
492
|
auth?: T;
|
|
493
|
+
instructions?: string;
|
|
426
494
|
name: string;
|
|
427
|
-
|
|
428
|
-
tools: Tool<T>[];
|
|
495
|
+
prompts: Prompt[];
|
|
429
496
|
resources: Resource[];
|
|
430
497
|
resourcesTemplates: InputResourceTemplate[];
|
|
431
|
-
|
|
498
|
+
tools: Tool<T>[];
|
|
499
|
+
version: string;
|
|
432
500
|
}) {
|
|
433
501
|
super();
|
|
434
502
|
|
|
@@ -454,7 +522,7 @@ export class FastMCPSession<T extends FastMCPSessionAuth = FastMCPSessionAuth> e
|
|
|
454
522
|
|
|
455
523
|
this.#server = new Server(
|
|
456
524
|
{ name: name, version: version },
|
|
457
|
-
{ capabilities: this.#capabilities },
|
|
525
|
+
{ capabilities: this.#capabilities, instructions: instructions },
|
|
458
526
|
);
|
|
459
527
|
|
|
460
528
|
this.setupErrorHandling();
|
|
@@ -487,33 +555,72 @@ export class FastMCPSession<T extends FastMCPSessionAuth = FastMCPSessionAuth> e
|
|
|
487
555
|
}
|
|
488
556
|
}
|
|
489
557
|
|
|
490
|
-
|
|
491
|
-
this.#
|
|
558
|
+
public async close() {
|
|
559
|
+
if (this.#pingInterval) {
|
|
560
|
+
clearInterval(this.#pingInterval);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
try {
|
|
564
|
+
await this.#server.close();
|
|
565
|
+
} catch (error) {
|
|
566
|
+
console.error("[FastMCP error]", "could not close server", error);
|
|
567
|
+
}
|
|
492
568
|
}
|
|
493
569
|
|
|
494
|
-
|
|
495
|
-
|
|
570
|
+
public async connect(transport: Transport) {
|
|
571
|
+
if (this.#server.transport) {
|
|
572
|
+
throw new UnexpectedStateError("Server is already connected");
|
|
573
|
+
}
|
|
496
574
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
575
|
+
await this.#server.connect(transport);
|
|
576
|
+
|
|
577
|
+
let attempt = 0;
|
|
578
|
+
|
|
579
|
+
while (attempt++ < 10) {
|
|
580
|
+
const capabilities = await this.#server.getClientCapabilities();
|
|
581
|
+
|
|
582
|
+
if (capabilities) {
|
|
583
|
+
this.#clientCapabilities = capabilities;
|
|
584
|
+
|
|
585
|
+
break;
|
|
500
586
|
}
|
|
587
|
+
|
|
588
|
+
await delay(100);
|
|
501
589
|
}
|
|
502
590
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
if (completers[name]) {
|
|
507
|
-
return await completers[name](value);
|
|
508
|
-
}
|
|
591
|
+
if (!this.#clientCapabilities) {
|
|
592
|
+
console.warn("[FastMCP warning] could not infer client capabilities");
|
|
593
|
+
}
|
|
509
594
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
595
|
+
if (this.#clientCapabilities?.roots?.listChanged) {
|
|
596
|
+
try {
|
|
597
|
+
const roots = await this.#server.listRoots();
|
|
598
|
+
this.#roots = roots.roots;
|
|
599
|
+
} catch (e) {
|
|
600
|
+
console.error(
|
|
601
|
+
`[FastMCP error] received error listing roots.\n\n${e instanceof Error ? e.stack : JSON.stringify(e)}`,
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
515
605
|
|
|
516
|
-
this.#
|
|
606
|
+
if (this.#clientCapabilities) {
|
|
607
|
+
this.#pingInterval = setInterval(async () => {
|
|
608
|
+
try {
|
|
609
|
+
await this.#server.ping();
|
|
610
|
+
} catch {
|
|
611
|
+
// The reason we are not emitting an error here is because some clients
|
|
612
|
+
// seem to not respond to the ping request, and we don't want to crash the server,
|
|
613
|
+
// e.g., https://github.com/punkpeye/fastmcp/issues/38.
|
|
614
|
+
console.warn("[FastMCP warning] server is not responding to ping");
|
|
615
|
+
}
|
|
616
|
+
}, 1000);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
public async requestSampling(
|
|
621
|
+
message: z.infer<typeof CreateMessageRequestSchema>["params"],
|
|
622
|
+
): Promise<SamplingResponse> {
|
|
623
|
+
return this.#server.createMessage(message);
|
|
517
624
|
}
|
|
518
625
|
|
|
519
626
|
private addPrompt(inputPrompt: InputPrompt) {
|
|
@@ -545,8 +652,8 @@ export class FastMCPSession<T extends FastMCPSessionAuth = FastMCPSessionAuth> e
|
|
|
545
652
|
const result = fuse.search(value);
|
|
546
653
|
|
|
547
654
|
return {
|
|
548
|
-
values: result.map((item) => item.item),
|
|
549
655
|
total: result.length,
|
|
656
|
+
values: result.map((item) => item.item),
|
|
550
657
|
};
|
|
551
658
|
}
|
|
552
659
|
|
|
@@ -559,94 +666,33 @@ export class FastMCPSession<T extends FastMCPSessionAuth = FastMCPSessionAuth> e
|
|
|
559
666
|
this.#prompts.push(prompt);
|
|
560
667
|
}
|
|
561
668
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
public get server(): Server {
|
|
567
|
-
return this.#server;
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
#pingInterval: ReturnType<typeof setInterval> | null = null;
|
|
571
|
-
|
|
572
|
-
public async requestSampling(
|
|
573
|
-
message: z.infer<typeof CreateMessageRequestSchema>["params"],
|
|
574
|
-
): Promise<SamplingResponse> {
|
|
575
|
-
return this.#server.createMessage(message);
|
|
669
|
+
private addResource(inputResource: Resource) {
|
|
670
|
+
this.#resources.push(inputResource);
|
|
576
671
|
}
|
|
577
672
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
throw new UnexpectedStateError("Server is already connected");
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
await this.#server.connect(transport);
|
|
584
|
-
|
|
585
|
-
let attempt = 0;
|
|
586
|
-
|
|
587
|
-
while (attempt++ < 10) {
|
|
588
|
-
const capabilities = await this.#server.getClientCapabilities();
|
|
589
|
-
|
|
590
|
-
if (capabilities) {
|
|
591
|
-
this.#clientCapabilities = capabilities;
|
|
592
|
-
|
|
593
|
-
break;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
await delay(100);
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
if (!this.#clientCapabilities) {
|
|
600
|
-
console.warn('[FastMCP warning] could not infer client capabilities')
|
|
601
|
-
}
|
|
673
|
+
private addResourceTemplate(inputResourceTemplate: InputResourceTemplate) {
|
|
674
|
+
const completers: Record<string, ArgumentValueCompleter> = {};
|
|
602
675
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
this.#roots = roots.roots;
|
|
607
|
-
} catch(e) {
|
|
608
|
-
console.error(`[FastMCP error] received error listing roots.\n\n${e instanceof Error ? e.stack : JSON.stringify(e)}`)
|
|
676
|
+
for (const argument of inputResourceTemplate.arguments ?? []) {
|
|
677
|
+
if (argument.complete) {
|
|
678
|
+
completers[argument.name] = argument.complete;
|
|
609
679
|
}
|
|
610
680
|
}
|
|
611
681
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
// The reason we are not emitting an error here is because some clients
|
|
618
|
-
// seem to not respond to the ping request, and we don't want to crash the server,
|
|
619
|
-
// e.g., https://github.com/punkpeye/fastmcp/issues/38.
|
|
620
|
-
console.warn("[FastMCP warning] server is not responding to ping")
|
|
682
|
+
const resourceTemplate = {
|
|
683
|
+
...inputResourceTemplate,
|
|
684
|
+
complete: async (name: string, value: string) => {
|
|
685
|
+
if (completers[name]) {
|
|
686
|
+
return await completers[name](value);
|
|
621
687
|
}
|
|
622
|
-
}, 1000);
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
688
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
public async close() {
|
|
631
|
-
if (this.#pingInterval) {
|
|
632
|
-
clearInterval(this.#pingInterval);
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
try {
|
|
636
|
-
await this.#server.close();
|
|
637
|
-
} catch (error) {
|
|
638
|
-
console.error("[FastMCP error]", "could not close server", error);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
private setupErrorHandling() {
|
|
643
|
-
this.#server.onerror = (error) => {
|
|
644
|
-
console.error("[FastMCP error]", error);
|
|
689
|
+
return {
|
|
690
|
+
values: [],
|
|
691
|
+
};
|
|
692
|
+
},
|
|
645
693
|
};
|
|
646
|
-
}
|
|
647
694
|
|
|
648
|
-
|
|
649
|
-
return this.#loggingLevel;
|
|
695
|
+
this.#resourceTemplates.push(resourceTemplate);
|
|
650
696
|
}
|
|
651
697
|
|
|
652
698
|
private setupCompleteHandlers() {
|
|
@@ -722,19 +768,10 @@ export class FastMCPSession<T extends FastMCPSessionAuth = FastMCPSessionAuth> e
|
|
|
722
768
|
});
|
|
723
769
|
}
|
|
724
770
|
|
|
725
|
-
private
|
|
726
|
-
this.#server.
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
this.#server.listRoots().then((roots) => {
|
|
730
|
-
this.#roots = roots.roots;
|
|
731
|
-
|
|
732
|
-
this.emit("rootsChanged", {
|
|
733
|
-
roots: roots.roots,
|
|
734
|
-
});
|
|
735
|
-
});
|
|
736
|
-
},
|
|
737
|
-
);
|
|
771
|
+
private setupErrorHandling() {
|
|
772
|
+
this.#server.onerror = (error) => {
|
|
773
|
+
console.error("[FastMCP error]", error);
|
|
774
|
+
};
|
|
738
775
|
}
|
|
739
776
|
|
|
740
777
|
private setupLoggingHandlers() {
|
|
@@ -745,134 +782,63 @@ export class FastMCPSession<T extends FastMCPSessionAuth = FastMCPSessionAuth> e
|
|
|
745
782
|
});
|
|
746
783
|
}
|
|
747
784
|
|
|
748
|
-
private
|
|
749
|
-
this.#server.setRequestHandler(
|
|
785
|
+
private setupPromptHandlers(prompts: Prompt[]) {
|
|
786
|
+
this.#server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
750
787
|
return {
|
|
751
|
-
|
|
788
|
+
prompts: prompts.map((prompt) => {
|
|
752
789
|
return {
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
: undefined,
|
|
790
|
+
arguments: prompt.arguments,
|
|
791
|
+
complete: prompt.complete,
|
|
792
|
+
description: prompt.description,
|
|
793
|
+
name: prompt.name,
|
|
758
794
|
};
|
|
759
|
-
})
|
|
795
|
+
}),
|
|
760
796
|
};
|
|
761
797
|
});
|
|
762
798
|
|
|
763
|
-
this.#server.setRequestHandler(
|
|
764
|
-
const
|
|
799
|
+
this.#server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
800
|
+
const prompt = prompts.find(
|
|
801
|
+
(prompt) => prompt.name === request.params.name,
|
|
802
|
+
);
|
|
765
803
|
|
|
766
|
-
if (!
|
|
804
|
+
if (!prompt) {
|
|
767
805
|
throw new McpError(
|
|
768
806
|
ErrorCode.MethodNotFound,
|
|
769
|
-
`Unknown
|
|
770
|
-
);
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
let args: any = undefined;
|
|
774
|
-
|
|
775
|
-
if (tool.parameters) {
|
|
776
|
-
const parsed = await tool.parameters["~standard"].validate(
|
|
777
|
-
request.params.arguments
|
|
807
|
+
`Unknown prompt: ${request.params.name}`,
|
|
778
808
|
);
|
|
809
|
+
}
|
|
779
810
|
|
|
780
|
-
|
|
781
|
-
throw new McpError(
|
|
782
|
-
ErrorCode.InvalidParams,
|
|
783
|
-
`Invalid ${request.params.name} parameters`,
|
|
784
|
-
);
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
args = parsed.value;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
const progressToken = request.params?._meta?.progressToken;
|
|
791
|
-
|
|
792
|
-
let result: ContentResult;
|
|
793
|
-
|
|
794
|
-
try {
|
|
795
|
-
const reportProgress = async (progress: Progress) => {
|
|
796
|
-
await this.#server.notification({
|
|
797
|
-
method: "notifications/progress",
|
|
798
|
-
params: {
|
|
799
|
-
...progress,
|
|
800
|
-
progressToken,
|
|
801
|
-
},
|
|
802
|
-
});
|
|
803
|
-
};
|
|
804
|
-
|
|
805
|
-
const log = {
|
|
806
|
-
debug: (message: string, context?: SerializableValue) => {
|
|
807
|
-
this.#server.sendLoggingMessage({
|
|
808
|
-
level: "debug",
|
|
809
|
-
data: {
|
|
810
|
-
message,
|
|
811
|
-
context,
|
|
812
|
-
},
|
|
813
|
-
});
|
|
814
|
-
},
|
|
815
|
-
error: (message: string, context?: SerializableValue) => {
|
|
816
|
-
this.#server.sendLoggingMessage({
|
|
817
|
-
level: "error",
|
|
818
|
-
data: {
|
|
819
|
-
message,
|
|
820
|
-
context,
|
|
821
|
-
},
|
|
822
|
-
});
|
|
823
|
-
},
|
|
824
|
-
info: (message: string, context?: SerializableValue) => {
|
|
825
|
-
this.#server.sendLoggingMessage({
|
|
826
|
-
level: "info",
|
|
827
|
-
data: {
|
|
828
|
-
message,
|
|
829
|
-
context,
|
|
830
|
-
},
|
|
831
|
-
});
|
|
832
|
-
},
|
|
833
|
-
warn: (message: string, context?: SerializableValue) => {
|
|
834
|
-
this.#server.sendLoggingMessage({
|
|
835
|
-
level: "warning",
|
|
836
|
-
data: {
|
|
837
|
-
message,
|
|
838
|
-
context,
|
|
839
|
-
},
|
|
840
|
-
});
|
|
841
|
-
},
|
|
842
|
-
};
|
|
843
|
-
|
|
844
|
-
const maybeStringResult = await tool.execute(args, {
|
|
845
|
-
reportProgress,
|
|
846
|
-
log,
|
|
847
|
-
session: this.#auth,
|
|
848
|
-
});
|
|
811
|
+
const args = request.params.arguments;
|
|
849
812
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
content: [maybeStringResult],
|
|
857
|
-
});
|
|
858
|
-
} else {
|
|
859
|
-
result = ContentResultZodSchema.parse(maybeStringResult);
|
|
860
|
-
}
|
|
861
|
-
} catch (error) {
|
|
862
|
-
if (error instanceof UserError) {
|
|
863
|
-
return {
|
|
864
|
-
content: [{ type: "text", text: error.message }],
|
|
865
|
-
isError: true,
|
|
866
|
-
};
|
|
813
|
+
for (const arg of prompt.arguments ?? []) {
|
|
814
|
+
if (arg.required && !(args && arg.name in args)) {
|
|
815
|
+
throw new McpError(
|
|
816
|
+
ErrorCode.InvalidRequest,
|
|
817
|
+
`Missing required argument: ${arg.name}`,
|
|
818
|
+
);
|
|
867
819
|
}
|
|
820
|
+
}
|
|
868
821
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
822
|
+
let result: Awaited<ReturnType<Prompt["load"]>>;
|
|
823
|
+
|
|
824
|
+
try {
|
|
825
|
+
result = await prompt.load(args as Record<string, string | undefined>);
|
|
826
|
+
} catch (error) {
|
|
827
|
+
throw new McpError(
|
|
828
|
+
ErrorCode.InternalError,
|
|
829
|
+
`Error loading prompt: ${error}`,
|
|
830
|
+
);
|
|
873
831
|
}
|
|
874
832
|
|
|
875
|
-
return
|
|
833
|
+
return {
|
|
834
|
+
description: prompt.description,
|
|
835
|
+
messages: [
|
|
836
|
+
{
|
|
837
|
+
content: { text: result, type: "text" },
|
|
838
|
+
role: "user",
|
|
839
|
+
},
|
|
840
|
+
],
|
|
841
|
+
};
|
|
876
842
|
});
|
|
877
843
|
}
|
|
878
844
|
|
|
@@ -881,9 +847,9 @@ export class FastMCPSession<T extends FastMCPSessionAuth = FastMCPSessionAuth> e
|
|
|
881
847
|
return {
|
|
882
848
|
resources: resources.map((resource) => {
|
|
883
849
|
return {
|
|
884
|
-
uri: resource.uri,
|
|
885
|
-
name: resource.name,
|
|
886
850
|
mimeType: resource.mimeType,
|
|
851
|
+
name: resource.name,
|
|
852
|
+
uri: resource.uri,
|
|
887
853
|
};
|
|
888
854
|
}),
|
|
889
855
|
};
|
|
@@ -917,9 +883,9 @@ export class FastMCPSession<T extends FastMCPSessionAuth = FastMCPSessionAuth> e
|
|
|
917
883
|
return {
|
|
918
884
|
contents: [
|
|
919
885
|
{
|
|
920
|
-
uri: uri,
|
|
921
886
|
mimeType: resourceTemplate.mimeType,
|
|
922
887
|
name: resourceTemplate.name,
|
|
888
|
+
uri: uri,
|
|
923
889
|
...result,
|
|
924
890
|
},
|
|
925
891
|
],
|
|
@@ -953,9 +919,9 @@ export class FastMCPSession<T extends FastMCPSessionAuth = FastMCPSessionAuth> e
|
|
|
953
919
|
if (Array.isArray(maybeArrayResult)) {
|
|
954
920
|
return {
|
|
955
921
|
contents: maybeArrayResult.map((result) => ({
|
|
956
|
-
uri: resource.uri,
|
|
957
922
|
mimeType: resource.mimeType,
|
|
958
923
|
name: resource.name,
|
|
924
|
+
uri: resource.uri,
|
|
959
925
|
...result,
|
|
960
926
|
})),
|
|
961
927
|
};
|
|
@@ -963,9 +929,9 @@ export class FastMCPSession<T extends FastMCPSessionAuth = FastMCPSessionAuth> e
|
|
|
963
929
|
return {
|
|
964
930
|
contents: [
|
|
965
931
|
{
|
|
966
|
-
uri: resource.uri,
|
|
967
932
|
mimeType: resource.mimeType,
|
|
968
933
|
name: resource.name,
|
|
934
|
+
uri: resource.uri,
|
|
969
935
|
...maybeArrayResult,
|
|
970
936
|
},
|
|
971
937
|
],
|
|
@@ -996,63 +962,152 @@ export class FastMCPSession<T extends FastMCPSessionAuth = FastMCPSessionAuth> e
|
|
|
996
962
|
);
|
|
997
963
|
}
|
|
998
964
|
|
|
999
|
-
private
|
|
1000
|
-
this.#server.
|
|
965
|
+
private setupRootsHandlers() {
|
|
966
|
+
this.#server.setNotificationHandler(
|
|
967
|
+
RootsListChangedNotificationSchema,
|
|
968
|
+
() => {
|
|
969
|
+
this.#server.listRoots().then((roots) => {
|
|
970
|
+
this.#roots = roots.roots;
|
|
971
|
+
|
|
972
|
+
this.emit("rootsChanged", {
|
|
973
|
+
roots: roots.roots,
|
|
974
|
+
});
|
|
975
|
+
});
|
|
976
|
+
},
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
private setupToolHandlers(tools: Tool<T>[]) {
|
|
981
|
+
this.#server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
1001
982
|
return {
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
983
|
+
tools: await Promise.all(
|
|
984
|
+
tools.map(async (tool) => {
|
|
985
|
+
return {
|
|
986
|
+
annotations: tool.annotations,
|
|
987
|
+
description: tool.description,
|
|
988
|
+
inputSchema: tool.parameters
|
|
989
|
+
? await toJsonSchema(tool.parameters)
|
|
990
|
+
: undefined,
|
|
991
|
+
name: tool.name,
|
|
992
|
+
};
|
|
993
|
+
}),
|
|
994
|
+
),
|
|
1010
995
|
};
|
|
1011
996
|
});
|
|
1012
997
|
|
|
1013
|
-
this.#server.setRequestHandler(
|
|
1014
|
-
const
|
|
1015
|
-
(prompt) => prompt.name === request.params.name,
|
|
1016
|
-
);
|
|
998
|
+
this.#server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
999
|
+
const tool = tools.find((tool) => tool.name === request.params.name);
|
|
1017
1000
|
|
|
1018
|
-
if (!
|
|
1001
|
+
if (!tool) {
|
|
1019
1002
|
throw new McpError(
|
|
1020
1003
|
ErrorCode.MethodNotFound,
|
|
1021
|
-
`Unknown
|
|
1004
|
+
`Unknown tool: ${request.params.name}`,
|
|
1022
1005
|
);
|
|
1023
1006
|
}
|
|
1024
1007
|
|
|
1025
|
-
|
|
1008
|
+
let args: unknown = undefined;
|
|
1026
1009
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1010
|
+
if (tool.parameters) {
|
|
1011
|
+
const parsed = await tool.parameters["~standard"].validate(
|
|
1012
|
+
request.params.arguments,
|
|
1013
|
+
);
|
|
1014
|
+
|
|
1015
|
+
if (parsed.issues) {
|
|
1029
1016
|
throw new McpError(
|
|
1030
|
-
ErrorCode.
|
|
1031
|
-
`
|
|
1017
|
+
ErrorCode.InvalidParams,
|
|
1018
|
+
`Invalid ${request.params.name} parameters`,
|
|
1032
1019
|
);
|
|
1033
1020
|
}
|
|
1021
|
+
|
|
1022
|
+
args = parsed.value;
|
|
1034
1023
|
}
|
|
1035
1024
|
|
|
1036
|
-
|
|
1025
|
+
const progressToken = request.params?._meta?.progressToken;
|
|
1026
|
+
|
|
1027
|
+
let result: ContentResult;
|
|
1037
1028
|
|
|
1038
1029
|
try {
|
|
1039
|
-
|
|
1030
|
+
const reportProgress = async (progress: Progress) => {
|
|
1031
|
+
await this.#server.notification({
|
|
1032
|
+
method: "notifications/progress",
|
|
1033
|
+
params: {
|
|
1034
|
+
...progress,
|
|
1035
|
+
progressToken,
|
|
1036
|
+
},
|
|
1037
|
+
});
|
|
1038
|
+
};
|
|
1039
|
+
|
|
1040
|
+
const log = {
|
|
1041
|
+
debug: (message: string, context?: SerializableValue) => {
|
|
1042
|
+
this.#server.sendLoggingMessage({
|
|
1043
|
+
data: {
|
|
1044
|
+
context,
|
|
1045
|
+
message,
|
|
1046
|
+
},
|
|
1047
|
+
level: "debug",
|
|
1048
|
+
});
|
|
1049
|
+
},
|
|
1050
|
+
error: (message: string, context?: SerializableValue) => {
|
|
1051
|
+
this.#server.sendLoggingMessage({
|
|
1052
|
+
data: {
|
|
1053
|
+
context,
|
|
1054
|
+
message,
|
|
1055
|
+
},
|
|
1056
|
+
level: "error",
|
|
1057
|
+
});
|
|
1058
|
+
},
|
|
1059
|
+
info: (message: string, context?: SerializableValue) => {
|
|
1060
|
+
this.#server.sendLoggingMessage({
|
|
1061
|
+
data: {
|
|
1062
|
+
context,
|
|
1063
|
+
message,
|
|
1064
|
+
},
|
|
1065
|
+
level: "info",
|
|
1066
|
+
});
|
|
1067
|
+
},
|
|
1068
|
+
warn: (message: string, context?: SerializableValue) => {
|
|
1069
|
+
this.#server.sendLoggingMessage({
|
|
1070
|
+
data: {
|
|
1071
|
+
context,
|
|
1072
|
+
message,
|
|
1073
|
+
},
|
|
1074
|
+
level: "warning",
|
|
1075
|
+
});
|
|
1076
|
+
},
|
|
1077
|
+
};
|
|
1078
|
+
|
|
1079
|
+
const maybeStringResult = await tool.execute(args, {
|
|
1080
|
+
log,
|
|
1081
|
+
reportProgress,
|
|
1082
|
+
session: this.#auth,
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
if (typeof maybeStringResult === "string") {
|
|
1086
|
+
result = ContentResultZodSchema.parse({
|
|
1087
|
+
content: [{ text: maybeStringResult, type: "text" }],
|
|
1088
|
+
});
|
|
1089
|
+
} else if ("type" in maybeStringResult) {
|
|
1090
|
+
result = ContentResultZodSchema.parse({
|
|
1091
|
+
content: [maybeStringResult],
|
|
1092
|
+
});
|
|
1093
|
+
} else {
|
|
1094
|
+
result = ContentResultZodSchema.parse(maybeStringResult);
|
|
1095
|
+
}
|
|
1040
1096
|
} catch (error) {
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1097
|
+
if (error instanceof UserError) {
|
|
1098
|
+
return {
|
|
1099
|
+
content: [{ text: error.message, type: "text" }],
|
|
1100
|
+
isError: true,
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
return {
|
|
1105
|
+
content: [{ text: `Error: ${error}`, type: "text" }],
|
|
1106
|
+
isError: true,
|
|
1107
|
+
};
|
|
1045
1108
|
}
|
|
1046
1109
|
|
|
1047
|
-
return
|
|
1048
|
-
description: prompt.description,
|
|
1049
|
-
messages: [
|
|
1050
|
-
{
|
|
1051
|
-
role: "user",
|
|
1052
|
-
content: { type: "text", text: result },
|
|
1053
|
-
},
|
|
1054
|
-
],
|
|
1055
|
-
};
|
|
1110
|
+
return result;
|
|
1056
1111
|
});
|
|
1057
1112
|
}
|
|
1058
1113
|
}
|
|
@@ -1061,19 +1116,25 @@ const FastMCPEventEmitterBase: {
|
|
|
1061
1116
|
new (): StrictEventEmitter<EventEmitter, FastMCPEvents<FastMCPSessionAuth>>;
|
|
1062
1117
|
} = EventEmitter;
|
|
1063
1118
|
|
|
1064
|
-
class FastMCPEventEmitter extends FastMCPEventEmitterBase {}
|
|
1065
|
-
|
|
1066
1119
|
type Authenticate<T> = (request: http.IncomingMessage) => Promise<T>;
|
|
1067
1120
|
|
|
1068
|
-
|
|
1121
|
+
class FastMCPEventEmitter extends FastMCPEventEmitterBase {}
|
|
1122
|
+
|
|
1123
|
+
export class FastMCP<
|
|
1124
|
+
T extends Record<string, unknown> | undefined = undefined,
|
|
1125
|
+
> extends FastMCPEventEmitter {
|
|
1126
|
+
public get sessions(): FastMCPSession<T>[] {
|
|
1127
|
+
return this.#sessions;
|
|
1128
|
+
}
|
|
1129
|
+
#authenticate: Authenticate<T> | undefined;
|
|
1069
1130
|
#options: ServerOptions<T>;
|
|
1070
1131
|
#prompts: InputPrompt[] = [];
|
|
1071
1132
|
#resources: Resource[] = [];
|
|
1072
1133
|
#resourcesTemplates: InputResourceTemplate[] = [];
|
|
1073
1134
|
#sessions: FastMCPSession<T>[] = [];
|
|
1074
|
-
#sseServer:
|
|
1135
|
+
#sseServer: null | SSEServer = null;
|
|
1136
|
+
|
|
1075
1137
|
#tools: Tool<T>[] = [];
|
|
1076
|
-
#authenticate: Authenticate<T> | undefined;
|
|
1077
1138
|
|
|
1078
1139
|
constructor(public options: ServerOptions<T>) {
|
|
1079
1140
|
super();
|
|
@@ -1082,15 +1143,13 @@ export class FastMCP<T extends Record<string, unknown> | undefined = undefined>
|
|
|
1082
1143
|
this.#authenticate = options.authenticate;
|
|
1083
1144
|
}
|
|
1084
1145
|
|
|
1085
|
-
public get sessions(): FastMCPSession<T>[] {
|
|
1086
|
-
return this.#sessions;
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
1146
|
/**
|
|
1090
|
-
* Adds a
|
|
1147
|
+
* Adds a prompt to the server.
|
|
1091
1148
|
*/
|
|
1092
|
-
public
|
|
1093
|
-
|
|
1149
|
+
public addPrompt<const Args extends InputPromptArgument[]>(
|
|
1150
|
+
prompt: InputPrompt<Args>,
|
|
1151
|
+
) {
|
|
1152
|
+
this.#prompts.push(prompt);
|
|
1094
1153
|
}
|
|
1095
1154
|
|
|
1096
1155
|
/**
|
|
@@ -1110,12 +1169,10 @@ export class FastMCP<T extends Record<string, unknown> | undefined = undefined>
|
|
|
1110
1169
|
}
|
|
1111
1170
|
|
|
1112
1171
|
/**
|
|
1113
|
-
* Adds a
|
|
1172
|
+
* Adds a tool to the server.
|
|
1114
1173
|
*/
|
|
1115
|
-
public
|
|
1116
|
-
|
|
1117
|
-
) {
|
|
1118
|
-
this.#prompts.push(prompt);
|
|
1174
|
+
public addTool<Params extends ToolParameters>(tool: Tool<T, Params>) {
|
|
1175
|
+
this.#tools.push(tool as unknown as Tool<T>);
|
|
1119
1176
|
}
|
|
1120
1177
|
|
|
1121
1178
|
/**
|
|
@@ -1123,11 +1180,11 @@ export class FastMCP<T extends Record<string, unknown> | undefined = undefined>
|
|
|
1123
1180
|
*/
|
|
1124
1181
|
public async start(
|
|
1125
1182
|
options:
|
|
1126
|
-
| { transportType: "stdio" }
|
|
1127
1183
|
| {
|
|
1128
|
-
transportType: "sse";
|
|
1129
1184
|
sse: { endpoint: `/${string}`; port: number };
|
|
1130
|
-
|
|
1185
|
+
transportType: "sse";
|
|
1186
|
+
}
|
|
1187
|
+
| { transportType: "stdio" } = {
|
|
1131
1188
|
transportType: "stdio",
|
|
1132
1189
|
},
|
|
1133
1190
|
) {
|
|
@@ -1135,12 +1192,13 @@ export class FastMCP<T extends Record<string, unknown> | undefined = undefined>
|
|
|
1135
1192
|
const transport = new StdioServerTransport();
|
|
1136
1193
|
|
|
1137
1194
|
const session = new FastMCPSession<T>({
|
|
1195
|
+
instructions: this.#options.instructions,
|
|
1138
1196
|
name: this.#options.name,
|
|
1139
|
-
|
|
1140
|
-
tools: this.#tools,
|
|
1197
|
+
prompts: this.#prompts,
|
|
1141
1198
|
resources: this.#resources,
|
|
1142
1199
|
resourcesTemplates: this.#resourcesTemplates,
|
|
1143
|
-
|
|
1200
|
+
tools: this.#tools,
|
|
1201
|
+
version: this.#options.version,
|
|
1144
1202
|
});
|
|
1145
1203
|
|
|
1146
1204
|
await session.connect(transport);
|
|
@@ -1150,11 +1208,8 @@ export class FastMCP<T extends Record<string, unknown> | undefined = undefined>
|
|
|
1150
1208
|
this.emit("connect", {
|
|
1151
1209
|
session,
|
|
1152
1210
|
});
|
|
1153
|
-
|
|
1154
1211
|
} else if (options.transportType === "sse") {
|
|
1155
1212
|
this.#sseServer = await startSSEServer<FastMCPSession<T>>({
|
|
1156
|
-
endpoint: options.sse.endpoint as `/${string}`,
|
|
1157
|
-
port: options.sse.port,
|
|
1158
1213
|
createServer: async (request) => {
|
|
1159
1214
|
let auth: T | undefined;
|
|
1160
1215
|
|
|
@@ -1165,13 +1220,14 @@ export class FastMCP<T extends Record<string, unknown> | undefined = undefined>
|
|
|
1165
1220
|
return new FastMCPSession<T>({
|
|
1166
1221
|
auth,
|
|
1167
1222
|
name: this.#options.name,
|
|
1168
|
-
|
|
1169
|
-
tools: this.#tools,
|
|
1223
|
+
prompts: this.#prompts,
|
|
1170
1224
|
resources: this.#resources,
|
|
1171
1225
|
resourcesTemplates: this.#resourcesTemplates,
|
|
1172
|
-
|
|
1226
|
+
tools: this.#tools,
|
|
1227
|
+
version: this.#options.version,
|
|
1173
1228
|
});
|
|
1174
1229
|
},
|
|
1230
|
+
endpoint: options.sse.endpoint as `/${string}`,
|
|
1175
1231
|
onClose: (session) => {
|
|
1176
1232
|
this.emit("disconnect", {
|
|
1177
1233
|
session,
|
|
@@ -1184,6 +1240,7 @@ export class FastMCP<T extends Record<string, unknown> | undefined = undefined>
|
|
|
1184
1240
|
session,
|
|
1185
1241
|
});
|
|
1186
1242
|
},
|
|
1243
|
+
port: options.sse.port,
|
|
1187
1244
|
});
|
|
1188
1245
|
|
|
1189
1246
|
console.info(
|
|
@@ -1206,11 +1263,11 @@ export class FastMCP<T extends Record<string, unknown> | undefined = undefined>
|
|
|
1206
1263
|
|
|
1207
1264
|
export type { Context };
|
|
1208
1265
|
export type { Tool, ToolParameters };
|
|
1209
|
-
export type { Content,
|
|
1266
|
+
export type { Content, ContentResult, ImageContent, TextContent };
|
|
1210
1267
|
export type { Progress, SerializableValue };
|
|
1211
1268
|
export type { Resource, ResourceResult };
|
|
1212
1269
|
export type { ResourceTemplate, ResourceTemplateArgument };
|
|
1213
1270
|
export type { Prompt, PromptArgument };
|
|
1214
1271
|
export type { InputPrompt, InputPromptArgument };
|
|
1215
|
-
export type {
|
|
1272
|
+
export type { LoggingLevel, ServerOptions };
|
|
1216
1273
|
export type { FastMCPEvents, FastMCPSessionEvents };
|