fastmcp 4.23.4 → 4.23.6

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.
@@ -9,19 +9,24 @@ import {
9
9
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
10
10
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
11
11
  import {
12
+ AudioContentSchema,
12
13
  CallToolRequestSchema,
13
14
  CompleteRequestSchema,
15
+ EmbeddedResourceSchema,
14
16
  ErrorCode,
15
17
  GetPromptRequestSchema,
18
+ ImageContentSchema,
16
19
  ListPromptsRequestSchema,
17
20
  ListResourcesRequestSchema,
18
21
  ListResourceTemplatesRequestSchema,
19
22
  ListToolsRequestSchema,
20
23
  McpError,
21
24
  ReadResourceRequestSchema,
25
+ ResourceLinkSchema,
22
26
  RootsListChangedNotificationSchema,
23
27
  SetLevelRequestSchema,
24
28
  SubscribeRequestSchema,
29
+ TextContentSchema,
25
30
  UnsubscribeRequestSchema
26
31
  } from "@modelcontextprotocol/sdk/types.js";
27
32
  import { EventEmitter } from "events";
@@ -220,6 +225,16 @@ function unwrapDefault(value) {
220
225
 
221
226
  // src/FastMCP.ts
222
227
  var MEDIA_FETCH_TIMEOUT_MS = 3e4;
228
+ var isSvg = (buffer) => {
229
+ let text = buffer.toString("utf8").trimStart();
230
+ const preamble = /^(?:<\?[\s\S]*?\?>|<!--[\s\S]*?-->|<!DOCTYPE\s[^[>]*(?:\[[\s\S]*?\]\s*)?>)/;
231
+ let match = preamble.exec(text);
232
+ while (match) {
233
+ text = text.slice(match[0].length).trimStart();
234
+ match = preamble.exec(text);
235
+ }
236
+ return /^<svg(?:\s|\/?>)/.test(text);
237
+ };
223
238
  var imageContent = async (input) => {
224
239
  let rawData;
225
240
  try {
@@ -262,16 +277,17 @@ var imageContent = async (input) => {
262
277
  );
263
278
  }
264
279
  const { fileTypeFromBuffer } = await import("file-type");
265
- const mimeType = await fileTypeFromBuffer(rawData);
266
- if (!mimeType || !mimeType.mime.startsWith("image/")) {
280
+ const fileType = await fileTypeFromBuffer(rawData);
281
+ const mimeType = (!fileType || fileType.mime === "application/xml") && isSvg(rawData) ? "image/svg+xml" : fileType?.mime;
282
+ if (!mimeType || !mimeType.startsWith("image/")) {
267
283
  console.warn(
268
- `Warning: Content may not be a valid image. Detected MIME: ${mimeType?.mime || "unknown"}`
284
+ `Warning: Content may not be a valid image. Detected MIME: ${mimeType || "unknown"}`
269
285
  );
270
286
  }
271
287
  const base64Data = rawData.toString("base64");
272
288
  return {
273
289
  data: base64Data,
274
- mimeType: mimeType?.mime ?? "image/png",
290
+ mimeType: mimeType ?? "image/png",
275
291
  type: "image"
276
292
  };
277
293
  } catch (error) {
@@ -282,8 +298,27 @@ var imageContent = async (input) => {
282
298
  }
283
299
  }
284
300
  };
301
+ var AUDIO_CONTAINER_TYPES = /* @__PURE__ */ new Map([
302
+ ["video/3gpp", "audio/3gpp"],
303
+ ["video/3gpp2", "audio/3gpp2"],
304
+ ["video/matroska", "audio/matroska"],
305
+ ["video/mp4", "audio/mp4"],
306
+ ["video/ogg", "audio/ogg"],
307
+ ["video/webm", "audio/webm"]
308
+ ]);
309
+ var resolveAudioMimeType = (detected, declared) => {
310
+ if (detected?.startsWith("audio/")) {
311
+ return detected;
312
+ }
313
+ const declaredMime = declared?.split(";", 1)[0].trim().toLowerCase();
314
+ if (declaredMime?.startsWith("audio/")) {
315
+ return declaredMime;
316
+ }
317
+ return (detected && AUDIO_CONTAINER_TYPES.get(detected)) ?? detected;
318
+ };
285
319
  var audioContent = async (input) => {
286
320
  let rawData;
321
+ let declaredMimeType = null;
287
322
  try {
288
323
  if ("url" in input) {
289
324
  const timeoutMs = input.timeoutMs ?? MEDIA_FETCH_TIMEOUT_MS;
@@ -297,6 +332,7 @@ var audioContent = async (input) => {
297
332
  `Server responded with status: ${response.status} - ${response.statusText}`
298
333
  );
299
334
  }
335
+ declaredMimeType = response.headers.get("content-type");
300
336
  rawData = Buffer.from(await response.arrayBuffer());
301
337
  } catch (error) {
302
338
  if (error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError")) {
@@ -324,16 +360,17 @@ var audioContent = async (input) => {
324
360
  );
325
361
  }
326
362
  const { fileTypeFromBuffer } = await import("file-type");
327
- const mimeType = await fileTypeFromBuffer(rawData);
328
- if (!mimeType || !mimeType.mime.startsWith("audio/")) {
363
+ const detected = await fileTypeFromBuffer(rawData);
364
+ const mimeType = resolveAudioMimeType(detected?.mime, declaredMimeType);
365
+ if (!mimeType || !mimeType.startsWith("audio/")) {
329
366
  console.warn(
330
- `Warning: Content may not be a valid audio file. Detected MIME: ${mimeType?.mime || "unknown"}`
367
+ `Warning: Content may not be a valid audio file. Detected MIME: ${mimeType || "unknown"}`
331
368
  );
332
369
  }
333
370
  const base64Data = rawData.toString("base64");
334
371
  return {
335
372
  data: base64Data,
336
- mimeType: mimeType?.mime ?? "audio/mpeg",
373
+ mimeType: mimeType ?? "audio/mpeg",
337
374
  type: "audio"
338
375
  };
339
376
  } catch (error) {
@@ -381,55 +418,20 @@ function assertToolSchemas(tool) {
381
418
  }
382
419
  var STREAM_KEEPALIVE_LOGGER = "fastmcp-keepalive";
383
420
  var STREAM_KEEPALIVE_DEFAULT_INTERVAL_MS = 2e4;
384
- var TextContentZodSchema = z.object({
385
- /**
386
- * The text content of the message.
387
- */
388
- text: z.string(),
389
- type: z.literal("text")
421
+ var TextContentZodSchema = TextContentSchema.strict();
422
+ var ImageContentZodSchema = ImageContentSchema.extend({
423
+ data: z.string().base64()
390
424
  }).strict();
391
- var ImageContentZodSchema = z.object({
392
- /**
393
- * The base64-encoded image data.
394
- */
395
- data: z.string().base64(),
396
- /**
397
- * The MIME type of the image. Different providers may support different image types.
398
- */
399
- mimeType: z.string(),
400
- type: z.literal("image")
401
- }).strict();
402
- var AudioContentZodSchema = z.object({
403
- /**
404
- * The base64-encoded audio data.
405
- */
406
- data: z.string().base64(),
407
- mimeType: z.string(),
408
- type: z.literal("audio")
409
- }).strict();
410
- var ResourceContentZodSchema = z.object({
411
- resource: z.object({
412
- blob: z.string().optional(),
413
- mimeType: z.string().optional(),
414
- text: z.string().optional(),
415
- uri: z.string()
416
- }),
417
- type: z.literal("resource")
425
+ var AudioContentZodSchema = AudioContentSchema.extend({
426
+ data: z.string().base64()
418
427
  }).strict();
419
- var ResourceLinkZodSchema = z.object({
420
- description: z.string().optional(),
421
- mimeType: z.string().optional(),
422
- name: z.string(),
423
- title: z.string().optional(),
424
- type: z.literal("resource_link"),
425
- uri: z.string()
426
- });
428
+ var ResourceContentZodSchema = EmbeddedResourceSchema.strict();
427
429
  var ContentZodSchema = z.discriminatedUnion("type", [
428
430
  TextContentZodSchema,
429
431
  ImageContentZodSchema,
430
432
  AudioContentZodSchema,
431
433
  ResourceContentZodSchema,
432
- ResourceLinkZodSchema
434
+ ResourceLinkSchema
433
435
  ]);
434
436
  var ContentResultZodSchema = z.object({
435
437
  _meta: z.record(z.string(), z.unknown()).optional(),
@@ -575,6 +577,7 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
575
577
  * to interested clients only.
576
578
  */
577
579
  #subscriptions = /* @__PURE__ */ new Set();
580
+ #tools;
578
581
  #transportType;
579
582
  /**
580
583
  * Capabilities this session has already warned about in `#canRefresh`, so a
@@ -583,6 +586,7 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
583
586
  */
584
587
  #unnegotiatedWarnings = /* @__PURE__ */ new Set();
585
588
  #utils;
589
+ #visibleTools = [];
586
590
  constructor({
587
591
  auth,
588
592
  authRequired = false,
@@ -617,6 +621,7 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
617
621
  this.#sessionId = sessionId;
618
622
  this.#stateless = stateless;
619
623
  this.#streamKeepaliveConfig = streamKeepalive;
624
+ this.#tools = tools;
620
625
  this.#transportType = transportType;
621
626
  this.#needsEventLoopFlush = transportType === "httpStream";
622
627
  const supportsTools = hasTools ?? tools.length > 0;
@@ -650,7 +655,9 @@ var FastMCPSession = class extends FastMCPSessionEventEmitter {
650
655
  this.setupRootsHandlers();
651
656
  this.setupCompleteHandlers();
652
657
  if (supportsTools) {
653
- this.setupToolHandlers(tools);
658
+ this.setupToolHandlers(
659
+ toolsVisibleTo(tools, this.#auth, this.#authRequired)
660
+ );
654
661
  }
655
662
  if (resources.length || resourcesTemplates.length) {
656
663
  for (const resource of resources) {
@@ -771,6 +778,7 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
771
778
  if (!this.#canRefresh("tools")) {
772
779
  return;
773
780
  }
781
+ this.#tools = tools;
774
782
  this.setupToolHandlers(
775
783
  toolsVisibleTo(tools, this.#auth, this.#authRequired)
776
784
  );
@@ -795,6 +803,11 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
795
803
  */
796
804
  updateAuth(auth) {
797
805
  this.#auth = auth;
806
+ const tools = toolsVisibleTo(this.#tools, auth, this.#authRequired);
807
+ if (tools.length !== this.#visibleTools.length || tools.some((tool, index) => tool !== this.#visibleTools[index])) {
808
+ this.setupToolHandlers(tools);
809
+ this.triggerListChangedNotification("notifications/tools/list_changed");
810
+ }
798
811
  }
799
812
  waitForReady() {
800
813
  if (this.isReady) {
@@ -872,31 +885,31 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
872
885
  * are tied to a tool call's progress token / streaming notification,
873
886
  * which resource and prompt reads do not have.
874
887
  */
875
- #createLoadContext(meta) {
888
+ #createLoadContext(meta, sendNotification) {
876
889
  return {
877
890
  client: {
878
891
  version: this.#server.getClientVersion()
879
892
  },
880
893
  elicit: (params, options) => this.#server.elicitInput(params, options),
881
- log: this.#createLog(),
894
+ log: this.#createLog(sendNotification),
882
895
  requestId: typeof meta?.requestId === "string" ? meta.requestId : void 0,
883
896
  session: this.#auth,
884
897
  sessionId: this.sessionId
885
898
  };
886
899
  }
887
- #createLog() {
900
+ #createLog(sendNotification) {
888
901
  return {
889
902
  debug: (message, context) => {
890
- this.#sendLog("debug", message, context);
903
+ this.#sendLog(sendNotification, "debug", message, context);
891
904
  },
892
905
  error: (message, context) => {
893
- this.#sendLog("error", message, context);
906
+ this.#sendLog(sendNotification, "error", message, context);
894
907
  },
895
908
  info: (message, context) => {
896
- this.#sendLog("info", message, context);
909
+ this.#sendLog(sendNotification, "info", message, context);
897
910
  },
898
911
  warn: (message, context) => {
899
- this.#sendLog("warning", message, context);
912
+ this.#sendLog(sendNotification, "warning", message, context);
900
913
  }
901
914
  };
902
915
  }
@@ -962,22 +975,23 @@ ${e instanceof Error ? e.stack : JSON.stringify(e)}`
962
975
  }
963
976
  }
964
977
  /**
965
- * `sendLoggingMessage` rejects once the client has hung up, and these four
978
+ * Sending rejects once the client has hung up, and these four
966
979
  * callers are synchronous, so the rejection has nowhere to go. Swallow it the
967
980
  * way every other notification sender in this class does, or a client that
968
981
  * disconnects mid-tool takes the server process down with it.
969
982
  */
970
- #sendLog(level, message, context) {
983
+ #sendLog(sendNotification, level, message, context) {
971
984
  if (this.#loggingLevel && LOGGING_LEVEL_SEVERITY[level] < LOGGING_LEVEL_SEVERITY[this.#loggingLevel]) {
972
985
  return;
973
986
  }
974
- this.#server.sendLoggingMessage({
987
+ const params = {
975
988
  data: {
976
989
  context,
977
990
  message
978
991
  },
979
992
  level
980
- }).catch((error) => {
993
+ };
994
+ sendNotification({ method: "notifications/message", params }).catch(() => this.#server.sendLoggingMessage(params)).catch((error) => {
981
995
  this.#logger.error(
982
996
  `[FastMCP error] failed to send ${level} log notification.
983
997
 
@@ -1288,54 +1302,60 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
1288
1302
  prompts: cachedPromptsList
1289
1303
  };
1290
1304
  });
1291
- this.#server.setRequestHandler(GetPromptRequestSchema, async (request) => {
1292
- const prompt = this.#prompts.get(request.params.name);
1293
- if (!prompt) {
1294
- throw new McpError(
1295
- ErrorCode.InvalidParams,
1296
- `Unknown prompt: ${request.params.name}`
1297
- );
1298
- }
1299
- const args = request.params.arguments;
1300
- for (const arg of prompt.arguments ?? []) {
1301
- if (arg.required && !(args && Object.hasOwn(args, arg.name))) {
1305
+ this.#server.setRequestHandler(
1306
+ GetPromptRequestSchema,
1307
+ async (request, extra) => {
1308
+ const prompt = this.#prompts.get(request.params.name);
1309
+ if (!prompt) {
1302
1310
  throw new McpError(
1303
1311
  ErrorCode.InvalidParams,
1304
- `Prompt '${request.params.name}' requires argument '${arg.name}': ${arg.description || "No description provided"}`
1312
+ `Unknown prompt: ${request.params.name}`
1305
1313
  );
1306
1314
  }
1315
+ const args = request.params.arguments;
1316
+ for (const arg of prompt.arguments ?? []) {
1317
+ if (arg.required && !(args && Object.hasOwn(args, arg.name))) {
1318
+ throw new McpError(
1319
+ ErrorCode.InvalidParams,
1320
+ `Prompt '${request.params.name}' requires argument '${arg.name}': ${arg.description || "No description provided"}`
1321
+ );
1322
+ }
1323
+ }
1324
+ let result;
1325
+ try {
1326
+ result = await prompt.load(
1327
+ args ?? {},
1328
+ this.#auth,
1329
+ this.#createLoadContext(
1330
+ request.params?._meta,
1331
+ extra.sendNotification
1332
+ )
1333
+ );
1334
+ } catch (error) {
1335
+ const errorMessage = error instanceof Error ? error.message : String(error);
1336
+ throw new McpError(
1337
+ ErrorCode.InternalError,
1338
+ `Failed to load prompt '${request.params.name}': ${errorMessage}`
1339
+ );
1340
+ }
1341
+ if (typeof result === "string") {
1342
+ return {
1343
+ description: prompt.description,
1344
+ messages: [
1345
+ {
1346
+ content: { text: result, type: "text" },
1347
+ role: "user"
1348
+ }
1349
+ ]
1350
+ };
1351
+ } else {
1352
+ return {
1353
+ description: prompt.description,
1354
+ messages: result.messages
1355
+ };
1356
+ }
1307
1357
  }
1308
- let result;
1309
- try {
1310
- result = await prompt.load(
1311
- args ?? {},
1312
- this.#auth,
1313
- this.#createLoadContext(request.params?._meta)
1314
- );
1315
- } catch (error) {
1316
- const errorMessage = error instanceof Error ? error.message : String(error);
1317
- throw new McpError(
1318
- ErrorCode.InternalError,
1319
- `Failed to load prompt '${request.params.name}': ${errorMessage}`
1320
- );
1321
- }
1322
- if (typeof result === "string") {
1323
- return {
1324
- description: prompt.description,
1325
- messages: [
1326
- {
1327
- content: { text: result, type: "text" },
1328
- role: "user"
1329
- }
1330
- ]
1331
- };
1332
- } else {
1333
- return {
1334
- description: prompt.description,
1335
- messages: result.messages
1336
- };
1337
- }
1338
- });
1358
+ );
1339
1359
  }
1340
1360
  setupResourceHandlers() {
1341
1361
  let cachedResourcesList = null;
@@ -1359,7 +1379,7 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
1359
1379
  });
1360
1380
  this.#server.setRequestHandler(
1361
1381
  ReadResourceRequestSchema,
1362
- async (request) => {
1382
+ async (request, extra) => {
1363
1383
  if ("uri" in request.params) {
1364
1384
  const resource = this.#resources.get(request.params.uri);
1365
1385
  if (!resource) {
@@ -1375,7 +1395,10 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
1375
1395
  const result = await resourceTemplate.load(
1376
1396
  match,
1377
1397
  this.#auth,
1378
- this.#createLoadContext(request.params?._meta)
1398
+ this.#createLoadContext(
1399
+ request.params?._meta,
1400
+ extra.sendNotification
1401
+ )
1379
1402
  );
1380
1403
  const resources = Array.isArray(result) ? result : [result];
1381
1404
  return {
@@ -1401,7 +1424,10 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
1401
1424
  try {
1402
1425
  maybeArrayResult = await resource.load(
1403
1426
  this.#auth,
1404
- this.#createLoadContext(request.params?._meta)
1427
+ this.#createLoadContext(
1428
+ request.params?._meta,
1429
+ extra.sendNotification
1430
+ )
1405
1431
  );
1406
1432
  } catch (error) {
1407
1433
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -1501,6 +1527,7 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
1501
1527
  }
1502
1528
  }
1503
1529
  setupToolHandlers(tools) {
1530
+ this.#visibleTools = tools;
1504
1531
  const toolsMap = new Map(tools.map((tool) => [tool.name, tool]));
1505
1532
  let cachedToolsList = null;
1506
1533
  this.#server.setRequestHandler(ListToolsRequestSchema, async () => {
@@ -1583,7 +1610,7 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
1583
1610
  );
1584
1611
  }
1585
1612
  };
1586
- const log = this.#createLog();
1613
+ const log = this.#createLog(extra.sendNotification);
1587
1614
  const streamContent = async (content) => {
1588
1615
  const contentArray = Array.isArray(content) ? content : [content];
1589
1616
  try {
@@ -2247,7 +2274,7 @@ var FastMCP = class extends FastMCPEventEmitter {
2247
2274
  roots: this.#options.roots,
2248
2275
  streamKeepalive: this.#options.streamKeepalive,
2249
2276
  title: this.#options.title,
2250
- tools: toolsVisibleTo(this.#tools, auth, authRequired),
2277
+ tools: this.#tools,
2251
2278
  transportType: "stdio",
2252
2279
  utils: this.#options.utils,
2253
2280
  version: this.#options.version,
@@ -2458,7 +2485,7 @@ var FastMCP = class extends FastMCPEventEmitter {
2458
2485
  stateless,
2459
2486
  streamKeepalive: this.#options.streamKeepalive,
2460
2487
  title: this.#options.title,
2461
- tools: toolsVisibleTo(this.#tools, auth, authRequired),
2488
+ tools: this.#tools,
2462
2489
  transportType: "httpStream",
2463
2490
  utils: this.#options.utils,
2464
2491
  version: this.#options.version,
@@ -3186,4 +3213,4 @@ export {
3186
3213
  FastMCPSession,
3187
3214
  FastMCP
3188
3215
  };
3189
- //# sourceMappingURL=chunk-G6TNGO7A.js.map
3216
+ //# sourceMappingURL=chunk-5QKBH7JC.js.map