fastmcp 4.3.1 → 4.4.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.
package/README.md CHANGED
@@ -139,6 +139,8 @@ This will start the server and listen for HTTP streaming connections on `http://
139
139
 
140
140
  > **Note:** You can also customize the endpoint path using the `httpStream.endpoint` option (default is `/mcp`).
141
141
 
142
+ > **Note:** To serve HTTP streaming and built-in OAuth routes under an issuer path, set `httpStream.basePath` (for example, `/issuer1`). This exposes authorization server metadata at `/.well-known/oauth-authorization-server/issuer1` per RFC 8414.
143
+
142
144
  > **Note:** This also starts an SSE server on `http://localhost:8080/sse`.
143
145
 
144
146
  You can connect to these servers using the appropriate client transport.
@@ -1923,9 +1925,29 @@ const server = new FastMCP({
1923
1925
  });
1924
1926
  ```
1925
1927
 
1928
+ If your MCP server is published below an issuer path, configure the HTTP
1929
+ stream base path as well:
1930
+
1931
+ ```ts
1932
+ server.start({
1933
+ transportType: "httpStream",
1934
+ httpStream: {
1935
+ basePath: "/issuer1",
1936
+ endpoint: "/mcp",
1937
+ port: 8080,
1938
+ },
1939
+ });
1940
+ ```
1941
+
1942
+ With this configuration, FastMCP serves the issuer-path authorization server
1943
+ metadata at `/.well-known/oauth-authorization-server/issuer1`, while protected
1944
+ resource metadata remains available for the MCP endpoint at
1945
+ `/.well-known/oauth-protected-resource/issuer1/mcp`.
1946
+
1926
1947
  This configuration automatically exposes OAuth discovery endpoints:
1927
1948
 
1928
1949
  - `/.well-known/oauth-authorization-server` - Authorization server metadata (RFC 8414)
1950
+ - `/.well-known/oauth-authorization-server<basePath>` - Authorization server metadata when `httpStream.basePath` is set (RFC 8414 Section 3)
1929
1951
  - `/.well-known/oauth-protected-resource` - Protected resource metadata (RFC 9728)
1930
1952
  - `/.well-known/oauth-protected-resource<endpoint>` - Protected resource metadata at sub-path (MCP 2025-11-25)
1931
1953
 
package/dist/FastMCP.cjs CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
 
9
9
 
10
- var _chunkRPY6M6HDcjs = require('./chunk-RPY6M6HD.cjs');
10
+ var _chunk3YTRRGCEcjs = require('./chunk-3YTRRGCE.cjs');
11
11
 
12
12
 
13
13
 
@@ -41,5 +41,5 @@ var _chunkIX3HKAX4cjs = require('./chunk-IX3HKAX4.cjs');
41
41
 
42
42
 
43
43
 
44
- exports.AuthProvider = _chunkIX3HKAX4cjs.AuthProvider; exports.AzureProvider = _chunkIX3HKAX4cjs.AzureProvider; exports.DiscoveryDocumentCache = _chunkRPY6M6HDcjs.DiscoveryDocumentCache; exports.FastMCP = _chunkRPY6M6HDcjs.FastMCP; exports.FastMCPSession = _chunkRPY6M6HDcjs.FastMCPSession; exports.GitHubProvider = _chunkIX3HKAX4cjs.GitHubProvider; exports.GoogleProvider = _chunkIX3HKAX4cjs.GoogleProvider; exports.OAuthProvider = _chunkIX3HKAX4cjs.OAuthProvider; exports.ServerState = _chunkRPY6M6HDcjs.ServerState; exports.UnexpectedStateError = _chunkRPY6M6HDcjs.UnexpectedStateError; exports.UserError = _chunkRPY6M6HDcjs.UserError; exports.audioContent = _chunkRPY6M6HDcjs.audioContent; exports.getAuthSession = _chunkIX3HKAX4cjs.getAuthSession; exports.imageContent = _chunkRPY6M6HDcjs.imageContent; exports.requireAll = _chunkIX3HKAX4cjs.requireAll; exports.requireAny = _chunkIX3HKAX4cjs.requireAny; exports.requireAuth = _chunkIX3HKAX4cjs.requireAuth; exports.requireRole = _chunkIX3HKAX4cjs.requireRole; exports.requireScopes = _chunkIX3HKAX4cjs.requireScopes;
44
+ exports.AuthProvider = _chunkIX3HKAX4cjs.AuthProvider; exports.AzureProvider = _chunkIX3HKAX4cjs.AzureProvider; exports.DiscoveryDocumentCache = _chunk3YTRRGCEcjs.DiscoveryDocumentCache; exports.FastMCP = _chunk3YTRRGCEcjs.FastMCP; exports.FastMCPSession = _chunk3YTRRGCEcjs.FastMCPSession; exports.GitHubProvider = _chunkIX3HKAX4cjs.GitHubProvider; exports.GoogleProvider = _chunkIX3HKAX4cjs.GoogleProvider; exports.OAuthProvider = _chunkIX3HKAX4cjs.OAuthProvider; exports.ServerState = _chunk3YTRRGCEcjs.ServerState; exports.UnexpectedStateError = _chunk3YTRRGCEcjs.UnexpectedStateError; exports.UserError = _chunk3YTRRGCEcjs.UserError; exports.audioContent = _chunk3YTRRGCEcjs.audioContent; exports.getAuthSession = _chunkIX3HKAX4cjs.getAuthSession; exports.imageContent = _chunk3YTRRGCEcjs.imageContent; exports.requireAll = _chunkIX3HKAX4cjs.requireAll; exports.requireAny = _chunkIX3HKAX4cjs.requireAny; exports.requireAuth = _chunkIX3HKAX4cjs.requireAuth; exports.requireRole = _chunkIX3HKAX4cjs.requireRole; exports.requireScopes = _chunkIX3HKAX4cjs.requireScopes;
45
45
  //# sourceMappingURL=FastMCP.cjs.map
@@ -861,6 +861,7 @@ declare class FastMCP<T extends FastMCPSessionAuth = FastMCPSessionAuth> extends
861
861
  */
862
862
  start(options?: Partial<{
863
863
  httpStream: {
864
+ basePath?: `/${string}`;
864
865
  cors?: boolean | CorsOptions;
865
866
  enableJsonResponse?: boolean;
866
867
  endpoint?: `/${string}`;
package/dist/FastMCP.d.ts CHANGED
@@ -861,6 +861,7 @@ declare class FastMCP<T extends FastMCPSessionAuth = FastMCPSessionAuth> extends
861
861
  */
862
862
  start(options?: Partial<{
863
863
  httpStream: {
864
+ basePath?: `/${string}`;
864
865
  cors?: boolean | CorsOptions;
865
866
  enableJsonResponse?: boolean;
866
867
  endpoint?: `/${string}`;
package/dist/FastMCP.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  UserError,
8
8
  audioContent,
9
9
  imageContent
10
- } from "./chunk-KFZGSERX.js";
10
+ } from "./chunk-MDIESGNI.js";
11
11
  import {
12
12
  AuthProvider,
13
13
  AzureProvider,
@@ -1044,6 +1044,9 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
1044
1044
  let result;
1045
1045
  try {
1046
1046
  const reportProgress = async (progress) => {
1047
+ if (progressToken === void 0) {
1048
+ return;
1049
+ }
1047
1050
  try {
1048
1051
  await this.#server.notification({
1049
1052
  method: "notifications/progress",
@@ -1227,6 +1230,20 @@ function convertObjectToSnakeCase(obj) {
1227
1230
  }
1228
1231
  return result;
1229
1232
  }
1233
+ function joinPaths(basePath, path) {
1234
+ return `${basePath}${normalizePath(path)}`;
1235
+ }
1236
+ function normalizeBasePath(path) {
1237
+ if (!path || path === "/") {
1238
+ return "";
1239
+ }
1240
+ const withLeadingSlash = path.startsWith("/") ? path : `/${path}`;
1241
+ const withoutTrailingSlash = withLeadingSlash.replace(/\/+$/, "");
1242
+ return withoutTrailingSlash ? withoutTrailingSlash : "";
1243
+ }
1244
+ function normalizePath(path) {
1245
+ return path.startsWith("/") ? path : `/${path}`;
1246
+ }
1230
1247
  function parseBasicAuthHeader(authHeader) {
1231
1248
  const basicMatch = _optionalChain([authHeader, 'optionalAccess', _25 => _25.match, 'call', _26 => _26(/^Basic\s+(.+)$/)]);
1232
1249
  if (!basicMatch) return null;
@@ -1239,6 +1256,18 @@ function parseBasicAuthHeader(authHeader) {
1239
1256
  return null;
1240
1257
  }
1241
1258
  }
1259
+ function stripBasePath(path, basePath) {
1260
+ if (!basePath) {
1261
+ return path;
1262
+ }
1263
+ if (path === basePath) {
1264
+ return "/";
1265
+ }
1266
+ if (path.startsWith(`${basePath}/`)) {
1267
+ return path.slice(basePath.length);
1268
+ }
1269
+ return null;
1270
+ }
1242
1271
  var FastMCPEventEmitterBase = _events.EventEmitter;
1243
1272
  var FastMCPEventEmitter = class extends FastMCPEventEmitterBase {
1244
1273
  };
@@ -1593,9 +1622,13 @@ var FastMCP = class extends FastMCPEventEmitter {
1593
1622
  } else if (config.transportType === "httpStream") {
1594
1623
  const httpConfig = config.httpStream;
1595
1624
  const protocol = httpConfig.sslCert || httpConfig.sslKey ? "https" : "http";
1625
+ const streamEndpoint = joinPaths(
1626
+ httpConfig.basePath,
1627
+ httpConfig.endpoint
1628
+ );
1596
1629
  if (httpConfig.stateless) {
1597
1630
  this.#logger.info(
1598
- `[FastMCP info] Starting server in stateless mode on HTTP Stream at ${protocol}://${httpConfig.host}:${httpConfig.port}${httpConfig.endpoint}`
1631
+ `[FastMCP info] Starting server in stateless mode on HTTP Stream at ${protocol}://${httpConfig.host}:${httpConfig.port}${streamEndpoint}`
1599
1632
  );
