fastmcp 4.13.1 → 4.14.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 +46 -0
- package/dist/FastMCP.cjs +2 -2
- package/dist/FastMCP.d.cts +32 -1
- package/dist/FastMCP.d.ts +32 -1
- package/dist/FastMCP.js +1 -1
- package/dist/{chunk-O72VCMGV.cjs → chunk-B5WJ5VRV.cjs} +255 -184
- package/dist/chunk-B5WJ5VRV.cjs.map +1 -0
- package/dist/{chunk-3BMGHXXY.js → chunk-SGCRCQNU.js} +225 -154
- package/dist/chunk-SGCRCQNU.js.map +1 -0
- package/dist/examples/custom-routes.cjs +2 -2
- package/dist/examples/custom-routes.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-3BMGHXXY.js.map +0 -1
- package/dist/chunk-O72VCMGV.cjs.map +0 -1
|
@@ -355,6 +355,8 @@ function assertToolSchemas(tool) {
|
|
|
355
355
|
assertStandardSchema(tool.name, "outputSchema", tool.outputSchema);
|
|
356
356
|
}
|
|
357
357
|
}
|
|
358
|
+
var STREAM_KEEPALIVE_LOGGER = "fastmcp-keepalive";
|
|
359
|
+
var STREAM_KEEPALIVE_DEFAULT_INTERVAL_MS = 2e4;
|
|
358
360
|
var TextContentZodSchema = z.object({
|
|
359
361
|
/**
|
|
360
362
|
* The text content of the message.
|
|
@@ -421,10 +423,23 @@ var CompletionZodSchema = z.object({
|
|
|
421
423
|
*/
|
|
422
424
|
total: z.optional(z.number().int()),
|
|
423
425
|
/**
|
|
424
|
-
* An array of completion values.
|
|
426
|
+
* An array of completion values. The MCP spec caps this at 100 items; values
|
|
427
|
+
* beyond the cap are trimmed by `capCompletionValues` (which sets `hasMore`)
|
|
428
|
+
* rather than rejected, so the schema itself does not enforce the limit.
|
|
425
429
|
*/
|
|
426
|
-
values: z.array(z.string())
|
|
430
|
+
values: z.array(z.string())
|
|
427
431
|
});
|
|
432
|
+
var COMPLETION_VALUES_LIMIT = 100;
|
|
433
|
+
var capCompletionValues = (completion) => {
|
|
434
|
+
if (completion.values.length <= COMPLETION_VALUES_LIMIT) {
|
|
435
|
+
return completion;
|
|
436
|
+
}
|
|
437
|
+
return {
|
|
438
|
+
...completion,
|
|
439
|
+
hasMore: true,
|
|
440
|
+
values: completion.values.slice(0, COMPLETION_VALUES_LIMIT)
|
|
441
|
+
};
|
|
442
|
+
};
|
|
428
443
|
var FastMCPSessionEventEmitterBase = EventEmitter;
|
|
429
444
|
var ServerState = /* @__PURE__ */ ((ServerState2) => {
|
|
430
445
|
ServerState2["Error"] = "error";
|
|
@@ -483,6 +498,7 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
|
|
|
483
498
|
* instance — so capabilities can never be inferred here.
|
|
484
499
|
*/
|
|
485
500
|
#stateless;
|
|
501
|
+
#streamKeepaliveConfig;
|
|
486
502
|
/**
|
|
487
503
|
* Resource URIs the connected client has subscribed to via
|
|
488
504
|
* `resources/subscribe`. Used to scope `notifications/resources/updated`
|
|
@@ -503,6 +519,7 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
|
|
|
503
519
|
roots,
|
|
504
520
|
sessionId,
|
|
505
521
|
stateless = false,
|
|
522
|
+
streamKeepalive,
|
|
506
523
|
tools,
|
|
507
524
|
transportType,
|
|
508
525
|
utils,
|
|
@@ -516,6 +533,7 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
|
|
|
516
533
|
this.#rootsConfig = roots;
|
|
517
534
|
this.#sessionId = sessionId;
|
|
518
535
|
this.#stateless = stateless;
|
|
536
|
+
this.#streamKeepaliveConfig = streamKeepalive;
|
|
519
537
|
this.#needsEventLoopFlush = transportType === "httpStream";
|
|
520
538
|
if (tools.length) {
|
|
521
539
|
this.#capabilities.tools = {};
|
|
@@ -843,6 +861,44 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
|
|
|
843
861
|
logLevel: pingConfig.logLevel || "debug"
|
|
844
862
|
};
|
|
845
863
|
}
|
|
864
|
+
/**
|
|
865
|
+
* Periodically writes to the response stream of an in-flight tool call, so an
|
|
866
|
+
* idle-connection timeout (proxy, load balancer) does not close it while a
|
|
867
|
+
* long-running tool produces no output of its own.
|
|
868
|
+
*
|
|
869
|
+
* The notification is related to the tool call, so it travels on that
|
|
870
|
+
* request's own stream, which is the only server-to-client route that exists
|
|
871
|
+
* when running stateless.
|
|
872
|
+
*
|
|
873
|
+
* @returns a function that stops the keepalive.
|
|
874
|
+
*/
|
|
875
|
+
#startStreamKeepalive(extra, toolName) {
|
|
876
|
+
const config = this.#streamKeepaliveConfig;
|
|
877
|
+
if (!config?.enabled) {
|
|
878
|
+
return () => {
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
const intervalMs = config.intervalMs && config.intervalMs > 0 ? config.intervalMs : STREAM_KEEPALIVE_DEFAULT_INTERVAL_MS;
|
|
882
|
+
const timer = setInterval(() => {
|
|
883
|
+
extra.sendNotification({
|
|
884
|
+
method: "notifications/message",
|
|
885
|
+
params: {
|
|
886
|
+
data: { message: `keepalive while '${toolName}' is running` },
|
|
887
|
+
level: config.logLevel ?? "debug",
|
|
888
|
+
logger: STREAM_KEEPALIVE_LOGGER
|
|
889
|
+
}
|
|
890
|
+
}).catch((error) => {
|
|
891
|
+
this.#logger.debug(
|
|
892
|
+
`[FastMCP debug] stream keepalive for '${toolName}' failed:`,
|
|
893
|
+
error instanceof Error ? error.message : String(error)
|
|
894
|
+
);
|
|
895
|
+
});
|
|
896
|
+
}, intervalMs);
|
|
897
|
+
timer.unref?.();
|
|
898
|
+
const stop = () => clearInterval(timer);
|
|
899
|
+
extra.signal?.addEventListener("abort", stop, { once: true });
|
|
900
|
+
return stop;
|
|
901
|
+
}
|
|
846
902
|
async #validateStructuredContent(tool, value, toolName) {
|
|
847
903
|
if (!tool.outputSchema) {
|
|
848
904
|
return value;
|
|
@@ -936,11 +992,13 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
|
|
|
936
992
|
request
|
|
937
993
|
});
|
|
938
994
|
}
|
|
939
|
-
const completion =
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
995
|
+
const completion = capCompletionValues(
|
|
996
|
+
CompletionZodSchema.parse(
|
|
997
|
+
await prompt.complete(
|
|
998
|
+
request.params.argument.name,
|
|
999
|
+
request.params.argument.value,
|
|
1000
|
+
this.#auth
|
|
1001
|
+
)
|
|
944
1002
|
)
|
|
945
1003
|
);
|
|
946
1004
|
return {
|
|
@@ -968,11 +1026,13 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
|
|
|
968
1026
|
}
|
|
969
1027
|
);
|
|
970
1028
|
}
|
|
971
|
-
const completion =
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1029
|
+
const completion = capCompletionValues(
|
|
1030
|
+
CompletionZodSchema.parse(
|
|
1031
|
+
await resource.complete(
|
|
1032
|
+
request.params.argument.name,
|
|
1033
|
+
request.params.argument.value,
|
|
1034
|
+
this.#auth
|
|
1035
|
+
)
|
|
976
1036
|
)
|
|
977
1037
|
);
|
|
978
1038
|
return {
|
|
@@ -1260,169 +1320,178 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
|
|
|
1260
1320
|
tools: cachedToolsList
|
|
1261
1321
|
};
|
|
1262
1322
|
});
|
|
1263
|
-
this.#server.setRequestHandler(
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
`Unknown tool: ${request.params.name}`
|
|
1269
|
-
);
|
|
1270
|
-
}
|
|
1271
|
-
let args = void 0;
|
|
1272
|
-
if (tool.parameters) {
|
|
1273
|
-
const parsed = await tool.parameters["~standard"].validate(
|
|
1274
|
-
request.params.arguments
|
|
1275
|
-
);
|
|
1276
|
-
if (parsed.issues) {
|
|
1277
|
-
const friendlyErrors = this.#formatSchemaIssues(parsed.issues);
|
|
1323
|
+
this.#server.setRequestHandler(
|
|
1324
|
+
CallToolRequestSchema,
|
|
1325
|
+
async (request, extra) => {
|
|
1326
|
+
const tool = toolsMap.get(request.params.name);
|
|
1327
|
+
if (!tool) {
|
|
1278
1328
|
throw new McpError(
|
|
1279
|
-
ErrorCode.
|
|
1280
|
-
`
|
|
1329
|
+
ErrorCode.MethodNotFound,
|
|
1330
|
+
`Unknown tool: ${request.params.name}`
|
|
1281
1331
|
);
|
|
1282
1332
|
}
|
|
1283
|
-
args =
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1333
|
+
let args = void 0;
|
|
1334
|
+
if (tool.parameters) {
|
|
1335
|
+
const parsed = await tool.parameters["~standard"].validate(
|
|
1336
|
+
request.params.arguments
|
|
1337
|
+
);
|
|
1338
|
+
if (parsed.issues) {
|
|
1339
|
+
const friendlyErrors = this.#formatSchemaIssues(parsed.issues);
|
|
1340
|
+
throw new McpError(
|
|
1341
|
+
ErrorCode.InvalidParams,
|
|
1342
|
+
`Tool '${request.params.name}' parameter validation failed: ${friendlyErrors}. Please check the parameter types and values according to the tool's schema.`
|
|
1343
|
+
);
|
|
1291
1344
|
}
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1345
|
+
args = parsed.value;
|
|
1346
|
+
}
|
|
1347
|
+
const progressToken = request.params?._meta?.progressToken;
|
|
1348
|
+
let result;
|
|
1349
|
+
try {
|
|
1350
|
+
const reportProgress = async (progress) => {
|
|
1351
|
+
if (progressToken === void 0) {
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
try {
|
|
1355
|
+
await this.#server.notification({
|
|
1356
|
+
method: "notifications/progress",
|
|
1357
|
+
params: {
|
|
1358
|
+
...progress,
|
|
1359
|
+
progressToken
|
|
1360
|
+
}
|
|
1361
|
+
});
|
|
1362
|
+
if (this.#needsEventLoopFlush) {
|
|
1363
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
1298
1364
|
}
|
|
1299
|
-
})
|
|
1300
|
-
|
|
1301
|
-
|
|
1365
|
+
} catch (progressError) {
|
|
1366
|
+
this.#logger.warn(
|
|
1367
|
+
`[FastMCP warning] Failed to report progress for tool '${request.params.name}':`,
|
|
1368
|
+
progressError instanceof Error ? progressError.message : String(progressError)
|
|
1369
|
+
);
|
|
1302
1370
|
}
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
content: contentArray,
|
|
1318
|
-
toolName: request.params.name
|
|
1371
|
+
};
|
|
1372
|
+
const log = this.#createLog();
|
|
1373
|
+
const streamContent = async (content) => {
|
|
1374
|
+
const contentArray = Array.isArray(content) ? content : [content];
|
|
1375
|
+
try {
|
|
1376
|
+
await this.#server.notification({
|
|
1377
|
+
method: "notifications/tool/streamContent",
|
|
1378
|
+
params: {
|
|
1379
|
+
content: contentArray,
|
|
1380
|
+
toolName: request.params.name
|
|
1381
|
+
}
|
|
1382
|
+
});
|
|
1383
|
+
if (this.#needsEventLoopFlush) {
|
|
1384
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
1319
1385
|
}
|
|
1320
|
-
})
|
|
1321
|
-
|
|
1322
|
-
|
|
1386
|
+
} catch (streamError) {
|
|
1387
|
+
this.#logger.warn(
|
|
1388
|
+
`[FastMCP warning] Failed to stream content for tool '${request.params.name}':`,
|
|
1389
|
+
streamError instanceof Error ? streamError.message : String(streamError)
|
|
1390
|
+
);
|
|
1323
1391
|
}
|
|
1324
|
-
}
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1392
|
+
};
|
|
1393
|
+
if (this.#onToolCall) {
|
|
1394
|
+
await this.#onToolCall({
|
|
1395
|
+
arguments: args ?? {},
|
|
1396
|
+
toolName: request.params.name
|
|
1397
|
+
});
|
|
1329
1398
|
}
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
)
|
|
1399
|
+
const executeToolPromise = Promise.resolve(
|
|
1400
|
+
tool.execute(args, {
|
|
1401
|
+
client: {
|
|
1402
|
+
version: this.#server.getClientVersion()
|
|
1403
|
+
},
|
|
1404
|
+
elicit: (params, options) => this.#server.elicitInput(params, options),
|
|
1405
|
+
log,
|
|
1406
|
+
reportProgress,
|
|
1407
|
+
requestId: typeof request.params?._meta?.requestId === "string" ? request.params._meta.requestId : void 0,
|
|
1408
|
+
session: this.#auth,
|
|
1409
|
+
sessionId: this.#sessionId,
|
|
1410
|
+
streamContent
|
|
1411
|
+
})
|
|
1412
|
+
);
|
|
1413
|
+
const stopStreamKeepalive = this.#startStreamKeepalive(
|
|
1414
|
+
extra,
|
|
1415
|
+
request.params.name
|
|
1416
|
+
);
|
|
1417
|
+
const maybeStringResult = await (tool.timeoutMs ? Promise.race([
|
|
1418
|
+
executeToolPromise,
|
|
1419
|
+
new Promise((_, reject) => {
|
|
1420
|
+
const timeoutId = setTimeout(() => {
|
|
1421
|
+
reject(
|
|
1422
|
+
new UserError(
|
|
1423
|
+
`Tool '${request.params.name}' timed out after ${tool.timeoutMs}ms. Consider increasing timeoutMs or optimizing the tool implementation.`
|
|
1424
|
+
)
|
|
1425
|
+
);
|
|
1426
|
+
}, tool.timeoutMs);
|
|
1427
|
+
executeToolPromise.then(
|
|
1428
|
+
() => clearTimeout(timeoutId),
|
|
1429
|
+
() => clearTimeout(timeoutId)
|
|
1357
1430
|
);
|
|
1358
|
-
}
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
})
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
})
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1431
|
+
})
|
|
1432
|
+
]) : executeToolPromise).finally(stopStreamKeepalive);
|
|
1433
|
+
await delay(1);
|
|
1434
|
+
if (maybeStringResult === void 0 || maybeStringResult === null) {
|
|
1435
|
+
result = ContentResultZodSchema.parse({
|
|
1436
|
+
content: []
|
|
1437
|
+
});
|
|
1438
|
+
} else if (typeof maybeStringResult === "string") {
|
|
1439
|
+
result = ContentResultZodSchema.parse({
|
|
1440
|
+
content: [{ text: maybeStringResult, type: "text" }]
|
|
1441
|
+
});
|
|
1442
|
+
} else if ("type" in maybeStringResult) {
|
|
1443
|
+
result = ContentResultZodSchema.parse({
|
|
1444
|
+
content: [maybeStringResult]
|
|
1445
|
+
});
|
|
1446
|
+
} else if ("content" in maybeStringResult) {
|
|
1447
|
+
result = ContentResultZodSchema.parse(maybeStringResult);
|
|
1448
|
+
if (result.structuredContent !== void 0 && tool.outputSchema) {
|
|
1449
|
+
result.structuredContent = await this.#validateStructuredContent(
|
|
1450
|
+
tool,
|
|
1451
|
+
result.structuredContent,
|
|
1452
|
+
request.params.name
|
|
1453
|
+
);
|
|
1454
|
+
}
|
|
1455
|
+
} else if (tool.outputSchema) {
|
|
1456
|
+
const structuredContent = await this.#validateStructuredContent(
|
|
1382
1457
|
tool,
|
|
1383
|
-
|
|
1458
|
+
maybeStringResult,
|
|
1384
1459
|
request.params.name
|
|
1385
1460
|
);
|
|
1461
|
+
result = ContentResultZodSchema.parse({
|
|
1462
|
+
content: [
|
|
1463
|
+
{
|
|
1464
|
+
text: JSON.stringify(structuredContent),
|
|
1465
|
+
type: "text"
|
|
1466
|
+
}
|
|
1467
|
+
],
|
|
1468
|
+
structuredContent
|
|
1469
|
+
});
|
|
1470
|
+
} else {
|
|
1471
|
+
result = ContentResultZodSchema.parse(maybeStringResult);
|
|
1386
1472
|
}
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1473
|
+
} catch (error) {
|
|
1474
|
+
if (error instanceof UserError) {
|
|
1475
|
+
return {
|
|
1476
|
+
content: [{ text: error.message, type: "text" }],
|
|
1477
|
+
isError: true,
|
|
1478
|
+
...error.extras ? { structuredContent: error.extras } : {}
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1481
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1482
|
+
return {
|
|
1394
1483
|
content: [
|
|
1395
1484
|
{
|
|
1396
|
-
text:
|
|
1485
|
+
text: `Tool '${request.params.name}' execution failed: ${errorMessage}`,
|
|
1397
1486
|
type: "text"
|
|
1398
1487
|
}
|
|
1399
1488
|
],
|
|
1400
|
-
|
|
1401
|
-
});
|
|
1402
|
-
} else {
|
|
1403
|
-
result = ContentResultZodSchema.parse(maybeStringResult);
|
|
1404
|
-
}
|
|
1405
|
-
} catch (error) {
|
|
1406
|
-
if (error instanceof UserError) {
|
|
1407
|
-
return {
|
|
1408
|
-
content: [{ text: error.message, type: "text" }],
|
|
1409
|
-
isError: true,
|
|
1410
|
-
...error.extras ? { structuredContent: error.extras } : {}
|
|
1489
|
+
isError: true
|
|
1411
1490
|
};
|
|
1412
1491
|
}
|
|
1413
|
-
|
|
1414
|
-
return {
|
|
1415
|
-
content: [
|
|
1416
|
-
{
|
|
1417
|
-
text: `Tool '${request.params.name}' execution failed: ${errorMessage}`,
|
|
1418
|
-
type: "text"
|
|
1419
|
-
}
|
|
1420
|
-
],
|
|
1421
|
-
isError: true
|
|
1422
|
-
};
|
|
1492
|
+
return result;
|
|
1423
1493
|
}
|
|
1424
|
-
|
|
1425
|
-
});
|
|
1494
|
+
);
|
|
1426
1495
|
}
|
|
1427
1496
|
};
|
|
1428
1497
|
function camelToSnakeCase(str) {
|
|
@@ -1863,6 +1932,7 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
1863
1932
|
resources: this.#resources,
|
|
1864
1933
|
resourcesTemplates: this.#resourcesTemplates,
|
|
1865
1934
|
roots: this.#options.roots,
|
|
1935
|
+
streamKeepalive: this.#options.streamKeepalive,
|
|
1866
1936
|
tools: this.#tools,
|
|
1867
1937
|
transportType: "stdio",
|
|
1868
1938
|
utils: this.#options.utils,
|
|
@@ -2062,6 +2132,7 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
2062
2132
|
roots: this.#options.roots,
|
|
2063
2133
|
sessionId,
|
|
2064
2134
|
stateless,
|
|
2135
|
+
streamKeepalive: this.#options.streamKeepalive,
|
|
2065
2136
|
tools: allowedTools,
|
|
2066
2137
|
transportType: "httpStream",
|
|
2067
2138
|
utils: this.#options.utils,
|
|
@@ -2676,4 +2747,4 @@ export {
|
|
|
2676
2747
|
FastMCPSession,
|
|
2677
2748
|
FastMCP
|
|
2678
2749
|
};
|
|
2679
|
-
//# sourceMappingURL=chunk-
|
|
2750
|
+
//# sourceMappingURL=chunk-SGCRCQNU.js.map
|