fastmcp 4.23.7 → 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.
Files changed (39) hide show
  1. package/dist/FastMCP.cjs +4 -4
  2. package/dist/FastMCP.d.cts +3 -2
  3. package/dist/FastMCP.d.ts +3 -2
  4. package/dist/FastMCP.js +3 -3
  5. package/dist/auth/index.cjs +2 -2
  6. package/dist/auth/index.js +1 -1
  7. package/dist/bin/fastmcp.cjs +7 -1
  8. package/dist/bin/fastmcp.cjs.map +1 -1
  9. package/dist/bin/fastmcp.js +7 -1
  10. package/dist/bin/fastmcp.js.map +1 -1
  11. package/dist/{chunk-C7TBI7A5.cjs → chunk-4OWHPJEL.cjs} +16 -5
  12. package/dist/chunk-4OWHPJEL.cjs.map +1 -0
  13. package/dist/{chunk-MPFPOZ2H.cjs → chunk-6TDYSPLC.cjs} +12 -4
  14. package/dist/chunk-6TDYSPLC.cjs.map +1 -0
  15. package/dist/{chunk-7WNI6AGD.js → chunk-75ZLE5AS.js} +153 -59
  16. package/dist/chunk-75ZLE5AS.js.map +1 -0
  17. package/dist/{chunk-CA2EPNZF.js → chunk-G44SPVP7.js} +16 -5
  18. package/dist/chunk-G44SPVP7.js.map +1 -0
  19. package/dist/{chunk-YZ5QEHBX.js → chunk-OKKFL2M4.js} +11 -3
  20. package/dist/chunk-OKKFL2M4.js.map +1 -0
  21. package/dist/{chunk-TBMRFQ6M.cjs → chunk-PV5VLW7R.cjs} +208 -114
  22. package/dist/chunk-PV5VLW7R.cjs.map +1 -0
  23. package/dist/edge/index.cjs +13 -5
  24. package/dist/edge/index.cjs.map +1 -1
  25. package/dist/edge/index.js +11 -3
  26. package/dist/edge/index.js.map +1 -1
  27. package/dist/examples/custom-routes.cjs +4 -4
  28. package/dist/examples/custom-routes.js +3 -3
  29. package/dist/openapi/index.cjs +17 -12
  30. package/dist/openapi/index.cjs.map +1 -1
  31. package/dist/openapi/index.js +12 -7
  32. package/dist/openapi/index.js.map +1 -1
  33. package/package.json +1 -1
  34. package/dist/chunk-7WNI6AGD.js.map +0 -1
  35. package/dist/chunk-C7TBI7A5.cjs.map +0 -1
  36. package/dist/chunk-CA2EPNZF.js.map +0 -1
  37. package/dist/chunk-MPFPOZ2H.cjs.map +0 -1
  38. package/dist/chunk-TBMRFQ6M.cjs.map +0 -1
  39. package/dist/chunk-YZ5QEHBX.js.map +0 -1
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  cancelResponseBody
3
- } from "./chunk-CA2EPNZF.js";
3
+ } from "./chunk-G44SPVP7.js";
4
4
  import {
5
+ objectRootSchema,
5
6
  strictInputSchema,
6
7
  zodInputSchema
7
- } from "./chunk-YZ5QEHBX.js";
8
+ } from "./chunk-OKKFL2M4.js";
8
9
 
9
10
  // src/FastMCP.ts
10
11
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -186,9 +187,18 @@ function jsonSchemaAdapter(schema) {
186
187
  };
187
188
  }
