fastmcp 4.3.2 → 4.5.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.
@@ -599,6 +599,67 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
599
599
  });
600
600
  });
601
601
  }
602
+ /**
603
+ * Builds the context object passed as the third argument to
604
+ * `resource.load` / `resourceTemplate.load` / `prompt.load`.
605
+ *
606
+ * This mirrors the `client`, `log`, `requestId`, `session`, and
607
+ * `sessionId` fields available to `tool.execute` via {@link Context}.
608
+ * `reportProgress` and `streamContent` are intentionally omitted: they
609
+ * are tied to a tool call's progress token / streaming notification,
610
+ * which resource and prompt reads do not have.
611
+ */
612
+ #createLoadContext(meta) {
613
+ return {
614
+ client: {
615
+ version: this.#server.getClientVersion()
616
+ },
617
+ log: this.#createLog(),
618
+ requestId: typeof meta?.requestId === "string" ? meta.requestId : void 0,
619
+ session: this.#auth,
620
+ sessionId: this.#sessionId
621
+ };
622
+ }
623
+ #createLog() {
624
+ return {
625
+ debug: (message, context) => {
626
+ this.#server.sendLoggingMessage({
627
+ data: {
628
+ context,
629
+ message
630
+ },
631
+ level: "debug"
632
+ });
633
+ },
634
+ error: (message, context) => {
635
+ this.#server.sendLoggingMessage({
636
+ data: {
637
+ context,
638
+ message
639
+ },
640
+ level: "error"
641
+ });
642
+ },
643
+ info: (message, context) => {
644
+ this.#server.sendLoggingMessage({
645
+ data: {
646
+ context,
647
+ message
648
+ },
649
+ level: "info"
650
+ });
651
+ },
652
+ warn: (message, context) => {
653
+ this.#server.sendLoggingMessage({
654
+ data: {
655
+ context,
656
+ message
657
+ },
658
+ level: "warning"
659
+ });
660
+ }
661
+ };
662
+ }
602
663
  #formatSchemaIssues(issues) {
603
664
  return this.#utils?.formatInvalidParamsErrorMessage ? this.#utils.formatInvalidParamsErrorMessage(issues) : issues.map((issue) => {
604
665
  const path = issue.path?.join(".") || "root";
@@ -812,7 +873,8 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
812
873
  try {
813
874
  result = await prompt.load(
814
875
  args,
815
- this.#auth
876
+ this.#auth,
877
+ this.#createLoadContext(request.params?._meta)
816
878
  );
817
879
  } catch (error) {
818
880
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -874,7 +936,11 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
874
936
  continue;
875
937
  }
876
938
  const uri = uriTemplate.fill(match);
