fastmcp 4.23.8 → 4.23.9

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.
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  cancelResponseBody
3
- } from "./chunk-UY253ACS.js";
3
+ } from "./chunk-G44SPVP7.js";
4
4
  import {
5
5
  objectRootSchema,
6
6
  strictInputSchema,
7
7
  zodInputSchema
8
- } from "./chunk-F76U45MT.js";
8
+ } from "./chunk-OKKFL2M4.js";
9
9
 
10
10
  // src/FastMCP.ts
11
11
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -189,9 +189,16 @@ function jsonSchemaAdapter(schema) {
189
189
  async function compileSchema(schema) {
190
190
  const dialect = schema.$schema?.replace(/^http:\/\//, "https://").replace(/#$/, "");
191
191
  const isDraft2020 = dialect === "https://json-schema.org/draft/2020-12/schema";
192
+ const isDraft2019 = dialect === "https://json-schema.org/draft/2019-09/schema";
192
193
  let ajvModule;
193
194
  try {
194
- ajvModule = isDraft2020 ? await import("ajv/dist/2020.js") : await import("ajv");
195
+ if (isDraft2020) {
196
+ ajvModule = await import("ajv/dist/2020.js");
197
+ } else if (isDraft2019) {
198
+ ajvModule = await import("ajv/dist/2019.js");
199
+ } else {
200
+ ajvModule = await import("ajv");
201
+ }
195
202
  } catch {
196
203
  throw new Error(
197
204
  'The "ajv" package is required to validate JSON Schema tool parameters. Install it with: npm install ajv'
@@ -207,7 +214,9 @@ async function compileSchema(schema) {
207
214
  addFormats(ajv);
208
215
  } catch {
209
216
  }
210
- return ajv.compile(isDraft2020 ? { ...schema, $schema: dialect } : schema);
217
+ return ajv.compile(
218
+ isDraft2020 || isDraft2019 ? { ...schema, $schema: dialect } : schema
219
+ );
211
220
  }
212
221
  function toIssue(error) {
213
222
  const path = error.instancePath.split("/").slice(1).map((segment) => segment.replaceAll("~1", "/").replaceAll("~0", "~")).map((segment) => {
@@ -490,6 +499,28 @@ var ServerState = /* @__PURE__ */ ((ServerState2) => {
490
499
  })(ServerState || {});
491
500
  var FastMCPSessionEventEmitter = class extends FastMCPSessionEventEmitterBase {
492
501
  };
502
+ var lazyRequestBody = (req) => {
503
+ let iterator;
504
+ return new ReadableStream(
505
+ {
506
+ async cancel(reason) {
507
+ await iterator?.return?.(reason);
508
+ },
509
+ async pull(controller) {
510
+ iterator ??= req[Symbol.asyncIterator]();
511
+ const { done, value } = await iterator.next();
512
+ if (done) {
513
+ controller.close();
514
+ return;
515
+ }
516
+ controller.enqueue(
517
+ typeof value === "string" ? Buffer.from(value) : new Uint8Array(value)
518
+ );
519
+ }
520
+ },
521
+ { highWaterMark: 0 }
522
+ );
523
+ };
493
524
  var FastMCPSession = class extends FastMCPSessionEventEmitter {
494
525
  get clientCapabilities() {
495
526
  return this.#clientCapabilities ?? null;
@@ -936,7 +967,11 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
936
967
  }
937
968
  #formatSchemaIssues(issues) {
938
969
  return this.#utils?.formatInvalidParamsErrorMessage ? this.#utils.formatInvalidParamsErrorMessage(issues) : issues.map((issue) => {
939
- const path = issue.path?.join(".") || "root";
970
+ const path = issue.path?.map(
971
+ (segment) => String(
972
+ typeof segment === "object" && segment !== null ? segment.key : segment
973
+ )
974
+ ).join(".") || "root";
940
975
  return `${path}: ${issue.message}`;
941
976
  }).join(", ");
942
977
  }
@@ -1106,7 +1141,7 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
1106
1141
  */
1107
1142
  #startStreamKeepalive(extra, toolName) {
1108
1143
  const config = this.#streamKeepaliveConfig;
1109
- if (!config?.enabled) {
1144
+ if (!config?.enabled || extra.signal?.aborted) {
1110
1145
  return () => {
1111
1146
  };
1112
1147
  }
@@ -1127,7 +1162,10 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
1127
1162
  });
1128
1163
  }, intervalMs);
1129
1164
  timer.unref?.();
1130
- const stop = () => clearInterval(timer);
1165
+ const stop = () => {
1166
+ clearInterval(timer);
1167
+ extra.signal?.removeEventListener("abort", stop);
1168
+ };
1131
1169
  extra.signal?.addEventListener("abort", stop, { once: true });
1132
1170
  return stop;
1133
1171
  }
@@ -1613,13 +1651,33 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
1613
1651
  }