188
189
  async function compileSchema(schema) {
190
+ const dialect = schema.$schema?.replace(/^http:\/\//, "https://").replace(/#$/, "");
191
+ const isDraft2020 = dialect === "https://json-schema.org/draft/2020-12/schema";
192
+ const isDraft2019 = dialect === "https://json-schema.org/draft/2019-09/schema";
189
193
  let ajvModule;
190
194
  try {
191
- ajvModule = 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
+ }
192
202
  } catch {
193
203
  throw new Error(
194
204
  'The "ajv" package is required to validate JSON Schema tool parameters. Install it with: npm install ajv'
@@ -204,7 +214,9 @@ async function compileSchema(schema) {
204
214
  addFormats(ajv);
205
215
  } catch {
206
216
  }
207
- return ajv.compile(schema);
217
+ return ajv.compile(
218
+ isDraft2020 || isDraft2019 ? { ...schema, $schema: dialect } : schema
219
+ );
208
220
  }
209
221
  function toIssue(error) {
210
222
  const path = error.instancePath.split("/").slice(1).map((segment) => segment.replaceAll("~1", "/").replaceAll("~0", "~")).map((segment) => {
@@ -487,6 +499,28 @@ var ServerState = /* @__PURE__ */ ((ServerState2) => {
487
499
  })(ServerState || {});
488
500
  var FastMCPSessionEventEmitter = class extends FastMCPSessionEventEmitterBase {
489
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
+ };
490
524
  var FastMCPSession = class extends FastMCPSessionEventEmitter {
491
525
  get clientCapabilities() {
492
526
  return this.#clientCapabilities ?? null;
@@ -933,7 +967,11 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
933
967
  }
934
968
  #formatSchemaIssues(issues) {
935
969
  return this.#utils?.formatInvalidParamsErrorMessage ? this.#utils.formatInvalidParamsErrorMessage(issues) : issues.map((issue) => {
936
- 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";
937
975
  return `${path}: ${issue.message}`;
938
976
  }).join(", ");
939
977
  }
@@ -1103,7 +1141,7 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
1103
1141
  */
1104
1142
  #startStreamKeepalive(extra, toolName) {
1105
1143
  const config = this.#streamKeepaliveConfig;
1106
- if (!config?.enabled) {
1144
+ if (!config?.enabled || extra.signal?.aborted) {
1107
1145
  return () => {
1108
1146
  };
1109
1147
  }
@@ -1124,7 +1162,10 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
1124
1162
  });
1125
1163
  }, intervalMs);
1126
1164
  timer.unref?.();
1127
- const stop = () => clearInterval(timer);
1165
+ const stop = () => {
1166
+ clearInterval(timer);
1167
+ extra.signal?.removeEventListener("abort", stop);
1168
+ };
1128
1169
  extra.signal?.addEventListener("abort", stop, { once: true });
1129
1170
  return stop;
1130
1171
  }
@@ -1571,8 +1612,8 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
1571
1612
  },
1572
1613
  name: tool.name,
1573
1614
  ...tool.outputSchema && {
1574
- outputSchema: await toJsonSchema(
1575
- tool.outputSchema
1615
+ outputSchema: objectRootSchema(
1616
+ await toJsonSchema(tool.outputSchema)
1576
1617
  )
1577
1618
  },
1578
1619
  // Pass through _meta for MCP ext-apps UI support (issue #229)
@@ -1610,13 +1651,33 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
1610
1651
  }
1611
1652
  const progressToken = request.params?._meta?.progressToken;