877
- const result = await resourceTemplate.load(match, this.#auth);
939
+ const result = await resourceTemplate.load(
940
+ match,
941
+ this.#auth,
942
+ this.#createLoadContext(request.params?._meta)
943
+ );
878
944
  const resources = Array.isArray(result) ? result : [result];
879
945
  return {
880
946
  contents: resources.map((resource2) => ({
@@ -896,7 +962,10 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
896
962
  }
897
963
  let maybeArrayResult;
898
964
  try {
899
- maybeArrayResult = await resource.load(this.#auth);
965
+ maybeArrayResult = await resource.load(
966
+ this.#auth,
967
+ this.#createLoadContext(request.params?._meta)
968
+ );
900
969
  } catch (error) {
901
970
  const errorMessage = error instanceof Error ? error.message : String(error);
902
971
  throw new McpError(
@@ -1065,44 +1134,7 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
1065
1134
  );
1066
1135
  }
1067
1136
  };
1068
- const log = {
1069
- debug: (message, context) => {
1070
- this.#server.sendLoggingMessage({
1071
- data: {
1072
- context,
1073
- message
1074
- },
1075
- level: "debug"
1076
- });
1077
- },
1078
- error: (message, context) => {
1079
- this.#server.sendLoggingMessage({
1080
- data: {
1081
- context,
1082
- message
1083
- },
1084
- level: "error"
1085
- });
1086
- },
1087
- info: (message, context) => {
1088
- this.#server.sendLoggingMessage({
1089
- data: {
1090
- context,
1091
- message
1092
- },
1093
- level: "info"
1094
- });
1095
- },
1096
- warn: (message, context) => {
1097
- this.#server.sendLoggingMessage({
1098
- data: {
1099
- context,
1100
- message
1101
- },
1102
- level: "warning"
1103
- });
1104
- }
1105
- };
1137
+ const log = this.#createLog();
1106
1138
  const streamContent = async (content) => {
1107
1139
  const contentArray = Array.isArray(content) ? content : [content];
1108
1140
  try {
@@ -1230,6 +1262,20 @@ function convertObjectToSnakeCase(obj) {
1230
1262
  }
1231
1263
  return result;
1232
1264
  }
1265
+ function joinPaths(basePath, path) {
1266
+ return `${basePath}${normalizePath(path)}`;
1267
+ }
1268
+ function normalizeBasePath(path) {
1269
+ if (!path || path === "/") {
1270
+ return "";
1271
+ }
1272
+ const withLeadingSlash = path.startsWith("/") ? path : `/${path}`;
1273
+ const withoutTrailingSlash = withLeadingSlash.replace(/\/+$/, "");
1274
+ return withoutTrailingSlash ? withoutTrailingSlash : "";
1275
+ }
1276
+ function normalizePath(path) {
1277
+ return path.startsWith("/") ? path : `/${path}`;
1278
+ }
1233
1279
  function parseBasicAuthHeader(authHeader) {
1234
1280
  const basicMatch = authHeader?.match(/^Basic\s+(.+)$/);
1235
1281
  if (!basicMatch) return null;
@@ -1242,6 +1288,18 @@ function parseBasicAuthHeader(authHeader) {
1242
1288
  return null;
1243
1289
  }
1244
1290
  }
1291
+ function stripBasePath(path, basePath) {
1292
+ if (!basePath) {
1293
+ return path;
1294
+ }
1295
+ if (path === basePath) {
1296
+ return "/";
1297
+ }
1298
+ if (path.startsWith(`${basePath}/`)) {
1299
+ return path.slice(basePath.length);
1300
+ }
1301
+ return null;
1302
+ }
1245
1303
  var FastMCPEventEmitterBase = EventEmitter;
1246
1304
  var FastMCPEventEmitter = class extends FastMCPEventEmitterBase {
1247
1305
  };
@@ -1596,9 +1654,13 @@ var FastMCP = class extends FastMCPEventEmitter {
1596
1654
  } else if (config.transportType === "httpStream") {
1597
1655
  const httpConfig = config.httpStream;
1598
1656
  const protocol = httpConfig.sslCert || httpConfig.sslKey ? "https" : "http";
1657
+ const streamEndpoint = joinPaths(
1658
+ httpConfig.basePath,
1659
+ httpConfig.endpoint
1660
+ );
1599
1661
  if (httpConfig.stateless) {
1600
1662
  this.#logger.info(
1601
- `[FastMCP info] Starting server in stateless mode on HTTP Stream at ${protocol}://${httpConfig.host}:${httpConfig.port}${httpConfig.endpoint}`
1663
+ `[FastMCP info] Starting server in stateless mode on HTTP Stream at ${protocol}://${httpConfig.host}:${httpConfig.port}${streamEndpoint}`
1602
1664
  );
1603
1665
  this.#httpStreamServer = await startHTTPServer({
1604
1666
  ...this.#authenticate ? { authenticate: this.#authenticate } : {},
@@ -1638,7 +1700,8 @@ var FastMCP = class extends FastMCPEventEmitter {
1638
1700
  res,
1639
1701
  true,
1640
1702
  httpConfig.host,
1641
- httpConfig.endpoint
1703
+ streamEndpoint,
1704
+ httpConfig.basePath
1642
1705
  );
1643
1706
  },
1644
1707
  port: httpConfig.port,
@@ -1646,7 +1709,7 @@ var FastMCP = class extends FastMCPEventEmitter {
1646
1709
  sslCert: httpConfig.sslCert,
1647
1710
  sslKey: httpConfig.sslKey,
1648
1711
  stateless: true,
1649
- streamEndpoint: httpConfig.endpoint
1712
+ streamEndpoint
1650
1713
  });
1651
1714
  } else {
1652
1715
  this.#httpStreamServer = await startHTTPServer({
@@ -1690,7 +1753,8 @@ var FastMCP = class extends FastMCPEventEmitter {
1690
1753
  res,
1691
1754
  false,
1692
1755
  httpConfig.host,
1693
- httpConfig.endpoint
1756
+ streamEndpoint,
1757
+ httpConfig.basePath
1694
1758
  );
1695
1759
  },
1696
1760
  port: httpConfig.port,
@@ -1698,10 +1762,10 @@ var FastMCP = class extends FastMCPEventEmitter {
1698
1762
  sslCert: httpConfig.sslCert,
1699
1763
  sslKey: httpConfig.sslKey,
1700
1764
  stateless: httpConfig.stateless,
1701
- streamEndpoint: httpConfig.endpoint
1765
+ streamEndpoint
1702
1766
  });
