fastmcp 1.27.7 → 2.1.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 -31
- package/dist/FastMCP.d.ts +41 -9
- package/dist/FastMCP.js +38 -46
- package/dist/FastMCP.js.map +1 -1
- package/jsr.json +1 -1
- package/package.json +9 -9
- package/src/FastMCP.test.ts +123 -26
- package/src/FastMCP.ts +106 -56
- package/src/examples/addition.ts +40 -4
package/src/FastMCP.ts
CHANGED
|
@@ -25,7 +25,7 @@ import { fileTypeFromBuffer } from "file-type";
|
|
|
25
25
|
import { readFile } from "fs/promises";
|
|
26
26
|
import Fuse from "fuse.js";
|
|
27
27
|
import http from "http";
|
|
28
|
-
import {
|
|
28
|
+
import { startHTTPServer } from "mcp-proxy";
|
|
29
29
|
import { StrictEventEmitter } from "strict-event-emitter-types";
|
|
30
30
|
import { setTimeout as delay } from "timers/promises";
|
|
31
31
|
import { fetch } from "undici";
|
|
@@ -180,6 +180,7 @@ type Context<T extends FastMCPSessionAuth> = {
|
|
|
180
180
|
};
|
|
181
181
|
reportProgress: (progress: Progress) => Promise<void>;
|
|
182
182
|
session: T | undefined;
|
|
183
|
+
streamContent: (content: Content | Content[]) => Promise<void>;
|
|
183
184
|
};
|
|
184
185
|
|
|
185
186
|
type Extra = unknown;
|
|
@@ -445,8 +446,44 @@ type ResourceTemplateArgumentsToObject<T extends { name: string }[]> = {
|
|
|
445
446
|
|
|
446
447
|
type ServerOptions<T extends FastMCPSessionAuth> = {
|
|
447
448
|
authenticate?: Authenticate<T>;
|
|
449
|
+
/**
|
|
450
|
+
* Configuration for the health-check endpoint that can be exposed when the
|
|
451
|
+
* server is running using the HTTP Stream transport. When enabled, the
|
|
452
|
+
* server will respond to an HTTP GET request with the configured path (by
|
|
453
|
+
* default "/health") rendering a plain-text response (by default "ok") and
|
|
454
|
+
* the configured status code (by default 200).
|
|
455
|
+
*
|
|
456
|
+
* The endpoint is only added when the server is started with
|
|
457
|
+
* `transportType: "httpStream"` – it is ignored for the stdio transport.
|
|
458
|
+
*/
|
|
459
|
+
health?: {
|
|
460
|
+
/**
|
|
461
|
+
* When set to `false` the health-check endpoint is disabled.
|
|
462
|
+
* @default true
|
|
463
|
+
*/
|
|
464
|
+
enabled?: boolean;
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Plain-text body returned by the endpoint.
|
|
468
|
+
* @default "ok"
|
|
469
|
+
*/
|
|
470
|
+
message?: string;
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* HTTP path that should be handled.
|
|
474
|
+
* @default "/health"
|
|
475
|
+
*/
|
|
476
|
+
path?: string;
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* HTTP response status that will be returned.
|
|
480
|
+
* @default 200
|
|
481
|
+
*/
|
|
482
|
+
status?: number;
|
|
483
|
+
};
|
|
448
484
|
instructions?: string;
|
|
449
485
|
name: string;
|
|
486
|
+
|
|
450
487
|
ping?: {
|
|
451
488
|
/**
|
|
452
489
|
* Whether ping should be enabled by default.
|
|
@@ -483,13 +520,19 @@ type Tool<
|
|
|
483
520
|
T extends FastMCPSessionAuth,
|
|
484
521
|
Params extends ToolParameters = ToolParameters,
|
|
485
522
|
> = {
|
|
486
|
-
annotations?:
|
|
523
|
+
annotations?: {
|
|
524
|
+
/**
|
|
525
|
+
* When true, the tool leverages incremental content streaming
|
|
526
|
+
* Return void for tools that handle all their output via streaming
|
|
527
|
+
*/
|
|
528
|
+
streamingHint?: boolean;
|
|
529
|
+
} & ToolAnnotations;
|
|
487
530
|
description?: string;
|
|
488
531
|
execute: (
|
|
489
532
|
args: StandardSchemaV1.InferOutput<Params>,
|
|
490
533
|
context: Context<T>,
|
|
491
534
|
) => Promise<
|
|
492
|
-
AudioContent | ContentResult | ImageContent | string | TextContent
|
|
535
|
+
AudioContent | ContentResult | ImageContent | string | TextContent | void
|
|
493
536
|
>;
|
|
494
537
|
name: string;
|
|
495
538
|
parameters?: Params;
|
|
@@ -767,7 +810,7 @@ export class FastMCPSession<
|
|
|
767
810
|
|
|
768
811
|
if ("type" in transport) {
|
|
769
812
|
// Enable by default for SSE and HTTP streaming
|
|
770
|
-
if (transport.type === "
|
|
813
|
+
if (transport.type === "httpStream") {
|
|
771
814
|
defaultEnabled = true;
|
|
772
815
|
}
|
|
773
816
|
}
|
|
@@ -1266,14 +1309,29 @@ export class FastMCPSession<
|
|
|
1266
1309
|
};
|
|
1267
1310
|
|
|
1268
1311
|
// Create a promise for tool execution
|
|
1312
|
+
// Streams partial results while a tool is still executing
|
|
1313
|
+
// Enables progressive rendering and real-time feedback
|
|
1314
|
+
const streamContent = async (content: Content | Content[]) => {
|
|
1315
|
+
const contentArray = Array.isArray(content) ? content : [content];
|
|
1316
|
+
|
|
1317
|
+
await this.#server.notification({
|
|
1318
|
+
method: "notifications/tool/streamContent",
|
|
1319
|
+
params: {
|
|
1320
|
+
content: contentArray,
|
|
1321
|
+
toolName: request.params.name,
|
|
1322
|
+
},
|
|
1323
|
+
});
|
|
1324
|
+
};
|
|
1325
|
+
|
|
1269
1326
|
const executeToolPromise = tool.execute(args, {
|
|
1270
1327
|
log,
|
|
1271
1328
|
reportProgress,
|
|
1272
1329
|
session: this.#auth,
|
|
1330
|
+
streamContent,
|
|
1273
1331
|
});
|
|
1274
1332
|
|
|
1275
1333
|
// Handle timeout if specified
|
|
1276
|
-
const maybeStringResult = await (tool.timeoutMs
|
|
1334
|
+
const maybeStringResult = (await (tool.timeoutMs
|
|
1277
1335
|
? Promise.race([
|
|
1278
1336
|
executeToolPromise,
|
|
1279
1337
|
new Promise<never>((_, reject) => {
|
|
@@ -1286,9 +1344,20 @@ export class FastMCPSession<
|
|
|
1286
1344
|
}, tool.timeoutMs);
|
|
1287
1345
|
}),
|
|
1288
1346
|
])
|
|
1289
|
-
: executeToolPromise)
|
|
1290
|
-
|
|
1291
|
-
|
|
1347
|
+
: executeToolPromise)) as
|
|
1348
|
+
| AudioContent
|
|
1349
|
+
| ContentResult
|
|
1350
|
+
| ImageContent
|
|
1351
|
+
| null
|
|
1352
|
+
| string
|
|
1353
|
+
| TextContent
|
|
1354
|
+
| undefined;
|
|
1355
|
+
|
|
1356
|
+
if (maybeStringResult === undefined || maybeStringResult === null) {
|
|
1357
|
+
result = ContentResultZodSchema.parse({
|
|
1358
|
+
content: [],
|
|
1359
|
+
});
|
|
1360
|
+
} else if (typeof maybeStringResult === "string") {
|
|
1292
1361
|
result = ContentResultZodSchema.parse({
|
|
1293
1362
|
content: [{ text: maybeStringResult, type: "text" }],
|
|
1294
1363
|
});
|
|
@@ -1339,7 +1408,6 @@ export class FastMCP<
|
|
|
1339
1408
|
#resources: Resource[] = [];
|
|
1340
1409
|
#resourcesTemplates: InputResourceTemplate[] = [];
|
|
1341
1410
|
#sessions: FastMCPSession<T>[] = [];
|
|
1342
|
-
#sseServer: null | SSEServer = null;
|
|
1343
1411
|
|
|
1344
1412
|
#tools: Tool<T>[] = [];
|
|
1345
1413
|
|
|
@@ -1388,13 +1456,9 @@ export class FastMCP<
|
|
|
1388
1456
|
public async start(
|
|
1389
1457
|
options:
|
|
1390
1458
|
| {
|
|
1391
|
-
httpStream: {
|
|
1459
|
+
httpStream: { port: number };
|
|
1392
1460
|
transportType: "httpStream";
|
|
1393
1461
|
}
|
|
1394
|
-
| {
|
|
1395
|
-
sse: { endpoint: `/${string}`; port: number };
|
|
1396
|
-
transportType: "sse";
|
|
1397
|
-
}
|
|
1398
1462
|
| { transportType: "stdio" } = {
|
|
1399
1463
|
transportType: "stdio",
|
|
1400
1464
|
},
|
|
@@ -1421,8 +1485,8 @@ export class FastMCP<
|
|
|
1421
1485
|
this.emit("connect", {
|
|
1422
1486
|
session,
|
|
1423
1487
|
});
|
|
1424
|
-
} else if (options.transportType === "
|
|
1425
|
-
this.#
|
|
1488
|
+
} else if (options.transportType === "httpStream") {
|
|
1489
|
+
this.#httpStreamServer = await startHTTPServer<FastMCPSession<T>>({
|
|
1426
1490
|
createServer: async (request) => {
|
|
1427
1491
|
let auth: T | undefined;
|
|
1428
1492
|
|
|
@@ -1442,7 +1506,6 @@ export class FastMCP<
|
|
|
1442
1506
|
version: this.#options.version,
|
|
1443
1507
|
});
|
|
1444
1508
|
},
|
|
1445
|
-
endpoint: options.sse.endpoint as `/${string}`,
|
|
1446
1509
|
onClose: (session) => {
|
|
1447
1510
|
this.emit("disconnect", {
|
|
1448
1511
|
session,
|
|
@@ -1455,51 +1518,41 @@ export class FastMCP<
|
|
|
1455
1518
|
session,
|
|
1456
1519
|
});
|
|
1457
1520
|
},
|
|
1458
|
-
|
|
1459
|
-
|
|
1521
|
+
onUnhandledRequest: async (req, res) => {
|
|
1522
|
+
const healthConfig = this.#options.health ?? {};
|
|
1460
1523
|
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
);
|
|
1464
|
-
} else if (options.transportType === "httpStream") {
|
|
1465
|
-
this.#httpStreamServer = await startHTTPStreamServer<FastMCPSession<T>>({
|
|
1466
|
-
createServer: async (request) => {
|
|
1467
|
-
let auth: T | undefined;
|
|
1524
|
+
const enabled =
|
|
1525
|
+
healthConfig.enabled === undefined ? true : healthConfig.enabled;
|
|
1468
1526
|
|
|
1469
|
-
if (
|
|
1470
|
-
|
|
1471
|
-
}
|
|
1527
|
+
if (enabled) {
|
|
1528
|
+
const path = healthConfig.path ?? "/health";
|
|
1472
1529
|
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
});
|
|
1484
|
-
},
|
|
1485
|
-
endpoint: options.httpStream.endpoint as `/${string}`,
|
|
1486
|
-
onClose: (session) => {
|
|
1487
|
-
this.emit("disconnect", {
|
|
1488
|
-
session,
|
|
1489
|
-
});
|
|
1490
|
-
},
|
|
1491
|
-
onConnect: async (session) => {
|
|
1492
|
-
this.#sessions.push(session);
|
|
1530
|
+
try {
|
|
1531
|
+
if (
|
|
1532
|
+
req.method === "GET" &&
|
|
1533
|
+
new URL(req.url || "", "http://localhost").pathname === path
|
|
1534
|
+
) {
|
|
1535
|
+
res
|
|
1536
|
+
.writeHead(healthConfig.status ?? 200, {
|
|
1537
|
+
"Content-Type": "text/plain",
|
|
1538
|
+
})
|
|
1539
|
+
.end(healthConfig.message ?? "ok");
|
|
1493
1540
|
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
} catch (error) {
|
|
1544
|
+
console.error("[FastMCP error] health endpoint error", error);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
// If the request was not handled above, return 404
|
|
1549
|
+
res.writeHead(404).end();
|
|
1497
1550
|
},
|
|
1498
1551
|
port: options.httpStream.port,
|
|
1499
1552
|
});
|
|
1500
1553
|
|
|
1501
1554
|
console.info(
|
|
1502
|
-
`[FastMCP info] server is running on HTTP Stream at http://localhost:${options.httpStream.port}
|
|
1555
|
+
`[FastMCP info] server is running on HTTP Stream at http://localhost:${options.httpStream.port}/stream`,
|
|
1503
1556
|
);
|
|
1504
1557
|
} else {
|
|
1505
1558
|
throw new Error("Invalid transport type");
|
|
@@ -1510,9 +1563,6 @@ export class FastMCP<
|
|
|
1510
1563
|
* Stops the server.
|
|
1511
1564
|
*/
|
|
1512
1565
|
public async stop() {
|
|
1513
|
-
if (this.#sseServer) {
|
|
1514
|
-
await this.#sseServer.close();
|
|
1515
|
-
}
|
|
1516
1566
|
if (this.#httpStreamServer) {
|
|
1517
1567
|
await this.#httpStreamServer.close();
|
|
1518
1568
|
}
|
package/src/examples/addition.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Example FastMCP server demonstrating core functionality plus streaming output.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Features demonstrated:
|
|
5
|
+
* - Basic tool with type-safe parameters
|
|
6
|
+
* - Streaming-enabled tool for incremental output
|
|
7
|
+
* - Advanced tool annotations
|
|
8
|
+
*
|
|
9
|
+
* For a complete project template, see https://github.com/punkpeye/fastmcp-boilerplate
|
|
6
10
|
*/
|
|
7
11
|
import { type } from "arktype";
|
|
8
12
|
import * as v from "valibot";
|
|
@@ -118,6 +122,39 @@ server.addResource({
|
|
|
118
122
|
uri: "file:///logs/app.log",
|
|
119
123
|
});
|
|
120
124
|
|
|
125
|
+
server.addTool({
|
|
126
|
+
annotations: {
|
|
127
|
+
openWorldHint: false,
|
|
128
|
+
readOnlyHint: true,
|
|
129
|
+
streamingHint: true,
|
|
130
|
+
},
|
|
131
|
+
description: "Generate a poem line by line with streaming output",
|
|
132
|
+
execute: async (args, context) => {
|
|
133
|
+
const { theme } = args;
|
|
134
|
+
const lines = [
|
|
135
|
+
`Poem about ${theme} - line 1`,
|
|
136
|
+
`Poem about ${theme} - line 2`,
|
|
137
|
+
`Poem about ${theme} - line 3`,
|
|
138
|
+
`Poem about ${theme} - line 4`,
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
for (const line of lines) {
|
|
142
|
+
await context.streamContent({
|
|
143
|
+
text: line,
|
|
144
|
+
type: "text",
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return;
|
|
151
|
+
},
|
|
152
|
+
name: "stream-poem",
|
|
153
|
+
parameters: z.object({
|
|
154
|
+
theme: z.string().describe("Theme for the poem"),
|
|
155
|
+
}),
|
|
156
|
+
});
|
|
157
|
+
|
|
121
158
|
server.addPrompt({
|
|
122
159
|
arguments: [
|
|
123
160
|
{
|
|
@@ -144,7 +181,6 @@ if (transportType === "httpStream") {
|
|
|
144
181
|
|
|
145
182
|
server.start({
|
|
146
183
|
httpStream: {
|
|
147
|
-
endpoint: "/stream",
|
|
148
184
|
port: PORT,
|
|
149
185
|
},
|
|
150
186
|
transportType: "httpStream",
|