fastmcp 1.27.6 → 2.0.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 +90 -31
- package/dist/FastMCP.d.ts +16 -11
- package/dist/FastMCP.js +31 -48
- package/dist/FastMCP.js.map +1 -1
- package/jsr.json +1 -1
- package/package.json +9 -9
- package/src/FastMCP.test.ts +149 -26
- package/src/FastMCP.ts +63 -62
- package/src/examples/addition.ts +40 -4
package/src/FastMCP.test.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { expect, test, vi } from "vitest";
|
|
|
17
17
|
import { z } from "zod";
|
|
18
18
|
|
|
19
19
|
import {
|
|
20
|
+
audioContent,
|
|
20
21
|
type ContentResult,
|
|
21
22
|
FastMCP,
|
|
22
23
|
FastMCPSession,
|
|
@@ -51,11 +52,10 @@ const runWithTestServer = async ({
|
|
|
51
52
|
});
|
|
52
53
|
|
|
53
54
|
await server.start({
|
|
54
|
-
|
|
55
|
-
endpoint: "/sse",
|
|
55
|
+
httpStream: {
|
|
56
56
|
port,
|
|
57
57
|
},
|
|
58
|
-
transportType: "
|
|
58
|
+
transportType: "httpStream",
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
try {
|
|
@@ -269,6 +269,55 @@ test("returns an image", async () => {
|
|
|
269
269
|
});
|
|
270
270
|
});
|
|
271
271
|
|
|
272
|
+
test("returns an audio", async () => {
|
|
273
|
+
await runWithTestServer({
|
|
274
|
+
run: async ({ client }) => {
|
|
275
|
+
expect(
|
|
276
|
+
await client.callTool({
|
|
277
|
+
arguments: {
|
|
278
|
+
a: 1,
|
|
279
|
+
b: 2,
|
|
280
|
+
},
|
|
281
|
+
name: "add",
|
|
282
|
+
}),
|
|
283
|
+
).toEqual({
|
|
284
|
+
content: [
|
|
285
|
+
{
|
|
286
|
+
data: "UklGRhwMAABXQVZFZm10IBAAAAABAAEAgD4AAIA+AAABAAgAZGF0Ya4LAACAgICAgICAgICAgICAgICAgICAgICAgICAf3hxeH+AfXZ1eHx6dnR5fYGFgoOKi42aloubq6GOjI2Op7ythXJ0eYF5aV1AOFFib32HmZSHhpCalIiYi4SRkZaLfnhxaWptb21qaWBea2BRYmZTVmFgWFNXVVVhaGdbYGhZbXh1gXZ1goeIlot1k6yxtKaOkaWhq7KonKCZoaCjoKWuqqmurK6ztrO7tbTAvru/vb68vbW6vLGqsLOfm5yal5KKhoyBeHt2dXBnbmljVlJWUEBBPDw9Mi4zKRwhIBYaGRQcHBURGB0XFxwhGxocJSstMjg6PTc6PUxVV1lWV2JqaXN0coCHhIyPjpOenqWppK6xu72yxMu9us7Pw83Wy9nY29ve6OPr6uvs6ezu6ejk6erm3uPj3dbT1sjBzdDFuMHAt7m1r7W6qaCupJOTkpWPgHqAd3JrbGlnY1peX1hTUk9PTFRKR0RFQkRBRUVEQkdBPjs9Pzo6NT04Njs+PTxAPzo/Ojk6PEA5PUJAQD04PkRCREZLUk1KT1BRUVdXU1VRV1tZV1xgXltcXF9hXl9eY2VmZmlna3J0b3F3eHyBfX+JgIWJiouTlZCTmpybnqSgnqyrqrO3srK2uL2/u7jAwMLFxsfEv8XLzcrIy83JzcrP0s3M0dTP0drY1dPR1dzc19za19XX2dnU1NjU0dXPzdHQy8rMysfGxMLBvLu3ta+sraeioJ2YlI+MioeFfX55cnJsaWVjXVlbVE5RTktHRUVAPDw3NC8uLyknKSIiJiUdHiEeGx4eHRwZHB8cHiAfHh8eHSEhISMoJyMnKisrLCszNy8yOTg9QEJFRUVITVFOTlJVWltaXmNfX2ZqZ21xb3R3eHqAhoeJkZKTlZmhpJ6kqKeur6yxtLW1trW4t6+us7axrbK2tLa6ury7u7u9u7vCwb+/vr7Ev7y9v8G8vby6vru4uLq+tri8ubi5t7W4uLW5uLKxs7G0tLGwt7Wvs7avr7O0tLW4trS4uLO1trW1trm1tLm0r7Kyr66wramsqaKlp52bmpeWl5KQkImEhIB8fXh3eHJrbW5mYGNcWFhUUE1LRENDQUI9ODcxLy8vMCsqLCgoKCgpKScoKCYoKygpKyssLi0sLi0uMDIwMTIuLzQ0Njg4Njc8ODlBQ0A/RUdGSU5RUVFUV1pdXWFjZGdpbG1vcXJ2eXh6fICAgIWIio2OkJGSlJWanJqbnZ2cn6Kkp6enq62srbCysrO1uLy4uL+/vL7CwMHAvb/Cvbq9vLm5uba2t7Sysq+urqyqqaalpqShoJ+enZuamZqXlZWTkpGSkpCNjpCMioqLioiHhoeGhYSGg4GDhoKDg4GBg4GBgoGBgoOChISChISChIWDg4WEgoSEgYODgYGCgYGAgICAgX99f398fX18e3p6e3t7enp7fHx4e3x6e3x7fHx9fX59fn1+fX19fH19fnx9fn19fX18fHx7fHx6fH18fXx8fHx7fH1+fXx+f319fn19fn1+gH9+f4B/fn+AgICAgH+AgICAgIGAgICAgH9+f4B+f35+fn58e3t8e3p5eXh4d3Z1dHRzcXBvb21sbmxqaWhlZmVjYmFfX2BfXV1cXFxaWVlaWVlYV1hYV1hYWVhZWFlaWllbXFpbXV5fX15fYWJhYmNiYWJhYWJjZGVmZ2hqbG1ub3Fxc3V3dnd6e3t8e3x+f3+AgICAgoGBgoKDhISFh4aHiYqKi4uMjYyOj4+QkZKUlZWXmJmbm52enqCioqSlpqeoqaqrrK2ur7CxsrGys7O0tbW2tba3t7i3uLe4t7a3t7i3tre2tba1tLSzsrKysbCvrq2sq6qop6alo6OioJ+dnJqZmJeWlJKSkI+OjoyLioiIh4WEg4GBgH9+fXt6eXh3d3V0c3JxcG9ubWxsamppaWhnZmVlZGRjYmNiYWBhYGBfYF9fXl5fXl1dXVxdXF1dXF1cXF1cXF1dXV5dXV5fXl9eX19gYGFgYWJhYmFiY2NiY2RjZGNkZWRlZGVmZmVmZmVmZ2dmZ2hnaGhnaGloZ2hpaWhpamlqaWpqa2pra2xtbGxtbm1ubm5vcG9wcXBxcnFycnN0c3N0dXV2d3d4eHh5ent6e3x9fn5/f4CAgIGCg4SEhYaGh4iIiYqLi4uMjY2Oj5CQkZGSk5OUlJWWlpeYl5iZmZqbm5ybnJ2cnZ6en56fn6ChoKChoqGio6KjpKOko6SjpKWkpaSkpKSlpKWkpaSlpKSlpKOkpKOko6KioaKhoaCfoJ+enp2dnJybmpmZmJeXlpWUk5STkZGQj4+OjYyLioqJh4eGhYSEgoKBgIB/fn59fHt7enl5eHd3dnZ1dHRzc3JycXBxcG9vbm5tbWxrbGxraWppaWhpaGdnZ2dmZ2ZlZmVmZWRlZGVkY2RjZGNkZGRkZGRkZGRkZGRjZGRkY2RjZGNkZWRlZGVmZWZmZ2ZnZ2doaWhpaWpra2xsbW5tbm9ub29wcXFycnNzdHV1dXZ2d3d4eXl6enp7fHx9fX5+f4CAgIGAgYGCgoOEhISFhoWGhoeIh4iJiImKiYqLiouLjI2MjI2OjY6Pj46PkI+QkZCRkJGQkZGSkZKRkpGSkZGRkZKRkpKRkpGSkZKRkpGSkZKRkpGSkZCRkZCRkI+Qj5CPkI+Pjo+OjY6Njo2MjYyLjIuMi4qLioqJiomJiImIh4iHh4aHhoaFhoWFhIWEg4SDg4KDgoKBgoGAgYCBgICAgICAf4CAf39+f35/fn1+fX59fHx9fH18e3x7fHt6e3p7ent6e3p5enl6enl6eXp5eXl4eXh5eHl4eXh5eHl4eXh5eHh3eHh4d3h4d3h3d3h4d3l4eHd4d3h3eHd4d3h3eHh4eXh5eHl4eHl4eXh5enl6eXp5enl6eXp5ent6ent6e3x7fHx9fH18fX19fn1+fX5/fn9+f4B/gH+Af4CAgICAgIGAgYCBgoGCgYKCgoKDgoOEg4OEg4SFhIWEhYSFhoWGhYaHhoeHhoeGh4iHiIiHiImIiImKiYqJiYqJiouKi4qLiouKi4qLiouKi4qLiouKi4qLi4qLiouKi4qLiomJiomIiYiJiImIh4iIh4iHhoeGhYWGhYaFhIWEg4OEg4KDgoOCgYKBgIGAgICAgH+Af39+f359fn18fX19fHx8e3t6e3p7enl6eXp5enl6enl5eXh5eHh5eHl4eXh5eHl4eHd5eHd3eHl4d3h3eHd4d3h3eHh4d3h4d3h3d3h5eHl4eXh5eHl5eXp5enl6eXp7ent6e3p7e3t7fHt8e3x8fHx9fH1+fX59fn9+f35/gH+AgICAgICAgYGAgYKBgoGCgoKDgoOEg4SEhIWFhIWFhoWGhYaGhoaHhoeGh4aHhoeIh4iHiIeHiIeIh4iHiIeIiIiHiIeIh4iHiIiHiIeIh4iHiIeIh4eIh4eIh4aHh4aHhoeGh4aHhoWGhYaFhoWFhIWEhYSFhIWEhISDhIOEg4OCg4OCg4KDgYKCgYKCgYCBgIGAgYCBgICAgICAgICAf4B/f4B/gH+Af35/fn9+f35/fn1+fn19fn1+fX59fn19fX19fH18fXx9fH18fXx9fH18fXx8fHt8e3x7fHt8e3x7fHt8e3x7fHt8e3x7fHt8e3x7fHt8e3x8e3x7fHt8e3x7fHx8fXx9fH18fX5+fX59fn9+f35+f35/gH+Af4B/gICAgICAgICAgICAgYCBgIGAgIGAgYGBgoGCgYKBgoGCgYKBgoGCgoKDgoOCg4KDgoOCg4KDgoOCg4KDgoOCg4KDgoOCg4KDgoOCg4KDgoOCg4KDgoOCg4KDgoOCg4KDgoOCg4KCgoGCgYKBgoGCgYKBgoGCgYKBgoGCgYKBgoGCgYKBgoGCgYKBgoGCgYKBgoGBgYCBgIGAgYCBgIGAgYCBgIGAgYCBgIGAgYCBgIGAgYCAgICBgIGAgYCBgIGAgYCBgIGAgYCBgExJU1RCAAAASU5GT0lDUkQMAAAAMjAwOC0wOS0yMQAASUVORwMAAAAgAAABSVNGVBYAAABTb255IFNvdW5kIEZvcmdlIDguMAAA",
|
|
287
|
+
mimeType: "audio/wav",
|
|
288
|
+
type: "audio",
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
});
|
|
292
|
+
},
|
|
293
|
+
server: async () => {
|
|
294
|
+
const server = new FastMCP({
|
|
295
|
+
name: "Test",
|
|
296
|
+
version: "1.0.0",
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
server.addTool({
|
|
300
|
+
description: "Add two numbers",
|
|
301
|
+
execute: async () => {
|
|
302
|
+
return audioContent({
|
|
303
|
+
buffer: Buffer.from(
|
|
304
|
+
"UklGRhwMAABXQVZFZm10IBAAAAABAAEAgD4AAIA+AAABAAgAZGF0Ya4LAACAgICAgICAgICAgICAgICAgICAgICAgICAf3hxeH+AfXZ1eHx6dnR5fYGFgoOKi42aloubq6GOjI2Op7ythXJ0eYF5aV1AOFFib32HmZSHhpCalIiYi4SRkZaLfnhxaWptb21qaWBea2BRYmZTVmFgWFNXVVVhaGdbYGhZbXh1gXZ1goeIlot1k6yxtKaOkaWhq7KonKCZoaCjoKWuqqmurK6ztrO7tbTAvru/vb68vbW6vLGqsLOfm5yal5KKhoyBeHt2dXBnbmljVlJWUEBBPDw9Mi4zKRwhIBYaGRQcHBURGB0XFxwhGxocJSstMjg6PTc6PUxVV1lWV2JqaXN0coCHhIyPjpOenqWppK6xu72yxMu9us7Pw83Wy9nY29ve6OPr6uvs6ezu6ejk6erm3uPj3dbT1sjBzdDFuMHAt7m1r7W6qaCupJOTkpWPgHqAd3JrbGlnY1peX1hTUk9PTFRKR0RFQkRBRUVEQkdBPjs9Pzo6NT04Njs+PTxAPzo/Ojk6PEA5PUJAQD04PkRCREZLUk1KT1BRUVdXU1VRV1tZV1xgXltcXF9hXl9eY2VmZmlna3J0b3F3eHyBfX+JgIWJiouTlZCTmpybnqSgnqyrqrO3srK2uL2/u7jAwMLFxsfEv8XLzcrIy83JzcrP0s3M0dTP0drY1dPR1dzc19za19XX2dnU1NjU0dXPzdHQy8rMysfGxMLBvLu3ta+sraeioJ2YlI+MioeFfX55cnJsaWVjXVlbVE5RTktHRUVAPDw3NC8uLyknKSIiJiUdHiEeGx4eHRwZHB8cHiAfHh8eHSEhISMoJyMnKisrLCszNy8yOTg9QEJFRUVITVFOTlJVWltaXmNfX2ZqZ21xb3R3eHqAhoeJkZKTlZmhpJ6kqKeur6yxtLW1trW4t6+us7axrbK2tLa6ury7u7u9u7vCwb+/vr7Ev7y9v8G8vby6vru4uLq+tri8ubi5t7W4uLW5uLKxs7G0tLGwt7Wvs7avr7O0tLW4trS4uLO1trW1trm1tLm0r7Kyr66wramsqaKlp52bmpeWl5KQkImEhIB8fXh3eHJrbW5mYGNcWFhUUE1LRENDQUI9ODcxLy8vMCsqLCgoKCgpKScoKCYoKygpKyssLi0sLi0uMDIwMTIuLzQ0Njg4Njc8ODlBQ0A/RUdGSU5RUVFUV1pdXWFjZGdpbG1vcXJ2eXh6fICAgIWIio2OkJGSlJWanJqbnZ2cn6Kkp6enq62srbCysrO1uLy4uL+/vL7CwMHAvb/Cvbq9vLm5uba2t7Sysq+urqyqqaalpqShoJ+enZuamZqXlZWTkpGSkpCNjpCMioqLioiHhoeGhYSGg4GDhoKDg4GBg4GBgoGBgoOChISChISChIWDg4WEgoSEgYODgYGCgYGAgICAgX99f398fX18e3p6e3t7enp7fHx4e3x6e3x7fHx9fX59fn1+fX19fH19fnx9fn19fX18fHx7fHx6fH18fXx8fHx7fH1+fXx+f319fn19fn1+gH9+f4B/fn+AgICAgH+AgICAgIGAgICAgH9+f4B+f35+fn58e3t8e3p5eXh4d3Z1dHRzcXBvb21sbmxqaWhlZmVjYmFfX2BfXV1cXFxaWVlaWVlYV1hYV1hYWVhZWFlaWllbXFpbXV5fX15fYWJhYmNiYWJhYWJjZGVmZ2hqbG1ub3Fxc3V3dnd6e3t8e3x+f3+AgICAgoGBgoKDhISFh4aHiYqKi4uMjYyOj4+QkZKUlZWXmJmbm52enqCioqSlpqeoqaqrrK2ur7CxsrGys7O0tbW2tba3t7i3uLe4t7a3t7i3tre2tba1tLSzsrKysbCvrq2sq6qop6alo6OioJ+dnJqZmJeWlJKSkI+OjoyLioiIh4WEg4GBgH9+fXt6eXh3d3V0c3JxcG9ubWxsamppaWhnZmVlZGRjYmNiYWBhYGBfYF9fXl5fXl1dXVxdXF1dXF1cXF1cXF1dXV5dXV5fXl9eX19gYGFgYWJhYmFiY2NiY2RjZGNkZWRlZGVmZmVmZmVmZ2dmZ2hnaGhnaGloZ2hpaWhpamlqaWpqa2pra2xtbGxtbm1ubm5vcG9wcXBxcnFycnN0c3N0dXV2d3d4eHh5ent6e3x9fn5/f4CAgIGCg4SEhYaGh4iIiYqLi4uMjY2Oj5CQkZGSk5OUlJWWlpeYl5iZmZqbm5ybnJ2cnZ6en56fn6ChoKChoqGio6KjpKOko6SjpKWkpaSkpKSlpKWkpaSlpKSlpKOkpKOko6KioaKhoaCfoJ+enp2dnJybmpmZmJeXlpWUk5STkZGQj4+OjYyLioqJh4eGhYSEgoKBgIB/fn59fHt7enl5eHd3dnZ1dHRzc3JycXBxcG9vbm5tbWxrbGxraWppaWhpaGdnZ2dmZ2ZlZmVmZWRlZGVkY2RjZGNkZGRkZGRkZGRkZGRjZGRkY2RjZGNkZWRlZGVmZWZmZ2ZnZ2doaWhpaWpra2xsbW5tbm9ub29wcXFycnNzdHV1dXZ2d3d4eXl6enp7fHx9fX5+f4CAgIGAgYGCgoOEhISFhoWGhoeIh4iJiImKiYqLiouLjI2MjI2OjY6Pj46PkI+QkZCRkJGQkZGSkZKRkpGSkZGRkZKRkpKRkpGSkZKRkpGSkZKRkpGSkZCRkZCRkI+Qj5CPkI+Pjo+OjY6Njo2MjYyLjIuMi4qLioqJiomJiImIh4iHh4aHhoaFhoWFhIWEg4SDg4KDgoKBgoGAgYCBgICAgICAf4CAf39+f35/fn1+fX59fHx9fH18e3x7fHt6e3p7ent6e3p5enl6enl6eXp5eXl4eXh5eHl4eXh5eHl4eXh5eHh3eHh4d3h4d3h3d3h4d3l4eHd4d3h3eHd4d3h3eHh4eXh5eHl4eHl4eXh5enl6eXp5enl6eXp5ent6ent6e3x7fHx9fH18fX19fn1+fX5/fn9+f4B/gH+Af4CAgICAgIGAgYCBgoGCgYKCgoKDgoOEg4OEg4SFhIWEhYSFhoWGhYaHhoeHhoeGh4iHiIiHiImIiImKiYqJiYqJiouKi4qLiouKi4qLiouKi4qLiouKi4qLi4qLiouKi4qLiomJiomIiYiJiImIh4iIh4iHhoeGhYWGhYaFhIWEg4OEg4KDgoOCgYKBgIGAgICAgH+Af39+f359fn18fX19fHx8e3t6e3p7enl6eXp5enl6enl5eXh5eHh5eHl4eXh5eHl4eHd5eHd3eHl4d3h3eHd4d3h3eHh4d3h4d3h3d3h5eHl4eXh5eHl5eXp5enl6eXp7ent6e3p7e3t7fHt8e3x8fHx9fH1+fX59fn9+f35/gH+AgICAgICAgYGAgYKBgoGCgoKDgoOEg4SEhIWFhIWFhoWGhYaGhoaHhoeGh4aHhoeIh4iHiIeHiIeIh4iHiIeIiIiHiIeIh4iHiIiHiIeIh4iHiIeIh4eIh4eIh4aHh4aHhoeGh4aHhoWGhYaFhoWFhIWEhYSFhIWEhISDhIOEg4OCg4OCg4KDgYKCgYKCgYCBgIGAgYCBgICAgICAgICAf4B/f4B/gH+Af35/fn9+f35/fn1+fn19fn1+fX59fn19fX19fH18fXx9fH18fXx9fH18fXx8fHt8e3x7fHt8e3x7fHt8e3x7fHt8e3x7fHt8e3x7fHt8e3x8e3x7fHt8e3x7fHx8fXx9fH18fX5+fX59fn9+f35+f35/gH+Af4B/gICAgICAgICAgICAgYCBgIGAgIGAgYGBgoGCgYKBgoGCgYKBgoGCgoKDgoOCg4KDgoOCg4KDgoOCg4KDgoOCg4KDgoOCg4KDgoOCg4KDgoOCg4KDgoOCg4KDgoOCg4KDgoOCg4KCgoGCgYKBgoGCgYKBgoGCgYKBgoGCgYKBgoGCgYKBgoGCgYKBgoGCgYKBgoGBgYCBgIGAgYCBgIGAgYCBgIGAgYCBgIGAgYCBgIGAgYCAgICBgIGAgYCBgIGAgYCBgIGAgYCBgExJU1RCAAAASU5GT0lDUkQMAAAAMjAwOC0wOS0yMQAASUVORwMAAAAgAAABSVNGVBYAAABTb255IFNvdW5kIEZvcmdlIDguMAAA",
|
|
305
|
+
"base64",
|
|
306
|
+
),
|
|
307
|
+
});
|
|
308
|
+
},
|
|
309
|
+
name: "add",
|
|
310
|
+
parameters: z.object({
|
|
311
|
+
a: z.number(),
|
|
312
|
+
b: z.number(),
|
|
313
|
+
}),
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
return server;
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
272
321
|
test("handles UserError errors", async () => {
|
|
273
322
|
await runWithTestServer({
|
|
274
323
|
run: async ({ client }) => {
|
|
@@ -742,11 +791,10 @@ test("uses events to notify server of client connect/disconnect", async () => {
|
|
|
742
791
|
server.on("disconnect", onDisconnect);
|
|
743
792
|
|
|
744
793
|
await server.start({
|
|
745
|
-
|
|
746
|
-
endpoint: "/sse",
|
|
794
|
+
httpStream: {
|
|
747
795
|
port,
|
|
748
796
|
},
|
|
749
|
-
transportType: "
|
|
797
|
+
transportType: "httpStream",
|
|
750
798
|
});
|
|
751
799
|
|
|
752
800
|
const client = new Client(
|
|
@@ -791,11 +839,10 @@ test("handles multiple clients", async () => {
|
|
|
791
839
|
});
|
|
792
840
|
|
|
793
841
|
await server.start({
|
|
794
|
-
|
|
795
|
-
endpoint: "/sse",
|
|
842
|
+
httpStream: {
|
|
796
843
|
port,
|
|
797
844
|
},
|
|
798
|
-
transportType: "
|
|
845
|
+
transportType: "httpStream",
|
|
799
846
|
});
|
|
800
847
|
|
|
801
848
|
const client1 = new Client(
|
|
@@ -1472,11 +1519,10 @@ test("allows new clients to connect after a client disconnects", async () => {
|
|
|
1472
1519
|
});
|
|
1473
1520
|
|
|
1474
1521
|
await server.start({
|
|
1475
|
-
|
|
1476
|
-
endpoint: "/sse",
|
|
1522
|
+
httpStream: {
|
|
1477
1523
|
port,
|
|
1478
1524
|
},
|
|
1479
|
-
transportType: "
|
|
1525
|
+
transportType: "httpStream",
|
|
1480
1526
|
});
|
|
1481
1527
|
|
|
1482
1528
|
const client1 = new Client(
|
|
@@ -1551,11 +1597,10 @@ test("able to close server immediately after starting it", async () => {
|
|
|
1551
1597
|
});
|
|
1552
1598
|
|
|
1553
1599
|
await server.start({
|
|
1554
|
-
|
|
1555
|
-
endpoint: "/sse",
|
|
1600
|
+
httpStream: {
|
|
1556
1601
|
port,
|
|
1557
1602
|
},
|
|
1558
|
-
transportType: "
|
|
1603
|
+
transportType: "httpStream",
|
|
1559
1604
|
});
|
|
1560
1605
|
|
|
1561
1606
|
// We were previously not waiting for the server to start.
|
|
@@ -1584,11 +1629,10 @@ test("closing event source does not produce error", async () => {
|
|
|
1584
1629
|
});
|
|
1585
1630
|
|
|
1586
1631
|
await server.start({
|
|
1587
|
-
|
|
1588
|
-
endpoint: "/sse",
|
|
1632
|
+
httpStream: {
|
|
1589
1633
|
port,
|
|
1590
1634
|
},
|
|
1591
|
-
transportType: "
|
|
1635
|
+
transportType: "httpStream",
|
|
1592
1636
|
});
|
|
1593
1637
|
|
|
1594
1638
|
const eventSource = await new Promise<EventSourceClient>((onMessage) => {
|
|
@@ -1647,11 +1691,10 @@ test("provides auth to tools", async () => {
|
|
|
1647
1691
|
});
|
|
1648
1692
|
|
|
1649
1693
|
await server.start({
|
|
1650
|
-
|
|
1651
|
-
endpoint: "/sse",
|
|
1694
|
+
httpStream: {
|
|
1652
1695
|
port,
|
|
1653
1696
|
},
|
|
1654
|
-
transportType: "
|
|
1697
|
+
transportType: "httpStream",
|
|
1655
1698
|
});
|
|
1656
1699
|
|
|
1657
1700
|
const client = new Client(
|
|
@@ -1716,10 +1759,90 @@ test("provides auth to tools", async () => {
|
|
|
1716
1759
|
},
|
|
1717
1760
|
reportProgress: expect.any(Function),
|
|
1718
1761
|
session: { id: 1 },
|
|
1762
|
+
streamContent: expect.any(Function),
|
|
1719
1763
|
},
|
|
1720
1764
|
);
|
|
1721
1765
|
});
|
|
1722
1766
|
|
|
1767
|
+
test("supports streaming output from tools", async () => {
|
|
1768
|
+
let streamResult: { content: Array<{ text: string; type: string }> };
|
|
1769
|
+
|
|
1770
|
+
await runWithTestServer({
|
|
1771
|
+
run: async ({ client }) => {
|
|
1772
|
+
const result = await client.callTool({
|
|
1773
|
+
arguments: {},
|
|
1774
|
+
name: "streaming-void-tool",
|
|
1775
|
+
});
|
|
1776
|
+
|
|
1777
|
+
expect(result).toEqual({
|
|
1778
|
+
content: [],
|
|
1779
|
+
});
|
|
1780
|
+
|
|
1781
|
+
streamResult = (await client.callTool({
|
|
1782
|
+
arguments: {},
|
|
1783
|
+
name: "streaming-with-result",
|
|
1784
|
+
})) as { content: Array<{ text: string; type: string }> };
|
|
1785
|
+
|
|
1786
|
+
expect(streamResult).toEqual({
|
|
1787
|
+
content: [{ text: "Final result after streaming", type: "text" }],
|
|
1788
|
+
});
|
|
1789
|
+
},
|
|
1790
|
+
server: async () => {
|
|
1791
|
+
const server = new FastMCP({
|
|
1792
|
+
name: "Test",
|
|
1793
|
+
version: "1.0.0",
|
|
1794
|
+
});
|
|
1795
|
+
|
|
1796
|
+
server.addTool({
|
|
1797
|
+
annotations: {
|
|
1798
|
+
streamingHint: true,
|
|
1799
|
+
},
|
|
1800
|
+
description: "Tool yang streaming dan mengembalikan void",
|
|
1801
|
+
execute: async (_args, context) => {
|
|
1802
|
+
await context.streamContent({
|
|
1803
|
+
text: "Streaming content 1",
|
|
1804
|
+
type: "text",
|
|
1805
|
+
});
|
|
1806
|
+
|
|
1807
|
+
await context.streamContent({
|
|
1808
|
+
text: "Streaming content 2",
|
|
1809
|
+
type: "text",
|
|
1810
|
+
});
|
|
1811
|
+
|
|
1812
|
+
// Return void
|
|
1813
|
+
return;
|
|
1814
|
+
},
|
|
1815
|
+
name: "streaming-void-tool",
|
|
1816
|
+
parameters: z.object({}),
|
|
1817
|
+
});
|
|
1818
|
+
|
|
1819
|
+
server.addTool({
|
|
1820
|
+
annotations: {
|
|
1821
|
+
streamingHint: true,
|
|
1822
|
+
},
|
|
1823
|
+
description: "Tool yang streaming dan mengembalikan hasil",
|
|
1824
|
+
execute: async (_args, context) => {
|
|
1825
|
+
await context.streamContent({
|
|
1826
|
+
text: "Streaming content 1",
|
|
1827
|
+
type: "text",
|
|
1828
|
+
});
|
|
1829
|
+
|
|
1830
|
+
await context.streamContent({
|
|
1831
|
+
text: "Streaming content 2",
|
|
1832
|
+
type: "text",
|
|
1833
|
+
});
|
|
1834
|
+
|
|
1835
|
+
return "Final result after streaming";
|
|
1836
|
+
},
|
|
1837
|
+
name: "streaming-with-result",
|
|
1838
|
+
parameters: z.object({}),
|
|
1839
|
+
});
|
|
1840
|
+
|
|
1841
|
+
return server;
|
|
1842
|
+
},
|
|
1843
|
+
});
|
|
1844
|
+
});
|
|
1845
|
+
|
|
1723
1846
|
test("blocks unauthorized requests", async () => {
|
|
1724
1847
|
const port = await getRandomPort();
|
|
1725
1848
|
|
|
@@ -1735,11 +1858,10 @@ test("blocks unauthorized requests", async () => {
|
|
|
1735
1858
|
});
|
|
1736
1859
|
|
|
1737
1860
|
await server.start({
|
|
1738
|
-
|
|
1739
|
-
endpoint: "/sse",
|
|
1861
|
+
httpStream: {
|
|
1740
1862
|
port,
|
|
1741
1863
|
},
|
|
1742
|
-
transportType: "
|
|
1864
|
+
transportType: "httpStream",
|
|
1743
1865
|
});
|
|
1744
1866
|
|
|
1745
1867
|
const client = new Client(
|
|
@@ -1767,6 +1889,7 @@ test("blocks unauthorized requests", async () => {
|
|
|
1767
1889
|
// Set longer timeout for HTTP Stream tests
|
|
1768
1890
|
test("HTTP Stream: calls a tool", { timeout: 20000 }, async () => {
|
|
1769
1891
|
console.log("Starting HTTP Stream test...");
|
|
1892
|
+
|
|
1770
1893
|
const port = await getRandomPort();
|
|
1771
1894
|
|
|
1772
1895
|
// Create server directly (don't use helper function)
|
|
@@ -1789,7 +1912,6 @@ test("HTTP Stream: calls a tool", { timeout: 20000 }, async () => {
|
|
|
1789
1912
|
|
|
1790
1913
|
await server.start({
|
|
1791
1914
|
httpStream: {
|
|
1792
|
-
endpoint: "/httpStream",
|
|
1793
1915
|
port,
|
|
1794
1916
|
},
|
|
1795
1917
|
transportType: "httpStream",
|
|
@@ -1810,7 +1932,7 @@ test("HTTP Stream: calls a tool", { timeout: 20000 }, async () => {
|
|
|
1810
1932
|
// IMPORTANT: Don't provide sessionId manually with HTTP streaming
|
|
1811
1933
|
// The server will generate a session ID automatically
|
|
1812
1934
|
const transport = new StreamableHTTPClientTransport(
|
|
1813
|
-
new URL(`http://localhost:${port}/
|
|
1935
|
+
new URL(`http://localhost:${port}/stream`),
|
|
1814
1936
|
);
|
|
1815
1937
|
|
|
1816
1938
|
// Connect client to server
|
|
@@ -1835,6 +1957,7 @@ test("HTTP Stream: calls a tool", { timeout: 20000 }, async () => {
|
|
|
1835
1957
|
|
|
1836
1958
|
// Clean up connection
|
|
1837
1959
|
await transport.terminateSession();
|
|
1960
|
+
|
|
1838
1961
|
await client.close();
|
|
1839
1962
|
} finally {
|
|
1840
1963
|
await server.stop();
|
package/src/FastMCP.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
3
|
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
4
4
|
import {
|
|
5
|
-
AudioContent,
|
|
6
5
|
CallToolRequestSchema,
|
|
7
6
|
ClientCapabilities,
|
|
8
7
|
CompleteRequestSchema,
|
|
@@ -26,7 +25,7 @@ import { fileTypeFromBuffer } from "file-type";
|
|
|
26
25
|
import { readFile } from "fs/promises";
|
|
27
26
|
import Fuse from "fuse.js";
|
|
28
27
|
import http from "http";
|
|
29
|
-
import {
|
|
28
|
+
import { startHTTPServer } from "mcp-proxy";
|
|
30
29
|
import { StrictEventEmitter } from "strict-event-emitter-types";
|
|
31
30
|
import { setTimeout as delay } from "timers/promises";
|
|
32
31
|
import { fetch } from "undici";
|
|
@@ -181,6 +180,7 @@ type Context<T extends FastMCPSessionAuth> = {
|
|
|
181
180
|
};
|
|
182
181
|
reportProgress: (progress: Progress) => Promise<void>;
|
|
183
182
|
session: T | undefined;
|
|
183
|
+
streamContent: (content: Content | Content[]) => Promise<void>;
|
|
184
184
|
};
|
|
185
185
|
|
|
186
186
|
type Extra = unknown;
|
|
@@ -264,11 +264,29 @@ const ImageContentZodSchema = z
|
|
|
264
264
|
})
|
|
265
265
|
.strict() satisfies z.ZodType<ImageContent>;
|
|
266
266
|
|
|
267
|
-
type
|
|
267
|
+
type AudioContent = {
|
|
268
|
+
data: string;
|
|
269
|
+
mimeType: string;
|
|
270
|
+
type: "audio";
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const AudioContentZodSchema = z
|
|
274
|
+
.object({
|
|
275
|
+
/**
|
|
276
|
+
* The base64-encoded audio data.
|
|
277
|
+
*/
|
|
278
|
+
data: z.string().base64(),
|
|
279
|
+
mimeType: z.string(),
|
|
280
|
+
type: z.literal("audio"),
|
|
281
|
+
})
|
|
282
|
+
.strict() satisfies z.ZodType<AudioContent>;
|
|
283
|
+
|
|
284
|
+
type Content = AudioContent | ImageContent | TextContent;
|
|
268
285
|
|
|
269
286
|
const ContentZodSchema = z.discriminatedUnion("type", [
|
|
270
287
|
TextContentZodSchema,
|
|
271
288
|
ImageContentZodSchema,
|
|
289
|
+
AudioContentZodSchema,
|
|
272
290
|
]) satisfies z.ZodType<Content>;
|
|
273
291
|
|
|
274
292
|
type ContentResult = {
|
|
@@ -466,13 +484,19 @@ type Tool<
|
|
|
466
484
|
T extends FastMCPSessionAuth,
|
|
467
485
|
Params extends ToolParameters = ToolParameters,
|
|
468
486
|
> = {
|
|
469
|
-
annotations?:
|
|
487
|
+
annotations?: {
|
|
488
|
+
/**
|
|
489
|
+
* When true, the tool leverages incremental content streaming
|
|
490
|
+
* Return void for tools that handle all their output via streaming
|
|
491
|
+
*/
|
|
492
|
+
streamingHint?: boolean;
|
|
493
|
+
} & ToolAnnotations;
|
|
470
494
|
description?: string;
|
|
471
495
|
execute: (
|
|
472
496
|
args: StandardSchemaV1.InferOutput<Params>,
|
|
473
497
|
context: Context<T>,
|
|
474
498
|
) => Promise<
|
|
475
|
-
AudioContent | ContentResult | ImageContent | string | TextContent
|
|
499
|
+
AudioContent | ContentResult | ImageContent | string | TextContent | void
|
|
476
500
|
>;
|
|
477
501
|
name: string;
|
|
478
502
|
parameters?: Params;
|
|
@@ -750,7 +774,7 @@ export class FastMCPSession<
|
|
|
750
774
|
|
|
751
775
|
if ("type" in transport) {
|
|
752
776
|
// Enable by default for SSE and HTTP streaming
|
|
753
|
-
if (transport.type === "
|
|
777
|
+
if (transport.type === "httpStream") {
|
|
754
778
|
defaultEnabled = true;
|
|
755
779
|
}
|
|
756
780
|
}
|
|
@@ -1249,14 +1273,29 @@ export class FastMCPSession<
|
|
|
1249
1273
|
};
|
|
1250
1274
|
|
|
1251
1275
|
// Create a promise for tool execution
|
|
1276
|
+
// Streams partial results while a tool is still executing
|
|
1277
|
+
// Enables progressive rendering and real-time feedback
|
|
1278
|
+
const streamContent = async (content: Content | Content[]) => {
|
|
1279
|
+
const contentArray = Array.isArray(content) ? content : [content];
|
|
1280
|
+
|
|
1281
|
+
await this.#server.notification({
|
|
1282
|
+
method: "notifications/tool/streamContent",
|
|
1283
|
+
params: {
|
|
1284
|
+
content: contentArray,
|
|
1285
|
+
toolName: request.params.name,
|
|
1286
|
+
},
|
|
1287
|
+
});
|
|
1288
|
+
};
|
|
1289
|
+
|
|
1252
1290
|
const executeToolPromise = tool.execute(args, {
|
|
1253
1291
|
log,
|
|
1254
1292
|
reportProgress,
|
|
1255
1293
|
session: this.#auth,
|
|
1294
|
+
streamContent,
|
|
1256
1295
|
});
|
|
1257
1296
|
|
|
1258
1297
|
// Handle timeout if specified
|
|
1259
|
-
const maybeStringResult = await (tool.timeoutMs
|
|
1298
|
+
const maybeStringResult = (await (tool.timeoutMs
|
|
1260
1299
|
? Promise.race([
|
|
1261
1300
|
executeToolPromise,
|
|
1262
1301
|
new Promise<never>((_, reject) => {
|
|
@@ -1269,9 +1308,20 @@ export class FastMCPSession<
|
|
|
1269
1308
|
}, tool.timeoutMs);
|
|
1270
1309
|
}),
|
|
1271
1310
|
])
|
|
1272
|
-
: executeToolPromise)
|
|
1273
|
-
|
|
1274
|
-
|
|
1311
|
+
: executeToolPromise)) as
|
|
1312
|
+
| AudioContent
|
|
1313
|
+
| ContentResult
|
|
1314
|
+
| ImageContent
|
|
1315
|
+
| null
|
|
1316
|
+
| string
|
|
1317
|
+
| TextContent
|
|
1318
|
+
| undefined;
|
|
1319
|
+
|
|
1320
|
+
if (maybeStringResult === undefined || maybeStringResult === null) {
|
|
1321
|
+
result = ContentResultZodSchema.parse({
|
|
1322
|
+
content: [],
|
|
1323
|
+
});
|
|
1324
|
+
} else if (typeof maybeStringResult === "string") {
|
|
1275
1325
|
result = ContentResultZodSchema.parse({
|
|
1276
1326
|
content: [{ text: maybeStringResult, type: "text" }],
|
|
1277
1327
|
});
|
|
@@ -1322,7 +1372,6 @@ export class FastMCP<
|
|
|
1322
1372
|
#resources: Resource[] = [];
|
|
1323
1373
|
#resourcesTemplates: InputResourceTemplate[] = [];
|
|
1324
1374
|
#sessions: FastMCPSession<T>[] = [];
|
|
1325
|
-
#sseServer: null | SSEServer = null;
|
|
1326
1375
|
|
|
1327
1376
|
#tools: Tool<T>[] = [];
|
|
1328
1377
|
|
|
@@ -1371,13 +1420,9 @@ export class FastMCP<
|
|
|
1371
1420
|
public async start(
|
|
1372
1421
|
options:
|
|
1373
1422
|
| {
|
|
1374
|
-
httpStream: {
|
|
1423
|
+
httpStream: { port: number };
|
|
1375
1424
|
transportType: "httpStream";
|
|
1376
1425
|
}
|
|
1377
|
-
| {
|
|
1378
|
-
sse: { endpoint: `/${string}`; port: number };
|
|
1379
|
-
transportType: "sse";
|
|
1380
|
-
}
|
|
1381
1426
|
| { transportType: "stdio" } = {
|
|
1382
1427
|
transportType: "stdio",
|
|
1383
1428
|
},
|
|
@@ -1404,48 +1449,8 @@ export class FastMCP<
|
|
|
1404
1449
|
this.emit("connect", {
|
|
1405
1450
|
session,
|
|
1406
1451
|
});
|
|
1407
|
-
} else if (options.transportType === "sse") {
|
|
1408
|
-
this.#sseServer = await startSSEServer<FastMCPSession<T>>({
|
|
1409
|
-
createServer: async (request) => {
|
|
1410
|
-
let auth: T | undefined;
|
|
1411
|
-
|
|
1412
|
-
if (this.#authenticate) {
|
|
1413
|
-
auth = await this.#authenticate(request);
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
return new FastMCPSession<T>({
|
|
1417
|
-
auth,
|
|
1418
|
-
name: this.#options.name,
|
|
1419
|
-
ping: this.#options.ping,
|
|
1420
|
-
prompts: this.#prompts,
|
|
1421
|
-
resources: this.#resources,
|
|
1422
|
-
resourcesTemplates: this.#resourcesTemplates,
|
|
1423
|
-
roots: this.#options.roots,
|
|
1424
|
-
tools: this.#tools,
|
|
1425
|
-
version: this.#options.version,
|
|
1426
|
-
});
|
|
1427
|
-
},
|
|
1428
|
-
endpoint: options.sse.endpoint as `/${string}`,
|
|
1429
|
-
onClose: (session) => {
|
|
1430
|
-
this.emit("disconnect", {
|
|
1431
|
-
session,
|
|
1432
|
-
});
|
|
1433
|
-
},
|
|
1434
|
-
onConnect: async (session) => {
|
|
1435
|
-
this.#sessions.push(session);
|
|
1436
|
-
|
|
1437
|
-
this.emit("connect", {
|
|
1438
|
-
session,
|
|
1439
|
-
});
|
|
1440
|
-
},
|
|
1441
|
-
port: options.sse.port,
|
|
1442
|
-
});
|
|
1443
|
-
|
|
1444
|
-
console.info(
|
|
1445
|
-
`[FastMCP info] server is running on SSE at http://localhost:${options.sse.port}${options.sse.endpoint}`,
|
|
1446
|
-
);
|
|
1447
1452
|
} else if (options.transportType === "httpStream") {
|
|
1448
|
-
this.#httpStreamServer = await
|
|
1453
|
+
this.#httpStreamServer = await startHTTPServer<FastMCPSession<T>>({
|
|
1449
1454
|
createServer: async (request) => {
|
|
1450
1455
|
let auth: T | undefined;
|
|
1451
1456
|
|
|
@@ -1465,7 +1470,6 @@ export class FastMCP<
|
|
|
1465
1470
|
version: this.#options.version,
|
|
1466
1471
|
});
|
|
1467
1472
|
},
|
|
1468
|
-
endpoint: options.httpStream.endpoint as `/${string}`,
|
|
1469
1473
|
onClose: (session) => {
|
|
1470
1474
|
this.emit("disconnect", {
|
|
1471
1475
|
session,
|
|
@@ -1482,7 +1486,7 @@ export class FastMCP<
|
|
|
1482
1486
|
});
|
|
1483
1487
|
|
|
1484
1488
|
console.info(
|
|
1485
|
-
`[FastMCP info] server is running on HTTP Stream at http://localhost:${options.httpStream.port}
|
|
1489
|
+
`[FastMCP info] server is running on HTTP Stream at http://localhost:${options.httpStream.port}/stream`,
|
|
1486
1490
|
);
|
|
1487
1491
|
} else {
|
|
1488
1492
|
throw new Error("Invalid transport type");
|
|
@@ -1493,9 +1497,6 @@ export class FastMCP<
|
|
|
1493
1497
|
* Stops the server.
|
|
1494
1498
|
*/
|
|
1495
1499
|
public async stop() {
|
|
1496
|
-
if (this.#sseServer) {
|
|
1497
|
-
await this.#sseServer.close();
|
|
1498
|
-
}
|
|
1499
1500
|
if (this.#httpStreamServer) {
|
|
1500
1501
|
await this.#httpStreamServer.close();
|
|
1501
1502
|
}
|
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",
|