fastmcp 4.15.2 → 4.16.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 +44 -0
- package/dist/FastMCP.cjs +2 -2
- package/dist/FastMCP.d.cts +27 -6
- package/dist/FastMCP.d.ts +27 -6
- package/dist/FastMCP.js +1 -1
- package/dist/{chunk-4VIPI4GU.js → chunk-EL6BTW4Y.js} +53 -13
- package/dist/chunk-EL6BTW4Y.js.map +1 -0
- package/dist/{chunk-N6ZZF5YB.cjs → chunk-RCOJI5RM.cjs} +93 -53
- package/dist/chunk-RCOJI5RM.cjs.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-4VIPI4GU.js.map +0 -1
- package/dist/chunk-N6ZZF5YB.cjs.map +0 -1
package/README.md
CHANGED
|
@@ -949,6 +949,50 @@ Limits worth knowing:
|
|
|
949
949
|
- On `stdio` there is no proxy to keep alive, so enabling it there only adds
|
|
950
950
|
notification traffic.
|
|
951
951
|
|
|
952
|
+
#### Cancelling Long Tool Calls (`context.signal`)
|
|
953
|
+
|
|
954
|
+
Every `execute` receives an `AbortSignal` that fires once its result can no
|
|
955
|
+
longer reach anyone. Forward it to whatever does the real work so the work stops
|
|
956
|
+
with the call instead of outliving it:
|
|
957
|
+
|
|
958
|
+
```ts
|
|
959
|
+
server.addTool({
|
|
960
|
+
name: "fetch_report",
|
|
961
|
+
parameters: z.object({ url: z.string() }),
|
|
962
|
+
timeoutMs: 30000,
|
|
963
|
+
execute: async (args, { signal }) => {
|
|
964
|
+
const response = await fetch(args.url, { signal });
|
|
965
|
+
return await response.text();
|
|
966
|
+
},
|
|
967
|
+
});
|
|
968
|
+
```
|
|
969
|
+
|
|
970
|
+
It aborts on any of three events:
|
|
971
|
+
|
|
972
|
+
- **The client cancelled the call** — it sent `notifications/cancelled`, which is
|
|
973
|
+
what the MCP SDK emits when a caller aborts its own request.
|
|
974
|
+
- **The session ended** — the transport closed, or the session was closed
|
|
975
|
+
explicitly. No cancellation notification is involved here.
|
|
976
|
+
- **`timeoutMs` elapsed** — `signal.reason` is the same `UserError` the caller
|
|
977
|
+
receives, so a tool can tell a timeout apart from a cancellation.
|
|
978
|
+
|
|
979
|
+
The signal is never aborted after a call completes normally, so attaching
|
|
980
|
+
cleanup to it is safe.
|
|
981
|
+
|
|
982
|
+
Limits worth knowing:
|
|
983
|
+
|
|
984
|
+
- **Nothing is killed for you.** FastMCP stops waiting for the tool, but the
|
|
985
|
+
promise `execute` returned keeps running until it settles. A tool that ignores
|
|
986
|
+
the signal still runs to completion — it just does so with nowhere to report.
|
|
987
|
+
- **An HTTP client that vanishes mid-request is not detected.** The MCP SDK only
|
|
988
|
+
aborts a request's own signal for an explicit `notifications/cancelled`, and
|
|
989
|
+
`StreamableHTTPServerTransport` does not treat an abandoned response stream as
|
|
990
|
+
a session close. A caller that hangs up without terminating its session
|
|
991
|
+
(`DELETE`) leaves the tool running until it finishes or times out. Set
|
|
992
|
+
`timeoutMs` on anything expensive rather than relying on disconnect detection.
|
|
993
|
+
- **`load` does not get one.** Resources, resource templates, and prompts
|
|
994
|
+
receive a smaller context without `signal`.
|
|
995
|
+
|
|
952
996
|
### Health-check Endpoint
|
|
953
997
|
|
|
954
998
|
When you run FastMCP with the `httpStream` transport you can optionally expose a
|
package/dist/FastMCP.cjs
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
var
|
|
14
|
+
var _chunkRCOJI5RMcjs = require('./chunk-RCOJI5RM.cjs');
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
|
|
@@ -49,5 +49,5 @@ var _chunk5FVNY65Mcjs = require('./chunk-5FVNY65M.cjs');
|
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
exports.AuthProvider = _chunk5FVNY65Mcjs.AuthProvider; exports.AzureProvider = _chunk5FVNY65Mcjs.AzureProvider; exports.DiscoveryDocumentCache =
|
|
52
|
+
exports.AuthProvider = _chunk5FVNY65Mcjs.AuthProvider; exports.AzureProvider = _chunk5FVNY65Mcjs.AzureProvider; exports.DiscoveryDocumentCache = _chunkRCOJI5RMcjs.DiscoveryDocumentCache; exports.FastMCP = _chunkRCOJI5RMcjs.FastMCP; exports.FastMCPError = _chunkRCOJI5RMcjs.FastMCPError; exports.FastMCPSession = _chunkRCOJI5RMcjs.FastMCPSession; exports.GitHubProvider = _chunk5FVNY65Mcjs.GitHubProvider; exports.GoogleProvider = _chunk5FVNY65Mcjs.GoogleProvider; exports.MEDIA_FETCH_TIMEOUT_MS = _chunkRCOJI5RMcjs.MEDIA_FETCH_TIMEOUT_MS; exports.OAuthProvider = _chunk5FVNY65Mcjs.OAuthProvider; exports.ServerState = _chunkRCOJI5RMcjs.ServerState; exports.SessionError = _chunkRCOJI5RMcjs.SessionError; exports.UnexpectedStateError = _chunkRCOJI5RMcjs.UnexpectedStateError; exports.UserError = _chunkRCOJI5RMcjs.UserError; exports.audioContent = _chunkRCOJI5RMcjs.audioContent; exports.getAuthSession = _chunk5FVNY65Mcjs.getAuthSession; exports.imageContent = _chunkRCOJI5RMcjs.imageContent; exports.jsonSchemaAdapter = _chunkRCOJI5RMcjs.jsonSchemaAdapter; exports.requireAll = _chunk5FVNY65Mcjs.requireAll; exports.requireAny = _chunk5FVNY65Mcjs.requireAny; exports.requireAuth = _chunk5FVNY65Mcjs.requireAuth; exports.requireRole = _chunk5FVNY65Mcjs.requireRole; exports.requireScopes = _chunk5FVNY65Mcjs.requireScopes;
|
|
53
53
|
//# sourceMappingURL=FastMCP.cjs.map
|
package/dist/FastMCP.d.cts
CHANGED
|
@@ -190,6 +190,19 @@ type Context<T extends FastMCPSessionAuth> = {
|
|
|
190
190
|
* counters, or maintain user-specific data across multiple requests.
|
|
191
191
|
*/
|
|
192
192
|
sessionId?: string;
|
|
193
|
+
/**
|
|
194
|
+
* Aborted once the tool's result can no longer reach anyone: the client
|
|
195
|
+
* cancelled the call, the session went away, or `timeoutMs` elapsed.
|
|
196
|
+
*
|
|
197
|
+
* Nothing is killed on your behalf — FastMCP stops waiting, but the promise
|
|
198
|
+
* `execute` returned keeps running until it settles. Forward this signal to
|
|
199
|
+
* whatever does the real work (`fetch`, a database driver, a subprocess) so
|
|
200
|
+
* the work stops with the call instead of outliving it.
|
|
201
|
+
*
|
|
202
|
+
* It is never aborted after a call completes normally, so it is safe to
|
|
203
|
+
* attach cleanup to it.
|
|
204
|
+
*/
|
|
205
|
+
signal: AbortSignal;
|
|
193
206
|
/**
|
|
194
207
|
* Streams incremental content while the tool is still executing, by emitting
|
|
195
208
|
* a `notifications/tool/streamContent` notification.
|
|
@@ -216,9 +229,11 @@ type Literal = boolean | null | number | string | undefined;
|
|
|
216
229
|
*
|
|
217
230
|
* This is a subset of the tool execution {@link Context}. `reportProgress`
|
|
218
231
|
* and `streamContent` are tied to a tool call's progress token / streaming
|
|
219
|
-
* notification and are not available outside of `tool.execute`.
|
|
232
|
+
* notification and are not available outside of `tool.execute`. `signal` is
|
|
233
|
+
* omitted too: its timeout leg comes from `tool.timeoutMs`, which `load` has
|
|
234
|
+
* no equivalent of.
|
|
220
235
|
*/
|
|
221
|
-
type LoadContext<T extends FastMCPSessionAuth> = Omit<Context<T>, "reportProgress" | "streamContent">;
|
|
236
|
+
type LoadContext<T extends FastMCPSessionAuth> = Omit<Context<T>, "reportProgress" | "signal" | "streamContent">;
|
|
222
237
|
type Progress = {
|
|
223
238
|
/**
|
|
224
239
|
* An optional human-readable message describing the current progress.
|
|
@@ -887,10 +902,16 @@ declare class FastMCPSession<T extends FastMCPSessionAuth = FastMCPSessionAuth>
|
|
|
887
902
|
/**
|
|
888
903
|
* The HTTP session ID, or `undefined` for transports that do not have one.
|
|
889
904
|
*
|
|
890
|
-
* Resolved from the transport
|
|
891
|
-
*
|
|
892
|
-
*
|
|
893
|
-
*
|
|
905
|
+
* Resolved from the transport rather than captured when the session
|
|
906
|
+
* connects: `StreamableHTTPServerTransport` assigns its `sessionId` while it
|
|
907
|
+
* handles `initialize`, which happens after `connect()` resolves. A value
|
|
908
|
+
* read at connect time is therefore still `undefined`.
|
|
909
|
+
*
|
|
910
|
+
* The first ID seen is latched, so the session keeps reporting it once the
|
|
911
|
+
* transport detaches — `Protocol` drops its transport reference on close,
|
|
912
|
+
* which would otherwise make the ID vanish mid-teardown. Latching is safe
|
|
913
|
+
* because {@link connect} refuses a second transport, so a session never
|
|
914
|
+
* sees two IDs.
|
|
894
915
|
*/
|
|
895
916
|
get sessionId(): string | undefined;
|
|
896
917
|
set sessionId(value: string | undefined);
|
package/dist/FastMCP.d.ts
CHANGED
|
@@ -190,6 +190,19 @@ type Context<T extends FastMCPSessionAuth> = {
|
|
|
190
190
|
* counters, or maintain user-specific data across multiple requests.
|
|
191
191
|
*/
|
|
192
192
|
sessionId?: string;
|
|
193
|
+
/**
|
|
194
|
+
* Aborted once the tool's result can no longer reach anyone: the client
|
|
195
|
+
* cancelled the call, the session went away, or `timeoutMs` elapsed.
|
|
196
|
+
*
|
|
197
|
+
* Nothing is killed on your behalf — FastMCP stops waiting, but the promise
|
|
198
|
+
* `execute` returned keeps running until it settles. Forward this signal to
|
|
199
|
+
* whatever does the real work (`fetch`, a database driver, a subprocess) so
|
|
200
|
+
* the work stops with the call instead of outliving it.
|
|
201
|
+
*
|
|
202
|
+
* It is never aborted after a call completes normally, so it is safe to
|
|
203
|
+
* attach cleanup to it.
|
|
204
|
+
*/
|
|
205
|
+
signal: AbortSignal;
|
|
193
206
|
/**
|
|
194
207
|
* Streams incremental content while the tool is still executing, by emitting
|
|
195
208
|
* a `notifications/tool/streamContent` notification.
|
|
@@ -216,9 +229,11 @@ type Literal = boolean | null | number | string | undefined;
|
|
|
216
229
|
*
|
|
217
230
|
* This is a subset of the tool execution {@link Context}. `reportProgress`
|
|
218
231
|
* and `streamContent` are tied to a tool call's progress token / streaming
|
|
219
|
-
* notification and are not available outside of `tool.execute`.
|
|
232
|
+
* notification and are not available outside of `tool.execute`. `signal` is
|
|
233
|
+
* omitted too: its timeout leg comes from `tool.timeoutMs`, which `load` has
|
|
234
|
+
* no equivalent of.
|
|
220
235
|
*/
|
|
221
|
-
type LoadContext<T extends FastMCPSessionAuth> = Omit<Context<T>, "reportProgress" | "streamContent">;
|
|
236
|
+
type LoadContext<T extends FastMCPSessionAuth> = Omit<Context<T>, "reportProgress" | "signal" | "streamContent">;
|
|
222
237
|
type Progress = {
|
|
223
238
|
/**
|
|
224
239
|
* An optional human-readable message describing the current progress.
|
|
@@ -887,10 +902,16 @@ declare class FastMCPSession<T extends FastMCPSessionAuth = FastMCPSessionAuth>
|
|
|
887
902
|
/**
|
|
888
903
|
* The HTTP session ID, or `undefined` for transports that do not have one.
|
|
889
904
|
*
|
|
890
|
-
* Resolved from the transport
|
|
891
|
-
*
|
|
892
|
-
*
|
|
893
|
-
*
|
|
905
|
+
* Resolved from the transport rather than captured when the session
|
|
906
|
+
* connects: `StreamableHTTPServerTransport` assigns its `sessionId` while it
|
|
907
|
+
* handles `initialize`, which happens after `connect()` resolves. A value
|
|
908
|
+
* read at connect time is therefore still `undefined`.
|
|
909
|
+
*
|
|
910
|
+
* The first ID seen is latched, so the session keeps reporting it once the
|
|
911
|
+
* transport detaches — `Protocol` drops its transport reference on close,
|
|
912
|
+
* which would otherwise make the ID vanish mid-teardown. Latching is safe
|
|
913
|
+
* because {@link connect} refuses a second transport, so a session never
|
|
914
|
+
* sees two IDs.
|
|
894
915
|
*/
|
|
895
916
|
get sessionId(): string | undefined;
|
|
896
917
|
set sessionId(value: string | undefined);
|
package/dist/FastMCP.js
CHANGED
|
@@ -467,21 +467,38 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
|
|
|
467
467
|
/**
|
|
468
468
|
* The HTTP session ID, or `undefined` for transports that do not have one.
|
|
469
469
|
*
|
|
470
|
-
* Resolved from the transport
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
470
|
+
* Resolved from the transport rather than captured when the session
|
|
471
|
+
* connects: `StreamableHTTPServerTransport` assigns its `sessionId` while it
|
|
472
|
+
* handles `initialize`, which happens after `connect()` resolves. A value
|
|
473
|
+
* read at connect time is therefore still `undefined`.
|
|
474
|
+
*
|
|
475
|
+
* The first ID seen is latched, so the session keeps reporting it once the
|
|
476
|
+
* transport detaches — `Protocol` drops its transport reference on close,
|
|
477
|
+
* which would otherwise make the ID vanish mid-teardown. Latching is safe
|
|
478
|
+
* because {@link connect} refuses a second transport, so a session never
|
|
479
|
+
* sees two IDs.
|
|
474
480
|
*/
|
|
475
481
|
get sessionId() {
|
|
476
|
-
if (this.#sessionId
|
|
477
|
-
|
|
482
|
+
if (this.#sessionId === void 0) {
|
|
483
|
+
const transportSessionId = this.#server.transport?.sessionId;
|
|
484
|
+
if (typeof transportSessionId === "string") {
|
|
485
|
+
this.#sessionId = transportSessionId;
|
|
486
|
+
}
|
|
478
487
|
}
|
|
479
|
-
|
|
480
|
-
return typeof transport?.sessionId === "string" ? transport.sessionId : void 0;
|
|
488
|
+
return this.#sessionId;
|
|
481
489
|
}
|
|
482
490
|
set sessionId(value) {
|
|
483
491
|
this.#sessionId = value;
|
|
484
492
|
}
|
|
493
|
+
/**
|
|
494
|
+
* Aborted once the session ends, and folded into the `signal` every tool
|
|
495
|
+
* call receives. The MCP SDK only aborts a request's own signal for an
|
|
496
|
+
* explicit `notifications/cancelled`, and neither `Protocol` nor
|
|
497
|
+
* `StreamableHTTPServerTransport` touches it when the transport simply goes
|
|
498
|
+
* away — so without this a tool keeps running after the client that asked
|
|
499
|
+
* for it has hung up.
|
|
500
|
+
*/
|
|
501
|
+
#abortController = new AbortController();
|
|
485
502
|
#auth;
|
|
486
503
|
#capabilities = {};
|
|
487
504
|
#clientCapabilities;
|
|
@@ -603,6 +620,7 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
|
|
|
603
620
|
if (this.#pingInterval) {
|
|
604
621
|
clearInterval(this.#pingInterval);
|
|
605
622
|
}
|
|
623
|
+
this.#abortSession();
|
|
606
624
|
try {
|
|
607
625
|
await this.#server.close();
|
|
608
626
|
} catch (error) {
|
|
@@ -793,6 +811,16 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
|
|
|
793
811
|
});
|
|
794
812
|
});
|
|
795
813
|
}
|
|
814
|
+
/**
|
|
815
|
+
* Cancels the `signal` held by every tool still executing on this session.
|
|
816
|
+
* Idempotent, so the close path and the transport's own close handler can
|
|
817
|
+
* both call it.
|
|
818
|
+
*/
|
|
819
|
+
#abortSession() {
|
|
820
|
+
if (!this.#abortController.signal.aborted) {
|
|
821
|
+
this.#abortController.abort(new SessionError("Session closed"));
|
|
822
|
+
}
|
|
823
|
+
}
|
|
796
824
|
/**
|
|
797
825
|
* Builds the context object passed as the third argument to
|
|
798
826
|
* `resource.load` / `resourceTemplate.load` / `prompt.load`.
|
|
@@ -1062,6 +1090,9 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
|
|
|
1062
1090
|
this.#server.onerror = (error) => {
|
|
1063
1091
|
this.#logger.error("[FastMCP error]", error);
|
|
1064
1092
|
};
|
|
1093
|
+
this.#server.onclose = () => {
|
|
1094
|
+
this.#abortSession();
|
|
1095
|
+
};
|
|
1065
1096
|
}
|
|
1066
1097
|
setupLoggingHandlers() {
|
|
1067
1098
|
this.#server.setRequestHandler(SetLevelRequestSchema, (request) => {
|
|
@@ -1410,6 +1441,14 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
|
|
|
1410
1441
|
toolName: request.params.name
|
|
1411
1442
|
});
|
|
1412
1443
|
}
|
|
1444
|
+
const timeoutAbort = new AbortController();
|
|
1445
|
+
const signal = AbortSignal.any([
|
|
1446
|
+
timeoutAbort.signal,
|
|
1447
|
+
this.#abortController.signal,
|
|
1448
|
+
// Only ever aborted for an explicit `notifications/cancelled`; the
|
|
1449
|
+
// session signal above is what covers a client that simply left.
|
|
1450
|
+
...extra.signal ? [extra.signal] : []
|
|
1451
|
+
]);
|
|
1413
1452
|
const executeToolPromise = Promise.resolve(
|
|
1414
1453
|
tool.execute(args, {
|
|
1415
1454
|
client: {
|
|
@@ -1421,6 +1460,7 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
|
|
|
1421
1460
|
requestId: typeof request.params?._meta?.requestId === "string" ? request.params._meta.requestId : void 0,
|
|
1422
1461
|
session: this.#auth,
|
|
1423
1462
|
sessionId: this.sessionId,
|
|
1463
|
+
signal,
|
|
1424
1464
|
streamContent
|
|
1425
1465
|
})
|
|
1426
1466
|
);
|
|
@@ -1432,11 +1472,11 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
|
|
|
1432
1472
|
executeToolPromise,
|
|
1433
1473
|
new Promise((_, reject) => {
|
|
1434
1474
|
const timeoutId = setTimeout(() => {
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
`Tool '${request.params.name}' timed out after ${tool.timeoutMs}ms. Consider increasing timeoutMs or optimizing the tool implementation.`
|
|
1438
|
-
)
|
|
1475
|
+
const timedOut = new UserError(
|
|
1476
|
+
`Tool '${request.params.name}' timed out after ${tool.timeoutMs}ms. Consider increasing timeoutMs or optimizing the tool implementation.`
|
|
1439
1477
|
);
|
|
1478
|
+
timeoutAbort.abort(timedOut);
|
|
1479
|
+
reject(timedOut);
|
|
1440
1480
|
}, tool.timeoutMs);
|
|
1441
1481
|
executeToolPromise.then(
|
|
1442
1482
|
() => clearTimeout(timeoutId),
|
|
@@ -2767,4 +2807,4 @@ export {
|
|
|
2767
2807
|
FastMCPSession,
|
|
2768
2808
|
FastMCP
|
|
2769
2809
|
};
|
|
2770
|
-
//# sourceMappingURL=chunk-
|
|
2810
|
+
//# sourceMappingURL=chunk-EL6BTW4Y.js.map
|