fastmcp 2.1.4 → 2.2.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 +110 -0
- package/dist/FastMCP.d.ts +21 -2
- package/dist/FastMCP.js +181 -48
- package/dist/FastMCP.js.map +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/FastMCP.test.ts +144 -5
- package/src/FastMCP.ts +257 -59
- package/src/examples/addition.ts +56 -0
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ A TypeScript framework for building [MCP](https://glama.ai/mcp) servers capable
|
|
|
13
13
|
- [Sessions](#sessions)
|
|
14
14
|
- [Image content](#returning-an-image)
|
|
15
15
|
- [Audio content](#returning-an-audio)
|
|
16
|
+
- [Embedded](#embedded-resources)
|
|
16
17
|
- [Logging](#logging)
|
|
17
18
|
- [Error handling](#errors)
|
|
18
19
|
- [HTTP Streaming](#http-streaming) (with SSE compatibility)
|
|
@@ -915,6 +916,114 @@ server.addResourceTemplate({
|
|
|
915
916
|
});
|
|
916
917
|
```
|
|
917
918
|
|
|
919
|
+
### Embedded Resources
|
|
920
|
+
|
|
921
|
+
FastMCP provides a convenient `embedded()` method that simplifies including resources in tool responses. This feature reduces code duplication and makes it easier to reference resources from within tools.
|
|
922
|
+
|
|
923
|
+
#### Basic Usage
|
|
924
|
+
|
|
925
|
+
```js
|
|
926
|
+
server.addTool({
|
|
927
|
+
name: "get_user_data",
|
|
928
|
+
description: "Retrieve user information",
|
|
929
|
+
parameters: z.object({
|
|
930
|
+
userId: z.string(),
|
|
931
|
+
}),
|
|
932
|
+
execute: async (args) => {
|
|
933
|
+
return {
|
|
934
|
+
content: [
|
|
935
|
+
{
|
|
936
|
+
type: "resource",
|
|
937
|
+
resource: await server.embedded(`user://profile/${args.userId}`),
|
|
938
|
+
},
|
|
939
|
+
],
|
|
940
|
+
};
|
|
941
|
+
},
|
|
942
|
+
});
|
|
943
|
+
```
|
|
944
|
+
|
|
945
|
+
#### Working with Resource Templates
|
|
946
|
+
|
|
947
|
+
The `embedded()` method works seamlessly with resource templates:
|
|
948
|
+
|
|
949
|
+
```js
|
|
950
|
+
// Define a resource template
|
|
951
|
+
server.addResourceTemplate({
|
|
952
|
+
uriTemplate: "docs://project/{section}",
|
|
953
|
+
name: "Project Documentation",
|
|
954
|
+
mimeType: "text/markdown",
|
|
955
|
+
arguments: [
|
|
956
|
+
{
|
|
957
|
+
name: "section",
|
|
958
|
+
required: true,
|
|
959
|
+
},
|
|
960
|
+
],
|
|
961
|
+
async load(args) {
|
|
962
|
+
const docs = {
|
|
963
|
+
"getting-started": "# Getting Started\n\nWelcome to our project!",
|
|
964
|
+
"api-reference": "# API Reference\n\nAuthentication is required.",
|
|
965
|
+
};
|
|
966
|
+
return {
|
|
967
|
+
text: docs[args.section] || "Documentation not found",
|
|
968
|
+
};
|
|
969
|
+
},
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
// Use embedded resources in a tool
|
|
973
|
+
server.addTool({
|
|
974
|
+
name: "get_documentation",
|
|
975
|
+
description: "Retrieve project documentation",
|
|
976
|
+
parameters: z.object({
|
|
977
|
+
section: z.enum(["getting-started", "api-reference"]),
|
|
978
|
+
}),
|
|
979
|
+
execute: async (args) => {
|
|
980
|
+
return {
|
|
981
|
+
content: [
|
|
982
|
+
{
|
|
983
|
+
type: "resource",
|
|
984
|
+
resource: await server.embedded(`docs://project/${args.section}`),
|
|
985
|
+
},
|
|
986
|
+
],
|
|
987
|
+
};
|
|
988
|
+
},
|
|
989
|
+
});
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
#### Working with Direct Resources
|
|
993
|
+
|
|
994
|
+
It also works with directly defined resources:
|
|
995
|
+
|
|
996
|
+
```js
|
|
997
|
+
// Define a direct resource
|
|
998
|
+
server.addResource({
|
|
999
|
+
uri: "system://status",
|
|
1000
|
+
name: "System Status",
|
|
1001
|
+
mimeType: "text/plain",
|
|
1002
|
+
async load() {
|
|
1003
|
+
return {
|
|
1004
|
+
text: "System operational",
|
|
1005
|
+
};
|
|
1006
|
+
},
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
// Use in a tool
|
|
1010
|
+
server.addTool({
|
|
1011
|
+
name: "get_system_status",
|
|
1012
|
+
description: "Get current system status",
|
|
1013
|
+
parameters: z.object({}),
|
|
1014
|
+
execute: async () => {
|
|
1015
|
+
return {
|
|
1016
|
+
content: [
|
|
1017
|
+
{
|
|
1018
|
+
type: "resource",
|
|
1019
|
+
resource: await server.embedded("system://status"),
|
|
1020
|
+
},
|
|
1021
|
+
],
|
|
1022
|
+
};
|
|
1023
|
+
},
|
|
1024
|
+
});
|
|
1025
|
+
```
|
|
1026
|
+
|
|
918
1027
|
### Prompts
|
|
919
1028
|
|
|
920
1029
|
[Prompts](https://modelcontextprotocol.io/docs/concepts/prompts) enable servers to define reusable prompt templates and workflows that clients can easily surface to users and LLMs. They provide a powerful way to standardize and share common LLM interactions.
|
|
@@ -1197,6 +1306,7 @@ Follow the guide https://modelcontextprotocol.io/quickstart/user and add the fol
|
|
|
1197
1306
|
- [aiamblichus/mcp-chat-adapter](https://github.com/aiamblichus/mcp-chat-adapter) – provides a clean interface for LLMs to use chat completion
|
|
1198
1307
|
- [eyaltoledano/claude-task-master](https://github.com/eyaltoledano/claude-task-master) – advanced AI project/task manager powered by FastMCP
|
|
1199
1308
|
- [cswkim/discogs-mcp-server](https://github.com/cswkim/discogs-mcp-server) - connects to the Discogs API for interacting with your music collection
|
|
1309
|
+
- [Panzer-Jack/feuse-mcp](https://github.com/Panzer-Jack/feuse-mcp) - Frontend Useful MCP Tools - Essential utilities for web developers to automate API integration and code generation
|
|
1200
1310
|
|
|
1201
1311
|
## Acknowledgements
|
|
1202
1312
|
|
package/dist/FastMCP.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ type FastMCPSessionEvents = {
|
|
|
22
22
|
error: (event: {
|
|
23
23
|
error: Error;
|
|
24
24
|
}) => void;
|
|
25
|
+
ready: () => void;
|
|
25
26
|
rootsChanged: (event: {
|
|
26
27
|
roots: Root[];
|
|
27
28
|
}) => void;
|
|
@@ -94,7 +95,16 @@ type AudioContent = {
|
|
|
94
95
|
mimeType: string;
|
|
95
96
|
type: "audio";
|
|
96
97
|
};
|
|
97
|
-
type
|
|
98
|
+
type ResourceContent = {
|
|
99
|
+
resource: {
|
|
100
|
+
blob?: string;
|
|
101
|
+
mimeType?: string;
|
|
102
|
+
text?: string;
|
|
103
|
+
uri: string;
|
|
104
|
+
};
|
|
105
|
+
type: "resource";
|
|
106
|
+
};
|
|
107
|
+
type Content = AudioContent | ImageContent | ResourceContent | TextContent;
|
|
98
108
|
type ContentResult = {
|
|
99
109
|
content: Content[];
|
|
100
110
|
isError?: boolean;
|
|
@@ -264,7 +274,7 @@ type Tool<T extends FastMCPSessionAuth, Params extends ToolParameters = ToolPara
|
|
|
264
274
|
streamingHint?: boolean;
|
|
265
275
|
} & ToolAnnotations;
|
|
266
276
|
description?: string;
|
|
267
|
-
execute: (args: StandardSchemaV1.InferOutput<Params>, context: Context<T>) => Promise<AudioContent | ContentResult | ImageContent | string | TextContent | void>;
|
|
277
|
+
execute: (args: StandardSchemaV1.InferOutput<Params>, context: Context<T>) => Promise<AudioContent | ContentResult | ImageContent | ResourceContent | string | TextContent | void>;
|
|
268
278
|
name: string;
|
|
269
279
|
parameters?: Params;
|
|
270
280
|
timeoutMs?: number;
|
|
@@ -316,6 +326,7 @@ declare class FastMCPSessionEventEmitter extends FastMCPSessionEventEmitterBase
|
|
|
316
326
|
declare class FastMCPSession<T extends FastMCPSessionAuth = FastMCPSessionAuth> extends FastMCPSessionEventEmitter {
|
|
317
327
|
#private;
|
|
318
328
|
get clientCapabilities(): ClientCapabilities | null;
|
|
329
|
+
get isReady(): boolean;
|
|
319
330
|
get loggingLevel(): LoggingLevel;
|
|
320
331
|
get roots(): Root[];
|
|
321
332
|
get server(): Server;
|
|
@@ -334,6 +345,7 @@ declare class FastMCPSession<T extends FastMCPSessionAuth = FastMCPSessionAuth>
|
|
|
334
345
|
close(): Promise<void>;
|
|
335
346
|
connect(transport: Transport): Promise<void>;
|
|
336
347
|
requestSampling(message: z.infer<typeof CreateMessageRequestSchema>["params"]): Promise<SamplingResponse>;
|
|
348
|
+
waitForReady(): Promise<void>;
|
|
337
349
|
private addPrompt;
|
|
338
350
|
private addResource;
|
|
339
351
|
private addResourceTemplate;
|
|
@@ -373,6 +385,13 @@ declare class FastMCP<T extends Record<string, unknown> | undefined = undefined>
|
|
|
373
385
|
* Adds a tool to the server.
|
|
374
386
|
*/
|
|
375
387
|
addTool<Params extends ToolParameters>(tool: Tool<T, Params>): void;
|
|
388
|
+
/**
|
|
389
|
+
* Embeds a resource by URI, making it easy to include resources in tool responses.
|
|
390
|
+
*
|
|
391
|
+
* @param uri - The URI of the resource to embed
|
|
392
|
+
* @returns Promise<ResourceContent> - The embedded resource content
|
|
393
|
+
*/
|
|
394
|
+
embedded(uri: string): Promise<ResourceContent["resource"]>;
|
|
376
395
|
/**
|
|
377
396
|
* Starts the server.
|
|
378
397
|
*/
|
package/dist/FastMCP.js
CHANGED
|
@@ -171,10 +171,20 @@ var AudioContentZodSchema = z.object({
|
|
|
171
171
|
mimeType: z.string(),
|
|
172
172
|
type: z.literal("audio")
|
|
173
173
|
}).strict();
|
|
174
|
+
var ResourceContentZodSchema = z.object({
|
|
175
|
+
resource: z.object({
|
|
176
|
+
blob: z.string().optional(),
|
|
177
|
+
mimeType: z.string().optional(),
|
|
178
|
+
text: z.string().optional(),
|
|
179
|
+
uri: z.string()
|
|
180
|
+
}),
|
|
181
|
+
type: z.literal("resource")
|
|
182
|
+
}).strict();
|
|
174
183
|
var ContentZodSchema = z.discriminatedUnion("type", [
|
|
175
184
|
TextContentZodSchema,
|
|
176
185
|
ImageContentZodSchema,
|
|
177
|
-
AudioContentZodSchema
|
|
186
|
+
AudioContentZodSchema,
|
|
187
|
+
ResourceContentZodSchema
|
|
178
188
|
]);
|
|
179
189
|
var ContentResultZodSchema = z.object({
|
|
180
190
|
content: ContentZodSchema.array(),
|
|
@@ -201,6 +211,9 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
|
|
|
201
211
|
get clientCapabilities() {
|
|
202
212
|
return this.#clientCapabilities ?? null;
|
|
203
213
|
}
|
|
214
|
+
get isReady() {
|
|
215
|
+
return this.#connectionState === "ready";
|
|
216
|
+
}
|
|
204
217
|
get loggingLevel() {
|
|
205
218
|
return this.#loggingLevel;
|
|
206
219
|
}
|
|
@@ -213,6 +226,7 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
|
|
|
213
226
|
#auth;
|
|
214
227
|
#capabilities = {};
|
|
215
228
|
#clientCapabilities;
|
|
229
|
+
#connectionState = "connecting";
|
|
216
230
|
#loggingLevel = "info";
|
|
217
231
|
#pingConfig;
|
|
218
232
|
#pingInterval = null;
|
|
@@ -279,6 +293,7 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
|
|
|
279
293
|
}
|
|
280
294
|
}
|
|
281
295
|
async close() {
|
|
296
|
+
this.#connectionState = "closed";
|
|
282
297
|
if (this.#pingInterval) {
|
|
283
298
|
clearInterval(this.#pingInterval);
|
|
284
299
|
}
|
|
@@ -292,64 +307,105 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
|
|
|
292
307
|
if (this.#server.transport) {
|
|
293
308
|
throw new UnexpectedStateError("Server is already connected");
|
|
294
309
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
310
|
+
this.#connectionState = "connecting";
|
|
311
|
+
try {
|
|
312
|
+
await this.#server.connect(transport);
|
|
313
|
+
let attempt = 0;
|
|
314
|
+
while (attempt++ < 10) {
|
|
315
|
+
const capabilities = this.#server.getClientCapabilities();
|
|
316
|
+
if (capabilities) {
|
|
317
|
+
this.#clientCapabilities = capabilities;
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
await delay(100);
|
|
302
321
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
console.error(
|
|
319
|
-
`[FastMCP error] received error listing roots.
|
|
322
|
+
if (!this.#clientCapabilities) {
|
|
323
|
+
console.warn("[FastMCP warning] could not infer client capabilities");
|
|
324
|
+
}
|
|
325
|
+
if (this.#clientCapabilities?.roots?.listChanged && typeof this.#server.listRoots === "function") {
|
|
326
|
+
try {
|
|
327
|
+
const roots = await this.#server.listRoots();
|
|
328
|
+
this.#roots = roots.roots;
|
|
329
|
+
} catch (e) {
|
|
330
|
+
if (e instanceof McpError && e.code === ErrorCode.MethodNotFound) {
|
|
331
|
+
console.debug(
|
|
332
|
+
"[FastMCP debug] listRoots method not supported by client"
|
|
333
|
+
);
|
|
334
|
+
} else {
|
|
335
|
+
console.error(
|
|
336
|
+
`[FastMCP error] received error listing roots.
|
|
320
337
|
|
|
321
338
|
${e instanceof Error ? e.stack : JSON.stringify(e)}`
|
|
322
|
-
|
|
339
|
+
);
|
|
340
|
+
}
|
|
323
341
|
}
|
|
324
342
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
)
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
343
|
+
if (this.#clientCapabilities) {
|
|
344
|
+
const pingConfig = this.#getPingConfig(transport);
|
|
345
|
+
if (pingConfig.enabled) {
|
|
346
|
+
this.#pingInterval = setInterval(async () => {
|
|
347
|
+
try {
|
|
348
|
+
await this.#server.ping();
|
|
349
|
+
} catch {
|
|
350
|
+
const logLevel = pingConfig.logLevel;
|
|
351
|
+
if (logLevel === "debug") {
|
|
352
|
+
console.debug("[FastMCP debug] server ping failed");
|
|
353
|
+
} else if (logLevel === "warning") {
|
|
354
|
+
console.warn(
|
|
355
|
+
"[FastMCP warning] server is not responding to ping"
|
|
356
|
+
);
|
|
357
|
+
} else if (logLevel === "error") {
|
|
358
|
+
console.error(
|
|
359
|
+
"[FastMCP error] server is not responding to ping"
|
|
360
|
+
);
|
|
361
|
+
} else {
|
|
362
|
+
console.info("[FastMCP info] server ping failed");
|
|
363
|
+
}
|
|
344
364
|
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
365
|
+
}, pingConfig.intervalMs);
|
|
366
|
+
}
|
|
347
367
|
}
|
|
368
|
+
this.#connectionState = "ready";
|
|
369
|
+
this.emit("ready");
|
|
370
|
+
} catch (error) {
|
|
371
|
+
this.#connectionState = "error";
|
|
372
|
+
const errorEvent = {
|
|
373
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
374
|
+
};
|
|
375
|
+
this.emit("error", errorEvent);
|
|
376
|
+
throw error;
|
|
348
377
|
}
|
|
349
378
|
}
|
|
350
379
|
async requestSampling(message) {
|
|
351
380
|
return this.#server.createMessage(message);
|
|
352
381
|
}
|
|
382
|
+
waitForReady() {
|
|
383
|
+
if (this.isReady) {
|
|
384
|
+
return Promise.resolve();
|
|
385
|
+
}
|
|
386
|
+
if (this.#connectionState === "error" || this.#connectionState === "closed") {
|
|
387
|
+
return Promise.reject(
|
|
388
|
+
new Error(`Connection is in ${this.#connectionState} state`)
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
return new Promise((resolve, reject) => {
|
|
392
|
+
const timeout = setTimeout(() => {
|
|
393
|
+
reject(
|
|
394
|
+
new Error(
|
|
395
|
+
"Connection timeout: Session failed to become ready within 5 seconds"
|
|
396
|
+
)
|
|
397
|
+
);
|
|
398
|
+
}, 5e3);
|
|
399
|
+
this.once("ready", () => {
|
|
400
|
+
clearTimeout(timeout);
|
|
401
|
+
resolve();
|
|
402
|
+
});
|
|
403
|
+
this.once("error", (event) => {
|
|
404
|
+
clearTimeout(timeout);
|
|
405
|
+
reject(event.error);
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
}
|
|
353
409
|
#getPingConfig(transport) {
|
|
354
410
|
const pingConfig = this.#pingConfig || {};
|
|
355
411
|
let defaultEnabled = false;
|
|
@@ -889,6 +945,66 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
889
945
|
addTool(tool) {
|
|
890
946
|
this.#tools.push(tool);
|
|
891
947
|
}
|
|
948
|
+
/**
|
|
949
|
+
* Embeds a resource by URI, making it easy to include resources in tool responses.
|
|
950
|
+
*
|
|
951
|
+
* @param uri - The URI of the resource to embed
|
|
952
|
+
* @returns Promise<ResourceContent> - The embedded resource content
|
|
953
|
+
*/
|
|
954
|
+
async embedded(uri) {
|
|
955
|
+
const directResource = this.#resources.find(
|
|
956
|
+
(resource) => resource.uri === uri
|
|
957
|
+
);
|
|
958
|
+
if (directResource) {
|
|
959
|
+
const result = await directResource.load();
|
|
960
|
+
const results = Array.isArray(result) ? result : [result];
|
|
961
|
+
const firstResult = results[0];
|
|
962
|
+
const resourceData = {
|
|
963
|
+
mimeType: directResource.mimeType,
|
|
964
|
+
uri
|
|
965
|
+
};
|
|
966
|
+
if ("text" in firstResult) {
|
|
967
|
+
resourceData.text = firstResult.text;
|
|
968
|
+
}
|
|
969
|
+
if ("blob" in firstResult) {
|
|
970
|
+
resourceData.blob = firstResult.blob;
|
|
971
|
+
}
|
|
972
|
+
return resourceData;
|
|
973
|
+
}
|
|
974
|
+
for (const template of this.#resourcesTemplates) {
|
|
975
|
+
const templateBase = template.uriTemplate.split("{")[0];
|
|
976
|
+
if (uri.startsWith(templateBase)) {
|
|
977
|
+
const params = {};
|
|
978
|
+
const templateParts = template.uriTemplate.split("/");
|
|
979
|
+
const uriParts = uri.split("/");
|
|
980
|
+
for (let i = 0; i < templateParts.length; i++) {
|
|
981
|
+
const templatePart = templateParts[i];
|
|
982
|
+
if (templatePart?.startsWith("{") && templatePart.endsWith("}")) {
|
|
983
|
+
const paramName = templatePart.slice(1, -1);
|
|
984
|
+
const paramValue = uriParts[i];
|
|
985
|
+
if (paramValue) {
|
|
986
|
+
params[paramName] = paramValue;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
const result = await template.load(
|
|
991
|
+
params
|
|
992
|
+
);
|
|
993
|
+
const resourceData = {
|
|
994
|
+
mimeType: template.mimeType,
|
|
995
|
+
uri
|
|
996
|
+
};
|
|
997
|
+
if ("text" in result) {
|
|
998
|
+
resourceData.text = result.text;
|
|
999
|
+
}
|
|
1000
|
+
if ("blob" in result) {
|
|
1001
|
+
resourceData.blob = result.blob;
|
|
1002
|
+
}
|
|
1003
|
+
return resourceData;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
throw new UnexpectedStateError(`Resource not found: ${uri}`, { uri });
|
|
1007
|
+
}
|
|
892
1008
|
/**
|
|
893
1009
|
* Starts the server.
|
|
894
1010
|
*/
|
|
@@ -948,13 +1064,30 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
948
1064
|
const enabled = healthConfig.enabled === void 0 ? true : healthConfig.enabled;
|
|
949
1065
|
if (enabled) {
|
|
950
1066
|
const path = healthConfig.path ?? "/health";
|
|
1067
|
+
const url = new URL(req.url || "", "http://localhost");
|
|
951
1068
|
try {
|
|
952
|
-
if (req.method === "GET" &&
|
|
1069
|
+
if (req.method === "GET" && url.pathname === path) {
|
|
953
1070
|
res.writeHead(healthConfig.status ?? 200, {
|
|
954
1071
|
"Content-Type": "text/plain"
|
|
955
1072
|
}).end(healthConfig.message ?? "ok");
|
|
956
1073
|
return;
|
|
957
1074
|
}
|
|
1075
|
+
if (req.method === "GET" && url.pathname === "/ready") {
|
|
1076
|
+
const readySessions = this.#sessions.filter(
|
|
1077
|
+
(s) => s.isReady
|
|
1078
|
+
).length;
|
|
1079
|
+
const totalSessions = this.#sessions.length;
|
|
1080
|
+
const allReady = readySessions === totalSessions && totalSessions > 0;
|
|
1081
|
+
const response = {
|
|
1082
|
+
ready: readySessions,
|
|
1083
|
+
status: allReady ? "ready" : totalSessions === 0 ? "no_sessions" : "initializing",
|
|
1084
|
+
total: totalSessions
|
|
1085
|
+
};
|
|
1086
|
+
res.writeHead(allReady ? 200 : 503, {
|
|
1087
|
+
"Content-Type": "application/json"
|
|
1088
|
+
}).end(JSON.stringify(response));
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
958
1091
|
} catch (error) {
|
|
959
1092
|
console.error("[FastMCP error] health endpoint error", error);
|
|
960
1093
|
}
|