1612
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
+ };
1613
1674
  try {
1614
1675
  const reportProgress = async (progress) => {
1615
1676
  if (progressToken === void 0) {
1616
1677
  return;
1617
1678
  }
1618
1679
  try {
1619
- await extra.sendNotification({
1680
+ await sendNotification({
1620
1681
  method: "notifications/progress",
1621
1682
  params: {
1622
1683
  ...progress,
@@ -1633,11 +1694,11 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
1633
1694
  );
1634
1695
  }
1635
1696
  };
1636
- const log = this.#createLog(extra.sendNotification);
1697
+ const log = this.#createLog(sendNotification);
1637
1698
  const streamContent = async (content) => {
1638
1699
  const contentArray = Array.isArray(content) ? content : [content];
1639
1700
  try {
1640
- await extra.sendNotification({
1701
+ await sendNotification({
1641
1702
  method: "notifications/tool/streamContent",
1642
1703
  params: {
1643
1704
  content: contentArray,
@@ -1660,14 +1721,6 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
1660
1721
  toolName: request.params.name
1661
1722
  });
1662
1723
  }
1663
- const timeoutAbort = new AbortController();
1664
- const signal = AbortSignal.any([
1665
- timeoutAbort.signal,
1666
- this.#abortController.signal,
1667
- // Only ever aborted for an explicit `notifications/cancelled`; the
1668
- // session signal above is what covers a client that simply left.
1669
- ...extra.signal ? [extra.signal] : []
1670
- ]);
1671
1724
  const executeToolPromise = Promise.resolve(
1672
1725
  tool.execute(args, {
1673
1726
  client: {
@@ -1684,25 +1737,37 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
1684
1737
  })
1685
1738
  );
1686
1739
  const stopStreamKeepalive = this.#startStreamKeepalive(
1687
- extra,
1740
+ { sendNotification, signal },
1688
1741
  request.params.name
1689
1742
  );
1690
- const maybeStringResult = await (tool.timeoutMs ? Promise.race([
1691
- executeToolPromise,
1692
- new Promise((_, reject) => {
1693
- const timeoutId = setTimeout(() => {
1694
- 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(
1695
1758
  `Tool '${request.params.name}' timed out after ${tool.timeoutMs}ms. Consider increasing timeoutMs or optimizing the tool implementation.`
1696
- );
1697
- timeoutAbort.abort(timedOut);
1698
- reject(timedOut);
1699
- }, tool.timeoutMs);
1700
- executeToolPromise.then(
1701
- () => clearTimeout(timeoutId),
1702
- () => clearTimeout(timeoutId)
1759
+ )
1703
1760
  );
1704
- })
1705
- ]) : 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
+ });
1706
1771
  await delay(1);
1707
1772
  if (maybeStringResult === void 0 || maybeStringResult === null) {
1708
1773
  result = ContentResultZodSchema.parse({
@@ -1743,12 +1808,29 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
1743
1808
  } else {
1744
1809
  result = ContentResultZodSchema.parse(maybeStringResult);
1745
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
+ }
1746
1816
  } catch (error) {
1747
1817
  if (error instanceof UserError) {
1818
+ let structuredContent = error.extras;
1819
+ if (structuredContent && tool.outputSchema) {
1820
+ try {
1821
+ structuredContent = await this.#validateStructuredContent(
1822
+ tool,
1823
+ structuredContent,
1824
+ request.params.name
1825
+ );
1826
+ } catch {
1827
+ structuredContent = void 0;
1828
+ }
1829
+ }
1748
1830
  return {
1749
1831
  content: [{ text: error.message, type: "text" }],
1750
1832
  isError: true,
1751
- ...error.extras ? { structuredContent: error.extras } : {}
1833
+ ...structuredContent ? { structuredContent } : {}
1752
1834
  };
1753
1835
  }
1754
1836
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -1964,7 +2046,9 @@ var FastMCP = class extends FastMCPEventEmitter {
1964
2046
  * Adds a resource to the server.
1965
2047
  */
1966
2048
  addResource(resource) {
1967
- this.#resources = this.#resources.filter((r) => r.name !== resource.name);
2049
+ this.#resources = this.#resources.filter(
2050
+ (r) => r.name !== resource.name && r.uri !== resource.uri
2051
+ );
1968
2052
  this.#resources.push(resource);
1969
2053
  if (this.#serverState === "running" /* Running */) {
1970
2054
  this.#resourcesListChanged(this.#resources);
@@ -1974,13 +2058,12 @@ var FastMCP = class extends FastMCPEventEmitter {
1974
2058
  * Adds resources to the server.
1975
2059
  */
1976
2060
  addResources(resources) {
1977
- const newResourceNames = new Set(
1978
- resources.map((resource) => resource.name)
1979
- );
1980
- this.#resources = this.#resources.filter(
1981
- (r) => !newResourceNames.has(r.name)
1982
- );
1983
- this.#resources.push(...resources);
2061
+ for (const resource of resources) {
2062
+ this.#resources = this.#resources.filter(
2063
+ (r) => r.name !== resource.name && r.uri !== resource.uri
2064
+ );
2065
+ this.#resources.push(resource);
2066
+ }
1984
2067
  if (this.#serverState === "running" /* Running */) {
1985
2068
  this.#resourcesListChanged(this.#resources);
1986
2069
  }
@@ -2001,13 +2084,12 @@ var FastMCP = class extends FastMCPEventEmitter {
2001
2084
  * Adds resource templates to the server.
2002
2085
  */
2003
2086
  addResourceTemplates(resources) {
2004
- const newResourceTemplateNames = new Set(
2005
- resources.map((resource) => resource.name)
2006
- );
2007
- this.#resourcesTemplates = this.#resourcesTemplates.filter(
2008
- (t) => !newResourceTemplateNames.has(t.name)
2009
- );
2010
- 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
+ }
2011
2093
  if (this.#serverState === "running" /* Running */) {
2012
2094
  this.#resourceTemplatesListChanged(this.#resourcesTemplates);
2013
2095
  }
@@ -2028,9 +2110,12 @@ var FastMCP = class extends FastMCPEventEmitter {
2028
2110
  */
2029
2111
  addTools(tools) {
2030
2112
  tools.forEach(assertToolSchemas);
2031
- const newToolNames = new Set(tools.map((tool) => tool.name));
2032
- this.#tools = this.#tools.filter((t) => !newToolNames.has(t.name));
2033
- this.#tools.push(...tools);
2113
+ const toolsByName = new Map(this.#tools.map((tool) => [tool.name, tool]));
2114
+ for (const tool of tools) {
2115
+ toolsByName.delete(tool.name);
2116
+ toolsByName.set(tool.name, tool);
2117
+ }
2118
+ this.#tools = Array.from(toolsByName.values());
2034
2119
  if (this.#serverState === "running" /* Running */) {
2035
2120
  this.#toolsListChanged(this.#tools);
2036
2121
  }
@@ -2261,6 +2346,13 @@ var FastMCP = class extends FastMCPEventEmitter {
2261
2346
  async start(options) {
2262
2347
  const config = this.#parseRuntimeConfig(options);
2263
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
+ };
2264
2356
  const transport = new StdioServerTransport();
2265
2357
  let auth;
2266
2358
  const authRequired = this.#authenticate !== void 0;
@@ -2287,7 +2379,7 @@ var FastMCP = class extends FastMCPEventEmitter {
2287
2379
  hasTools: this.#tools.length > 0,
2288
2380
  icons: this.#options.icons,
2289
2381
  instructions: this.#options.instructions,
2290
- logger: this.#logger,
2382
+ logger,
2291
2383
  name: this.#options.name,
2292
2384
  onToolCall: this.#options.onToolCall,
2293
2385
  ping: this.#options.ping,
@@ -2332,6 +2424,9 @@ var FastMCP = class extends FastMCPEventEmitter {
2332
2424
  this.#serverState = "running" /* Running */;
2333
2425
  }
2334
2426
  } else if (config.transportType === "httpStream") {
2427
+ if (this.#httpStreamServer) {
2428
+ throw new Error("HTTP server is already running");
2429
+ }
2335
2430
  const httpConfig = config.httpStream;
2336
2431
  const protocol = httpConfig.sslCert || httpConfig.sslKey ? "https" : "http";
2337
2432
  const streamEndpoint = joinPaths(
@@ -2461,6 +2556,7 @@ var FastMCP = class extends FastMCPEventEmitter {
2461
2556
  );
2462
2557
  if (this.#httpStreamServer) {
2463
2558
  await this.#httpStreamServer.close();
2559
+ this.#httpStreamServer = null;
2464
2560
  }
2465
2561
  this.#serverState = "stopped" /* Stopped */;
2466
2562
  }
@@ -3067,9 +3163,7 @@ var FastMCP = class extends FastMCPEventEmitter {
3067
3163
  const hasBody = method !== "GET" && method !== "HEAD";
3068
3164
  if (hasBody) {
3069
3165
  return new Request(url.toString(), {
3070
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
3071
- body: req,
3072
- // Node.js IncomingMessage is readable stream
3166
+ body: lazyRequestBody(req),
3073
3167
  duplex: "half",
3074
3168
  // Required for streaming bodies
3075
3169
  headers,
@@ -3247,4 +3341,4 @@ export {
3247
3341
  FastMCPSession,
3248
3342
  FastMCP
3249
3343
  };
3250
- //# sourceMappingURL=chunk-7WNI6AGD.js.map
3344
+ //# sourceMappingURL=chunk-75ZLE5AS.js.map