1614
1652
  const progressToken = request.params?._meta?.progressToken;
1615
1653
  let result;
1654
+ const timeoutAbort = new AbortController();
1655
+ const signal = AbortSignal.any([
1656
+ timeoutAbort.signal,
1657
+ this.#abortController.signal,
1658
+ // Only ever aborted for an explicit `notifications/cancelled`; the
1659
+ // session signal above is what covers a client that simply left.
1660
+ ...extra.signal ? [extra.signal] : []
1661
+ ]);
1662
+ const sendNotification = async (notification) => {
1663
+ if (signal.aborted) {
1664
+ return;
1665
+ }
1666
+ try {
1667
+ await extra.sendNotification(notification);
1668
+ } catch (error) {
1669
+ if (!signal.aborted) {
1670
+ throw error;
1671
+ }
1672
+ }
1673
+ };
1616
1674
  try {
1617
1675
  const reportProgress = async (progress) => {
1618
1676
  if (progressToken === void 0) {
1619
1677
  return;
1620
1678
  }
1621
1679
  try {
1622
- await extra.sendNotification({
1680
+ await sendNotification({
1623
1681
  method: "notifications/progress",
1624
1682
  params: {
1625
1683
  ...progress,
@@ -1636,11 +1694,11 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
1636
1694
  );
1637
1695
  }
1638
1696
  };
1639
- const log = this.#createLog(extra.sendNotification);
1697
+ const log = this.#createLog(sendNotification);
1640
1698
  const streamContent = async (content) => {
1641
1699
  const contentArray = Array.isArray(content) ? content : [content];
1642
1700
  try {
1643
- await extra.sendNotification({
1701
+ await sendNotification({
1644
1702
  method: "notifications/tool/streamContent",
1645
1703
  params: {
1646
1704
  content: contentArray,
@@ -1663,14 +1721,6 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
1663
1721
  toolName: request.params.name
1664
1722
  });
1665
1723
  }
1666
- const timeoutAbort = new AbortController();
1667
- const signal = AbortSignal.any([
1668
- timeoutAbort.signal,
1669
- this.#abortController.signal,
1670
- // Only ever aborted for an explicit `notifications/cancelled`; the
1671
- // session signal above is what covers a client that simply left.
1672
- ...extra.signal ? [extra.signal] : []
1673
- ]);
1674
1724
  const executeToolPromise = Promise.resolve(
1675
1725
  tool.execute(args, {
1676
1726
  client: {
@@ -1687,25 +1737,37 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
1687
1737
  })
1688
1738
  );
1689
1739
  const stopStreamKeepalive = this.#startStreamKeepalive(
1690
- extra,
1740
+ { sendNotification, signal },
1691
1741
  request.params.name
1692
1742
  );
1693
- const maybeStringResult = await (tool.timeoutMs ? Promise.race([
1694
- executeToolPromise,
1695
- new Promise((_, reject) => {
1696
- const timeoutId = setTimeout(() => {
1697
- const timedOut = new UserError(
1743
+ let timeoutId;
1744
+ let stopWaitingForAbort = () => {
1745
+ };
1746
+ const aborted = new Promise((_, reject) => {
1747
+ const onAbort = () => reject(signal.reason);
1748
+ stopWaitingForAbort = () => signal.removeEventListener("abort", onAbort);
1749
+ signal.addEventListener("abort", onAbort, { once: true });
1750
+ if (signal.aborted) {
1751
+ onAbort();
1752
+ }
1753
+ });
1754
+ if (tool.timeoutMs && !signal.aborted) {
1755
+ timeoutId = setTimeout(() => {
1756
+ timeoutAbort.abort(
1757
+ new UserError(
1698
1758
  `Tool '${request.params.name}' timed out after ${tool.timeoutMs}ms. Consider increasing timeoutMs or optimizing the tool implementation.`
1699
- );
1700
- timeoutAbort.abort(timedOut);
1701
- reject(timedOut);
1702
- }, tool.timeoutMs);
1703
- executeToolPromise.then(
1704
- () => clearTimeout(timeoutId),
1705
- () => clearTimeout(timeoutId)
1759
+ )
1706
1760
  );
1707
- })
1708
- ]) : executeToolPromise).finally(stopStreamKeepalive);
1761
+ }, tool.timeoutMs);
1762
+ }
1763
+ const maybeStringResult = await Promise.race([
1764
+ executeToolPromise,
1765
+ aborted
1766
+ ]).finally(() => {
1767
+ clearTimeout(timeoutId);
1768
+ stopWaitingForAbort();
1769
+ stopStreamKeepalive();
1770
+ });
1709
1771
  await delay(1);
1710
1772
  if (maybeStringResult === void 0 || maybeStringResult === null) {
1711
1773
  result = ContentResultZodSchema.parse({
@@ -1746,6 +1808,11 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
1746
1808
  } else {
1747
1809
  result = ContentResultZodSchema.parse(maybeStringResult);
1748
1810
  }
1811
+ if (tool.outputSchema && result.structuredContent === void 0 && !result.isError) {
1812
+ throw new UserError(
1813
+ `Tool '${request.params.name}' returned no structured content. Please return a value that matches the tool's outputSchema.`
1814
+ );
1815
+ }
1749
1816
  } catch (error) {
1750
1817
  if (error instanceof UserError) {
1751
1818
  let structuredContent = error.extras;
@@ -2017,13 +2084,12 @@ var FastMCP = class extends FastMCPEventEmitter {
2017
2084
  * Adds resource templates to the server.
2018
2085
  */
2019
2086
  addResourceTemplates(resources) {
2020
- const newResourceTemplateNames = new Set(
2021
- resources.map((resource) => resource.name)
2022
- );
2023
- this.#resourcesTemplates = this.#resourcesTemplates.filter(
2024
- (t) => !newResourceTemplateNames.has(t.name)
2025
- );
2026
- this.#resourcesTemplates.push(...resources);
2087
+ for (const resource of resources) {
2088
+ this.#resourcesTemplates = this.#resourcesTemplates.filter(
2089
+ (template) => template.name !== resource.name
2090
+ );
2091
+ this.#resourcesTemplates.push(resource);
2092
+ }
2027
2093
  if (this.#serverState === "running" /* Running */) {
2028
2094
  this.#resourceTemplatesListChanged(this.#resourcesTemplates);
2029
2095
  }
@@ -2280,6 +2346,13 @@ var FastMCP = class extends FastMCPEventEmitter {
2280
2346
  async start(options) {
2281
2347
  const config = this.#parseRuntimeConfig(options);
2282
2348
  if (config.transportType === "stdio") {
2349
+ const logger = this.#options.logger ?? {
2350
+ debug: (...args) => console.error(...args),
2351
+ error: (...args) => console.error(...args),
2352
+ info: (...args) => console.error(...args),
2353
+ log: (...args) => console.error(...args),
2354
+ warn: (...args) => console.warn(...args)
2355
+ };
2283
2356
  const transport = new StdioServerTransport();
2284
2357
  let auth;
2285
2358
  const authRequired = this.#authenticate !== void 0;
@@ -2306,7 +2379,7 @@ var FastMCP = class extends FastMCPEventEmitter {
2306
2379
  hasTools: this.#tools.length > 0,
2307
2380
  icons: this.#options.icons,
2308
2381
  instructions: this.#options.instructions,
2309
- logger: this.#logger,
2382
+ logger,
2310
2383
  name: this.#options.name,
2311
2384
  onToolCall: this.#options.onToolCall,
2312
2385
  ping: this.#options.ping,
@@ -3090,9 +3163,7 @@ var FastMCP = class extends FastMCPEventEmitter {
3090
3163
  const hasBody = method !== "GET" && method !== "HEAD";
3091
3164
  if (hasBody) {
3092
3165
  return new Request(url.toString(), {
3093
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
3094
- body: req,
3095
- // Node.js IncomingMessage is readable stream
3166
+ body: lazyRequestBody(req),
3096
3167
  duplex: "half",
3097
3168
  // Required for streaming bodies
3098
3169
  headers,
@@ -3270,4 +3341,4 @@ export {
3270
3341
  FastMCPSession,
3271
3342
  FastMCP
3272
3343
  };
3273
- //# sourceMappingURL=chunk-PLUAEIWE.js.map
3344
+ //# sourceMappingURL=chunk-75ZLE5AS.js.map