1600
1633
  this.#httpStreamServer = await _mcpproxy.startHTTPServer.call(void 0, {
1601
1634
  ...this.#authenticate ? { authenticate: this.#authenticate } : {},
@@ -1635,7 +1668,8 @@ var FastMCP = class extends FastMCPEventEmitter {
1635
1668
  res,
1636
1669
  true,
1637
1670
  httpConfig.host,
1638
- httpConfig.endpoint
1671
+ streamEndpoint,
1672
+ httpConfig.basePath
1639
1673
  );
1640
1674
  },
1641
1675
  port: httpConfig.port,
@@ -1643,7 +1677,7 @@ var FastMCP = class extends FastMCPEventEmitter {
1643
1677
  sslCert: httpConfig.sslCert,
1644
1678
  sslKey: httpConfig.sslKey,
1645
1679
  stateless: true,
1646
- streamEndpoint: httpConfig.endpoint
1680
+ streamEndpoint
1647
1681
  });
1648
1682
  } else {
1649
1683
  this.#httpStreamServer = await _mcpproxy.startHTTPServer.call(void 0, {
@@ -1687,7 +1721,8 @@ var FastMCP = class extends FastMCPEventEmitter {
1687
1721
  res,
1688
1722
  false,
1689
1723
  httpConfig.host,
1690
- httpConfig.endpoint
1724
+ streamEndpoint,
1725
+ httpConfig.basePath
1691
1726
  );
1692
1727
  },
