fastmcp 4.15.3 → 4.16.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 +44 -0
- package/dist/FastMCP.cjs +2 -2
- package/dist/FastMCP.d.cts +17 -2
- package/dist/FastMCP.d.ts +17 -2
- package/dist/FastMCP.js +1 -1
- package/dist/{chunk-IXYHFFR6.cjs → chunk-GTUJ3UXA.cjs} +41 -11
- package/dist/chunk-GTUJ3UXA.cjs.map +1 -0
- package/dist/{chunk-UEBA2F55.js → chunk-KLA3HXAN.js} +41 -11
- package/dist/chunk-KLA3HXAN.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-IXYHFFR6.cjs.map +0 -1
- package/dist/chunk-UEBA2F55.js.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 _chunkGTUJ3UXAcjs = require('./chunk-GTUJ3UXA.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 = _chunkGTUJ3UXAcjs.DiscoveryDocumentCache; exports.FastMCP = _chunkGTUJ3UXAcjs.FastMCP; exports.FastMCPError = _chunkGTUJ3UXAcjs.FastMCPError; exports.FastMCPSession = _chunkGTUJ3UXAcjs.FastMCPSession; exports.GitHubProvider = _chunk5FVNY65Mcjs.GitHubProvider; exports.GoogleProvider = _chunk5FVNY65Mcjs.GoogleProvider; exports.MEDIA_FETCH_TIMEOUT_MS = _chunkGTUJ3UXAcjs.MEDIA_FETCH_TIMEOUT_MS; exports.OAuthProvider = _chunk5FVNY65Mcjs.OAuthProvider; exports.ServerState = _chunkGTUJ3UXAcjs.ServerState; exports.SessionError = _chunkGTUJ3UXAcjs.SessionError; exports.UnexpectedStateError = _chunkGTUJ3UXAcjs.UnexpectedStateError; exports.UserError = _chunkGTUJ3UXAcjs.UserError; exports.audioContent = _chunkGTUJ3UXAcjs.audioContent; exports.getAuthSession = _chunk5FVNY65Mcjs.getAuthSession; exports.imageContent = _chunkGTUJ3UXAcjs.imageContent; exports.jsonSchemaAdapter = _chunkGTUJ3UXAcjs.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.
|
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.
|
package/dist/FastMCP.js
CHANGED
|
@@ -490,6 +490,15 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
|
|
|
490
490
|
set sessionId(value) {
|
|
491
491
|
this.#sessionId = value;
|
|
492
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();
|
|
493
502
|
#auth;
|
|
494
503
|
#capabilities = {};
|
|
495
504
|
#clientCapabilities;
|
|
@@ -593,14 +602,12 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
|
|
|
593
602
|
for (const resource of resources) {
|
|
594
603
|
this.addResource(resource);
|
|
595
604
|
}
|
|
605
|
+
for (const resourceTemplate of resourcesTemplates) {
|
|
606
|
+
this.addResourceTemplate(resourceTemplate);
|
|
607
|
+
}
|
|
596
608
|
this.setupResourceHandlers();
|
|
597
609
|
this.setupResourceSubscriptionHandlers();
|
|
598
|
-
|
|
599
|
-
for (const resourceTemplate of resourcesTemplates) {
|
|
600
|
-
this.addResourceTemplate(resourceTemplate);
|
|
601
|
-
}
|
|
602
|
-
this.setupResourceTemplateHandlers();
|
|
603
|
-
}
|
|
610
|
+
this.setupResourceTemplateHandlers();
|
|
604
611
|
}
|
|
605
612
|
if (prompts.length) {
|
|
606
613
|
this.setupPromptHandlers();
|
|
@@ -611,6 +618,7 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
|
|
|
611
618
|
if (this.#pingInterval) {
|
|
612
619
|
clearInterval(this.#pingInterval);
|
|
613
620
|
}
|
|
621
|
+
this.#abortSession();
|
|
614
622
|
try {
|
|
615
623
|
await this.#server.close();
|
|
616
624
|
} catch (error) {
|
|
@@ -801,6 +809,16 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
|
|
|
801
809
|
});
|
|
802
810
|
});
|
|
803
811
|
}
|
|
812
|
+
/**
|
|
813
|
+
* Cancels the `signal` held by every tool still executing on this session.
|
|
814
|
+
* Idempotent, so the close path and the transport's own close handler can
|
|
815
|
+
* both call it.
|
|
816
|
+
*/
|
|
817
|
+
#abortSession() {
|
|
818
|
+
if (!this.#abortController.signal.aborted) {
|
|
819
|
+
this.#abortController.abort(new SessionError("Session closed"));
|
|
820
|
+
}
|
|
821
|
+
}
|
|
804
822
|
/**
|
|
805
823
|
* Builds the context object passed as the third argument to
|
|
806
824
|
* `resource.load` / `resourceTemplate.load` / `prompt.load`.
|
|
@@ -1070,6 +1088,9 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
|
|
|
1070
1088
|
this.#server.onerror = (error) => {
|
|
1071
1089
|
this.#logger.error("[FastMCP error]", error);
|
|
1072
1090
|
};
|
|
1091
|
+
this.#server.onclose = () => {
|
|
1092
|
+
this.#abortSession();
|
|
1093
|
+
};
|
|
1073
1094
|
}
|
|
1074
1095
|
setupLoggingHandlers() {
|
|
1075
1096
|
this.#server.setRequestHandler(_typesjs.SetLevelRequestSchema, (request) => {
|
|
@@ -1418,6 +1439,14 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
|
|
|
1418
1439
|
toolName: request.params.name
|
|
1419
1440
|
});
|
|
1420
1441
|
}
|
|
1442
|
+
const timeoutAbort = new AbortController();
|
|
1443
|
+
const signal = AbortSignal.any([
|
|
1444
|
+
timeoutAbort.signal,
|
|
1445
|
+
this.#abortController.signal,
|
|
1446
|
+
// Only ever aborted for an explicit `notifications/cancelled`; the
|
|
1447
|
+
// session signal above is what covers a client that simply left.
|
|
1448
|
+
...extra.signal ? [extra.signal] : []
|
|
1449
|
+
]);
|
|
1421
1450
|
const executeToolPromise = Promise.resolve(
|
|
1422
1451
|
tool.execute(args, {
|
|
1423
1452
|
client: {
|
|
@@ -1429,6 +1458,7 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
|
|
|
1429
1458
|
requestId: typeof _optionalChain([request, 'access', _41 => _41.params, 'optionalAccess', _42 => _42._meta, 'optionalAccess', _43 => _43.requestId]) === "string" ? request.params._meta.requestId : void 0,
|
|
1430
1459
|
session: this.#auth,
|
|
1431
1460
|
sessionId: this.sessionId,
|
|
1461
|
+
signal,
|
|
1432
1462
|
streamContent
|
|
1433
1463
|
})
|
|
1434
1464
|
);
|
|
@@ -1440,11 +1470,11 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
|
|
|
1440
1470
|
executeToolPromise,
|
|
1441
1471
|
new Promise((_, reject) => {
|
|
1442
1472
|
const timeoutId = setTimeout(() => {
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
`Tool '${request.params.name}' timed out after ${tool.timeoutMs}ms. Consider increasing timeoutMs or optimizing the tool implementation.`
|
|
1446
|
-
)
|
|
1473
|
+
const timedOut = new UserError(
|
|
1474
|
+
`Tool '${request.params.name}' timed out after ${tool.timeoutMs}ms. Consider increasing timeoutMs or optimizing the tool implementation.`
|
|
1447
1475
|
);
|
|
1476
|
+
timeoutAbort.abort(timedOut);
|
|
1477
|
+
reject(timedOut);
|
|
1448
1478
|
}, tool.timeoutMs);
|
|
1449
1479
|
executeToolPromise.then(
|
|
1450
1480
|
() => clearTimeout(timeoutId),
|
|
@@ -2775,4 +2805,4 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
2775
2805
|
|
|
2776
2806
|
|
|
2777
2807
|
exports.DiscoveryDocumentCache = DiscoveryDocumentCache; exports.jsonSchemaAdapter = jsonSchemaAdapter; exports.MEDIA_FETCH_TIMEOUT_MS = MEDIA_FETCH_TIMEOUT_MS; exports.imageContent = imageContent; exports.audioContent = audioContent; exports.FastMCPError = FastMCPError; exports.SessionError = SessionError; exports.UnexpectedStateError = UnexpectedStateError; exports.UserError = UserError; exports.ServerState = ServerState; exports.FastMCPSession = FastMCPSession; exports.FastMCP = FastMCP;
|
|
2778
|
-
//# sourceMappingURL=chunk-
|
|
2808
|
+
//# sourceMappingURL=chunk-GTUJ3UXA.cjs.map
|