1703
1767
  this.#logger.info(
1704
- `[FastMCP info] server is running on HTTP Stream at ${protocol}://${httpConfig.host}:${httpConfig.port}${httpConfig.endpoint}`
1768
+ `[FastMCP info] server is running on HTTP Stream at ${protocol}://${httpConfig.host}:${httpConfig.port}${streamEndpoint}`
1705
1769
  );
1706
1770
  }
1707
1771
  this.#serverState = "running" /* Running */;
@@ -1751,8 +1815,9 @@ var FastMCP = class extends FastMCPEventEmitter {
1751
1815
  /**
1752
1816
  * Handles unhandled HTTP requests with health, readiness, OAuth endpoints, and custom routes
1753
1817
  */
1754
- #handleUnhandledRequest = async (req, res, isStateless = false, host, streamEndpoint) => {
1818
+ #handleUnhandledRequest = async (req, res, isStateless = false, host, streamEndpoint, basePath = "") => {
1755
1819
  const url = new URL(req.url || "", `http://${host}`);
1820
+ const basePathRelativePath = stripBasePath(url.pathname, basePath);
1756
1821
  try {
1757
1822
  const webRequest = this.#nodeRequestToWebRequest(req, url);
1758
1823
  const honoResponse = await this.#honoApp.fetch(webRequest, {
@@ -1788,9 +1853,8 @@ var FastMCP = class extends FastMCPEventEmitter {
1788
1853
  const enabled = healthConfig.enabled === void 0 ? true : healthConfig.enabled;
1789
1854
  if (enabled) {
1790
1855
  const path = healthConfig.path ?? "/health";
1791
- const url2 = new URL(req.url || "", `http://${host}`);
1792
1856
  try {
1793
- if ((req.method === "GET" || req.method === "HEAD") && url2.pathname === path) {
1857
+ if ((req.method === "GET" || req.method === "HEAD") && url.pathname === joinPaths(basePath, path)) {
1794
1858
  res.writeHead(healthConfig.status ?? 200, {
1795
1859
  "Content-Type": "text/plain"
1796
1860
  }).end(
@@ -1798,7 +1862,7 @@ var FastMCP = class extends FastMCPEventEmitter {
1798
1862
  );
1799
1863
  return;
1800
1864
  }
1801
- if ((req.method === "GET" || req.method === "HEAD") && url2.pathname === "/ready") {
1865
+ if ((req.method === "GET" || req.method === "HEAD") && url.pathname === joinPaths(basePath, "/ready")) {
1802
1866
  if (isStateless) {
1803
1867
  const response = {
1804
1868
  mode: "stateless",
@@ -1837,7 +1901,11 @@ var FastMCP = class extends FastMCPEventEmitter {
1837
1901
  const oauthConfig = this.#options.oauth;
1838
1902
  if (oauthConfig?.enabled && req.method === "GET") {
1839
1903
  const url2 = new URL(req.url || "", `http://${host}`);
1840
- if (url2.pathname === "/.well-known/oauth-authorization-server" && oauthConfig.authorizationServer) {
1904
+ const authorizationServerMetadataPath = joinPaths(
1905
+ "",
1906
+ `/.well-known/oauth-authorization-server${basePath}`
1907
+ );
1908
+ if (url2.pathname === authorizationServerMetadataPath && oauthConfig.authorizationServer) {
1841
1909
  const metadata = convertObjectToSnakeCase(
1842
1910
  oauthConfig.authorizationServer
1843
1911
  );
@@ -1868,8 +1936,9 @@ var FastMCP = class extends FastMCPEventEmitter {
1868
1936
  const oauthProxy = oauthConfig?.proxy;
1869
1937
  if (oauthProxy && oauthConfig?.enabled) {
1870
1938
  const url2 = new URL(req.url || "", `http://${host}`);
1939
+ const oauthPath = basePathRelativePath;
1871
1940
  try {
1872
- if (req.method === "POST" && url2.pathname === "/oauth/register") {
1941
+ if (req.method === "POST" && oauthPath === "/oauth/register") {
1873
1942
  await new Promise((resolve) => {
1874
1943
  let body = "";
1875
1944
  req.on("data", (chunk) => body += chunk);
@@ -1893,7 +1962,7 @@ var FastMCP = class extends FastMCPEventEmitter {
1893
1962
  });
1894
1963
  return;
1895
1964
  }
1896
- if (req.method === "GET" && url2.pathname === "/oauth/authorize") {
1965
+ if (req.method === "GET" && oauthPath === "/oauth/authorize") {
1897
1966
  try {
1898
1967
  const params = Object.fromEntries(url2.searchParams.entries());
1899
1968
  const response = await oauthProxy.authorize(
@@ -1917,7 +1986,7 @@ var FastMCP = class extends FastMCPEventEmitter {
1917
1986
  }
1918
1987
  return;
1919
1988
  }
1920
- if (req.method === "GET" && url2.pathname === "/oauth/callback") {
1989
+ if (req.method === "GET" && oauthPath === "/oauth/callback") {
1921
1990
  try {
1922
1991
  const mockRequest = new Request(`http://${host}${req.url}`);
1923
1992
  const response = await oauthProxy.handleCallback(mockRequest);
@@ -1939,14 +2008,14 @@ var FastMCP = class extends FastMCPEventEmitter {
1939
2008
  }
1940
2009
  return;
1941
2010
  }
1942
- if (req.method === "POST" && url2.pathname === "/oauth/consent") {
2011
+ if (req.method === "POST" && oauthPath === "/oauth/consent") {
1943
2012
  await new Promise((resolve) => {
1944
2013
  let body = "";
1945
2014
  req.on("data", (chunk) => body += chunk);
1946
2015
  req.on("end", async () => {
1947
2016
  try {
1948
2017
  const mockRequest = new Request(
1949
- `http://${host}/oauth/consent`,
2018
+ `http://${host}${url2.pathname}${url2.search}`,
1950
2019
  {
1951
2020
  body,
1952
2021
  headers: {
@@ -1977,7 +2046,7 @@ var FastMCP = class extends FastMCPEventEmitter {
1977
2046
  });
1978
2047
  return;
1979
2048
  }
1980
- if (req.method === "POST" && url2.pathname === "/oauth/token") {
2049
+ if (req.method === "POST" && oauthPath === "/oauth/token") {
1981
2050
  await new Promise((resolve) => {
1982
2051
  let body = "";
1983
2052
  req.on("data", (chunk) => body += chunk);
@@ -2081,11 +2150,13 @@ var FastMCP = class extends FastMCPEventEmitter {
2081
2150
  const transportArg = getArg("transport");
2082
2151
  const portArg = getArg("port");
2083
2152
  const endpointArg = getArg("endpoint");
2153
+ const basePathArg = getArg("base-path");
2084
2154
  const statelessArg = getArg("stateless");
2085
2155
  const hostArg = getArg("host");
2086
2156
  const envTransport = process.env.FASTMCP_TRANSPORT;
2087
2157
  const envPort = process.env.FASTMCP_PORT;
2088
2158
  const envEndpoint = process.env.FASTMCP_ENDPOINT;
2159
+ const envBasePath = process.env.FASTMCP_BASE_PATH;
2089
2160
  const envStateless = process.env.FASTMCP_STATELESS;
2090
2161
  const envHost = process.env.FASTMCP_HOST;
2091
2162
  const transportType = overrides?.transportType || (transportArg === "http-stream" ? "httpStream" : transportArg) || envTransport || "stdio";
@@ -2095,6 +2166,9 @@ var FastMCP = class extends FastMCPEventEmitter {
2095
2166
  );
2096
2167
  const host = overrides?.httpStream?.host || hostArg || envHost || "localhost";
2097
2168
  const endpoint = overrides?.httpStream?.endpoint || endpointArg || envEndpoint || "/mcp";
2169
+ const basePath = normalizeBasePath(
2170
+ overrides?.httpStream?.basePath || basePathArg || envBasePath
2171
+ );
2098
2172
  const enableJsonResponse = overrides?.httpStream?.enableJsonResponse || false;
2099
2173
  const stateless = overrides?.httpStream?.stateless || statelessArg === "true" || envStateless === "true" || false;
2100
2174
  const cors = overrides?.httpStream?.cors;
@@ -2104,6 +2178,7 @@ var FastMCP = class extends FastMCPEventEmitter {
2104
2178
  const sslKey = overrides?.httpStream?.sslKey;
2105
2179
  return {
2106
2180
  httpStream: {
2181
+ basePath,
2107
2182
  cors,
2108
2183
  enableJsonResponse,
2109
2184
  endpoint,
@@ -2173,4 +2248,4 @@ export {
2173
2248
  FastMCPSession,
2174
2249
  FastMCP
2175
2250
  };
2176
- //# sourceMappingURL=chunk-LWU5CQGW.js.map
2251
+ //# sourceMappingURL=chunk-BTMLQWIR.js.map