1693
1728
  port: httpConfig.port,
@@ -1695,10 +1730,10 @@ var FastMCP = class extends FastMCPEventEmitter {
1695
1730
  sslCert: httpConfig.sslCert,
1696
1731
  sslKey: httpConfig.sslKey,
1697
1732
  stateless: httpConfig.stateless,
1698
- streamEndpoint: httpConfig.endpoint
1733
+ streamEndpoint
1699
1734
  });
1700
1735
  this.#logger.info(
1701
- `[FastMCP info] server is running on HTTP Stream at ${protocol}://${httpConfig.host}:${httpConfig.port}${httpConfig.endpoint}`
1736
+ `[FastMCP info] server is running on HTTP Stream at ${protocol}://${httpConfig.host}:${httpConfig.port}${streamEndpoint}`
1702
1737
  );
1703
1738
  }
1704
1739
  this.#serverState = "running" /* Running */;
@@ -1748,8 +1783,9 @@ var FastMCP = class extends FastMCPEventEmitter {
1748
1783
  /**
1749
1784
  * Handles unhandled HTTP requests with health, readiness, OAuth endpoints, and custom routes
1750
1785
  */
1751
- #handleUnhandledRequest = async (req, res, isStateless = false, host, streamEndpoint) => {
1786
+ #handleUnhandledRequest = async (req, res, isStateless = false, host, streamEndpoint, basePath = "") => {
1752
1787
  const url = new URL(req.url || "", `http://${host}`);
1788
+ const basePathRelativePath = stripBasePath(url.pathname, basePath);
1753
1789
  try {
1754
1790
  const webRequest = this.#nodeRequestToWebRequest(req, url);
1755
1791
  const honoResponse = await this.#honoApp.fetch(webRequest, {
@@ -1785,9 +1821,8 @@ var FastMCP = class extends FastMCPEventEmitter {
1785
1821
  const enabled = healthConfig.enabled === void 0 ? true : healthConfig.enabled;
1786
1822
  if (enabled) {
1787
1823
  const path = _nullishCoalesce(healthConfig.path, () => ( "/health"));
1788
- const url2 = new URL(req.url || "", `http://${host}`);
1789
1824
  try {
1790
- if ((req.method === "GET" || req.method === "HEAD") && url2.pathname === path) {
1825
+ if ((req.method === "GET" || req.method === "HEAD") && url.pathname === joinPaths(basePath, path)) {
1791
1826
  res.writeHead(_nullishCoalesce(healthConfig.status, () => ( 200)), {
1792
1827
  "Content-Type": "text/plain"
1793
1828
  }).end(
@@ -1795,7 +1830,7 @@ var FastMCP = class extends FastMCPEventEmitter {
1795
1830
  );
1796
1831
  return;
1797
1832
  }
1798
- if ((req.method === "GET" || req.method === "HEAD") && url2.pathname === "/ready") {
1833
+ if ((req.method === "GET" || req.method === "HEAD") && url.pathname === joinPaths(basePath, "/ready")) {
1799
1834
  if (isStateless) {
1800
1835
  const response = {
1801
1836
  mode: "stateless",
@@ -1834,7 +1869,11 @@ var FastMCP = class extends FastMCPEventEmitter {
1834
1869
  const oauthConfig = this.#options.oauth;
1835
1870
  if (_optionalChain([oauthConfig, 'optionalAccess', _41 => _41.enabled]) && req.method === "GET") {
1836
1871
  const url2 = new URL(req.url || "", `http://${host}`);
1837
- if (url2.pathname === "/.well-known/oauth-authorization-server" && oauthConfig.authorizationServer) {
1872
+ const authorizationServerMetadataPath = joinPaths(
1873
+ "",
1874
+ `/.well-known/oauth-authorization-server${basePath}`
1875
+ );
1876
+ if (url2.pathname === authorizationServerMetadataPath && oauthConfig.authorizationServer) {
1838
1877
  const metadata = convertObjectToSnakeCase(
1839
1878
  oauthConfig.authorizationServer
1840
1879
  );
@@ -1865,8 +1904,9 @@ var FastMCP = class extends FastMCPEventEmitter {
1865
1904
  const oauthProxy = _optionalChain([oauthConfig, 'optionalAccess', _42 => _42.proxy]);
1866
1905
  if (oauthProxy && _optionalChain([oauthConfig, 'optionalAccess', _43 => _43.enabled])) {
1867
1906
  const url2 = new URL(req.url || "", `http://${host}`);
1907
+ const oauthPath = basePathRelativePath;
1868
1908
  try {
1869
- if (req.method === "POST" && url2.pathname === "/oauth/register") {
1909
+ if (req.method === "POST" && oauthPath === "/oauth/register") {
1870
1910
  await new Promise((resolve) => {
1871
1911
  let body = "";
1872
1912
  req.on("data", (chunk) => body += chunk);
@@ -1890,7 +1930,7 @@ var FastMCP = class extends FastMCPEventEmitter {
1890
1930
  });
1891
1931
  return;
1892
1932
  }
1893
- if (req.method === "GET" && url2.pathname === "/oauth/authorize") {
1933
+ if (req.method === "GET" && oauthPath === "/oauth/authorize") {
1894
1934
  try {
1895
1935
  const params = Object.fromEntries(url2.searchParams.entries());
1896
1936
  const response = await oauthProxy.authorize(
@@ -1914,7 +1954,7 @@ var FastMCP = class extends FastMCPEventEmitter {
1914
1954
  }
1915
1955
  return;
1916
1956
  }
1917
- if (req.method === "GET" && url2.pathname === "/oauth/callback") {
1957
+ if (req.method === "GET" && oauthPath === "/oauth/callback") {
1918
1958
  try {
1919
1959
  const mockRequest = new Request(`http://${host}${req.url}`);
1920
1960
  const response = await oauthProxy.handleCallback(mockRequest);
@@ -1936,14 +1976,14 @@ var FastMCP = class extends FastMCPEventEmitter {
1936
1976
  }
1937
1977
  return;
1938
1978
  }
1939
- if (req.method === "POST" && url2.pathname === "/oauth/consent") {
1979
+ if (req.method === "POST" && oauthPath === "/oauth/consent") {
1940
1980
  await new Promise((resolve) => {
1941
1981
  let body = "";
1942
1982
  req.on("data", (chunk) => body += chunk);
1943
1983
  req.on("end", async () => {
1944
1984
  try {
1945
1985
  const mockRequest = new Request(
1946
- `http://${host}/oauth/consent`,
1986
+ `http://${host}${url2.pathname}${url2.search}`,
1947
1987
  {
1948
1988
  body,
1949
1989
  headers: {
@@ -1974,7 +2014,7 @@ var FastMCP = class extends FastMCPEventEmitter {
1974
2014
  });
1975
2015
  return;
1976
2016
  }
1977
- if (req.method === "POST" && url2.pathname === "/oauth/token") {
2017
+ if (req.method === "POST" && oauthPath === "/oauth/token") {
1978
2018
  await new Promise((resolve) => {
1979
2019
  let body = "";
1980
2020
  req.on("data", (chunk) => body += chunk);
@@ -2078,11 +2118,13 @@ var FastMCP = class extends FastMCPEventEmitter {
2078
2118
  const transportArg = getArg("transport");
2079
2119
  const portArg = getArg("port");
2080
2120
  const endpointArg = getArg("endpoint");
2121
+ const basePathArg = getArg("base-path");
2081
2122
  const statelessArg = getArg("stateless");
2082
2123
  const hostArg = getArg("host");
2083
2124
  const envTransport = process.env.FASTMCP_TRANSPORT;
2084
2125
  const envPort = process.env.FASTMCP_PORT;
2085
2126
  const envEndpoint = process.env.FASTMCP_ENDPOINT;
2127
+ const envBasePath = process.env.FASTMCP_BASE_PATH;
2086
2128
  const envStateless = process.env.FASTMCP_STATELESS;
2087
2129
  const envHost = process.env.FASTMCP_HOST;
2088
2130
  const transportType = _optionalChain([overrides, 'optionalAccess', _56 => _56.transportType]) || (transportArg === "http-stream" ? "httpStream" : transportArg) || envTransport || "stdio";
@@ -2092,15 +2134,19 @@ var FastMCP = class extends FastMCPEventEmitter {
2092
2134
  );
2093
2135
  const host = _optionalChain([overrides, 'optionalAccess', _61 => _61.httpStream, 'optionalAccess', _62 => _62.host]) || hostArg || envHost || "localhost";
2094
2136
  const endpoint = _optionalChain([overrides, 'optionalAccess', _63 => _63.httpStream, 'optionalAccess', _64 => _64.endpoint]) || endpointArg || envEndpoint || "/mcp";
2095
- const enableJsonResponse = _optionalChain([overrides, 'optionalAccess', _65 => _65.httpStream, 'optionalAccess', _66 => _66.enableJsonResponse]) || false;
2096
- const stateless = _optionalChain([overrides, 'optionalAccess', _67 => _67.httpStream, 'optionalAccess', _68 => _68.stateless]) || statelessArg === "true" || envStateless === "true" || false;
2097
- const cors = _optionalChain([overrides, 'optionalAccess', _69 => _69.httpStream, 'optionalAccess', _70 => _70.cors]);
2098
- const eventStore = _optionalChain([overrides, 'optionalAccess', _71 => _71.httpStream, 'optionalAccess', _72 => _72.eventStore]);
2099
- const sslCa = _optionalChain([overrides, 'optionalAccess', _73 => _73.httpStream, 'optionalAccess', _74 => _74.sslCa]);
2100
- const sslCert = _optionalChain([overrides, 'optionalAccess', _75 => _75.httpStream, 'optionalAccess', _76 => _76.sslCert]);
2101
- const sslKey = _optionalChain([overrides, 'optionalAccess', _77 => _77.httpStream, 'optionalAccess', _78 => _78.sslKey]);
2137
+ const basePath = normalizeBasePath(
2138
+ _optionalChain([overrides, 'optionalAccess', _65 => _65.httpStream, 'optionalAccess', _66 => _66.basePath]) || basePathArg || envBasePath
2139
+ );
2140
+ const enableJsonResponse = _optionalChain([overrides, 'optionalAccess', _67 => _67.httpStream, 'optionalAccess', _68 => _68.enableJsonResponse]) || false;
2141
+ const stateless = _optionalChain([overrides, 'optionalAccess', _69 => _69.httpStream, 'optionalAccess', _70 => _70.stateless]) || statelessArg === "true" || envStateless === "true" || false;
2142
+ const cors = _optionalChain([overrides, 'optionalAccess', _71 => _71.httpStream, 'optionalAccess', _72 => _72.cors]);
2143
+ const eventStore = _optionalChain([overrides, 'optionalAccess', _73 => _73.httpStream, 'optionalAccess', _74 => _74.eventStore]);
2144
+ const sslCa = _optionalChain([overrides, 'optionalAccess', _75 => _75.httpStream, 'optionalAccess', _76 => _76.sslCa]);
2145
+ const sslCert = _optionalChain([overrides, 'optionalAccess', _77 => _77.httpStream, 'optionalAccess', _78 => _78.sslCert]);
2146
+ const sslKey = _optionalChain([overrides, 'optionalAccess', _79 => _79.httpStream, 'optionalAccess', _80 => _80.sslKey]);
2102
2147
  return {
2103
2148
  httpStream: {
2149
+ basePath,
2104
2150
  cors,
2105
2151
  enableJsonResponse,
2106
2152
  endpoint,
@@ -2170,4 +2216,4 @@ var FastMCP = class extends FastMCPEventEmitter {
2170
2216
 
2171
2217
 
2172
2218
  exports.DiscoveryDocumentCache = DiscoveryDocumentCache; exports.imageContent = imageContent; exports.audioContent = audioContent; exports.UnexpectedStateError = UnexpectedStateError; exports.UserError = UserError; exports.ServerState = ServerState; exports.FastMCPSession = FastMCPSession; exports.FastMCP = FastMCP;
2173
- //# sourceMappingURL=chunk-RPY6M6HD.cjs.map
2219
+ //# sourceMappingURL=chunk-3YTRRGCE.cjs.map