fastmcp 4.3.2 → 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 _chunkUYG7NPM6cjs = require('./chunk-UYG7NPM6.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 = _chunkUYG7NPM6cjs.DiscoveryDocumentCache; exports.FastMCP = _chunkUYG7NPM6cjs.FastMCP; exports.FastMCPSession = _chunkUYG7NPM6cjs.FastMCPSession; exports.GitHubProvider = _chunkIX3HKAX4cjs.GitHubProvider; exports.GoogleProvider = _chunkIX3HKAX4cjs.GoogleProvider; exports.OAuthProvider = _chunkIX3HKAX4cjs.OAuthProvider; exports.ServerState = _chunkUYG7NPM6cjs.ServerState; exports.UnexpectedStateError = _chunkUYG7NPM6cjs.UnexpectedStateError; exports.UserError = _chunkUYG7NPM6cjs.UserError; exports.audioContent = _chunkUYG7NPM6cjs.audioContent; exports.getAuthSession = _chunkIX3HKAX4cjs.getAuthSession; exports.imageContent = _chunkUYG7NPM6cjs.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-LWU5CQGW.js";
10
+ } from "./chunk-MDIESGNI.js";
11
11
  import {
12
12
  AuthProvider,
13
13
  AzureProvider,
@@ -1230,6 +1230,20 @@ function convertObjectToSnakeCase(obj) {
1230
1230
  }
1231
1231
  return result;
1232
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
+ }
1233
1247
  function parseBasicAuthHeader(authHeader) {
1234
1248
  const basicMatch = _optionalChain([authHeader, 'optionalAccess', _25 => _25.match, 'call', _26 => _26(/^Basic\s+(.+)$/)]);
1235
1249
  if (!basicMatch) return null;
@@ -1242,6 +1256,18 @@ function parseBasicAuthHeader(authHeader) {
1242
1256
  return null;
1243
1257
  }
1244
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
+ }
1245
1271
  var FastMCPEventEmitterBase = _events.EventEmitter;
1246
1272
  var FastMCPEventEmitter = class extends FastMCPEventEmitterBase {
1247
1273
  };
@@ -1596,9 +1622,13 @@ var FastMCP = class extends FastMCPEventEmitter {
1596
1622
  } else if (config.transportType === "httpStream") {
1597
1623
  const httpConfig = config.httpStream;
1598
1624
  const protocol = httpConfig.sslCert || httpConfig.sslKey ? "https" : "http";
1625
+ const streamEndpoint = joinPaths(
1626
+ httpConfig.basePath,
1627
+ httpConfig.endpoint
1628
+ );
1599
1629
  if (httpConfig.stateless) {
1600
1630
  this.#logger.info(
1601
- `[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}`
1602
1632
  );
1603
1633
  this.#httpStreamServer = await _mcpproxy.startHTTPServer.call(void 0, {
1604
1634
  ...this.#authenticate ? { authenticate: this.#authenticate } : {},
@@ -1638,7 +1668,8 @@ var FastMCP = class extends FastMCPEventEmitter {
1638
1668
  res,
1639
1669
  true,
1640
1670
  httpConfig.host,
1641
- httpConfig.endpoint
1671
+ streamEndpoint,
1672
+ httpConfig.basePath
1642
1673
  );
1643
1674
  },
1644
1675
  port: httpConfig.port,
@@ -1646,7 +1677,7 @@ var FastMCP = class extends FastMCPEventEmitter {
1646
1677
  sslCert: httpConfig.sslCert,
1647
1678
  sslKey: httpConfig.sslKey,
1648
1679
  stateless: true,
1649
- streamEndpoint: httpConfig.endpoint
1680
+ streamEndpoint
1650
1681
  });
1651
1682
  } else {
1652
1683
  this.#httpStreamServer = await _mcpproxy.startHTTPServer.call(void 0, {
@@ -1690,7 +1721,8 @@ var FastMCP = class extends FastMCPEventEmitter {
1690
1721
  res,
1691
1722
  false,
1692
1723
  httpConfig.host,
1693
- httpConfig.endpoint
1724
+ streamEndpoint,
1725
+ httpConfig.basePath
1694
1726
  );
1695
1727
  },
1696
1728
  port: httpConfig.port,
@@ -1698,10 +1730,10 @@ var FastMCP = class extends FastMCPEventEmitter {
1698
1730
  sslCert: httpConfig.sslCert,
1699
1731
  sslKey: httpConfig.sslKey,
1700
1732
  stateless: httpConfig.stateless,
1701
- streamEndpoint: httpConfig.endpoint
1733
+ streamEndpoint
1702
1734
  });
1703
1735
  this.#logger.info(
1704
- `[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}`
1705
1737
  );
1706
1738
  }
1707
1739
  this.#serverState = "running" /* Running */;
@@ -1751,8 +1783,9 @@ var FastMCP = class extends FastMCPEventEmitter {
1751
1783
  /**
1752
1784
  * Handles unhandled HTTP requests with health, readiness, OAuth endpoints, and custom routes
1753
1785
  */
1754
- #handleUnhandledRequest = async (req, res, isStateless = false, host, streamEndpoint) => {
1786
+ #handleUnhandledRequest = async (req, res, isStateless = false, host, streamEndpoint, basePath = "") => {
1755
1787
  const url = new URL(req.url || "", `http://${host}`);
1788
+ const basePathRelativePath = stripBasePath(url.pathname, basePath);
1756
1789
  try {
1757
1790
  const webRequest = this.#nodeRequestToWebRequest(req, url);
1758
1791
  const honoResponse = await this.#honoApp.fetch(webRequest, {
@@ -1788,9 +1821,8 @@ var FastMCP = class extends FastMCPEventEmitter {
1788
1821
  const enabled = healthConfig.enabled === void 0 ? true : healthConfig.enabled;
1789
1822
  if (enabled) {
1790
1823
  const path = _nullishCoalesce(healthConfig.path, () => ( "/health"));
1791
- const url2 = new URL(req.url || "", `http://${host}`);
1792
1824
  try {
1793
- if ((req.method === "GET" || req.method === "HEAD") && url2.pathname === path) {
1825
+ if ((req.method === "GET" || req.method === "HEAD") && url.pathname === joinPaths(basePath, path)) {
1794
1826
  res.writeHead(_nullishCoalesce(healthConfig.status, () => ( 200)), {
1795
1827
  "Content-Type": "text/plain"
1796
1828
  }).end(
@@ -1798,7 +1830,7 @@ var FastMCP = class extends FastMCPEventEmitter {
1798
1830
  );
1799
1831
  return;
1800
1832
  }
1801
- if ((req.method === "GET" || req.method === "HEAD") && url2.pathname === "/ready") {
1833
+ if ((req.method === "GET" || req.method === "HEAD") && url.pathname === joinPaths(basePath, "/ready")) {
1802
1834
  if (isStateless) {
1803
1835
  const response = {
1804
1836
  mode: "stateless",
@@ -1837,7 +1869,11 @@ var FastMCP = class extends FastMCPEventEmitter {
1837
1869
  const oauthConfig = this.#options.oauth;
1838
1870
  if (_optionalChain([oauthConfig, 'optionalAccess', _41 => _41.enabled]) && req.method === "GET") {
1839
1871
  const url2 = new URL(req.url || "", `http://${host}`);
1840
- 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) {
1841
1877
  const metadata = convertObjectToSnakeCase(
1842
1878
  oauthConfig.authorizationServer
1843
1879
  );
@@ -1868,8 +1904,9 @@ var FastMCP = class extends FastMCPEventEmitter {
1868
1904
  const oauthProxy = _optionalChain([oauthConfig, 'optionalAccess', _42 => _42.proxy]);
1869
1905
  if (oauthProxy && _optionalChain([oauthConfig, 'optionalAccess', _43 => _43.enabled])) {
1870
1906
  const url2 = new URL(req.url || "", `http://${host}`);
1907
+ const oauthPath = basePathRelativePath;
1871
1908
  try {
1872
- if (req.method === "POST" && url2.pathname === "/oauth/register") {
1909
+ if (req.method === "POST" && oauthPath === "/oauth/register") {
1873
1910
  await new Promise((resolve) => {
1874
1911
  let body = "";
1875
1912
  req.on("data", (chunk) => body += chunk);
@@ -1893,7 +1930,7 @@ var FastMCP = class extends FastMCPEventEmitter {
1893
1930
  });
1894
1931
  return;
1895
1932
  }
1896
- if (req.method === "GET" && url2.pathname === "/oauth/authorize") {
1933
+ if (req.method === "GET" && oauthPath === "/oauth/authorize") {
1897
1934
  try {
1898
1935
  const params = Object.fromEntries(url2.searchParams.entries());
1899
1936
  const response = await oauthProxy.authorize(
@@ -1917,7 +1954,7 @@ var FastMCP = class extends FastMCPEventEmitter {
1917
1954
  }
1918
1955
  return;
1919
1956
  }
1920
- if (req.method === "GET" && url2.pathname === "/oauth/callback") {
1957
+ if (req.method === "GET" && oauthPath === "/oauth/callback") {
1921
1958
  try {
1922
1959
  const mockRequest = new Request(`http://${host}${req.url}`);
1923
1960
  const response = await oauthProxy.handleCallback(mockRequest);
@@ -1939,14 +1976,14 @@ var FastMCP = class extends FastMCPEventEmitter {
1939
1976
  }
1940
1977
  return;
1941
1978
  }
1942
- if (req.method === "POST" && url2.pathname === "/oauth/consent") {
1979
+ if (req.method === "POST" && oauthPath === "/oauth/consent") {
1943
1980
  await new Promise((resolve) => {
1944
1981
  let body = "";
1945
1982
  req.on("data", (chunk) => body += chunk);
1946
1983
  req.on("end", async () => {
1947
1984
  try {
1948
1985
  const mockRequest = new Request(
1949
- `http://${host}/oauth/consent`,
1986
+ `http://${host}${url2.pathname}${url2.search}`,
1950
1987
  {
1951
1988
  body,
1952
1989
  headers: {
@@ -1977,7 +2014,7 @@ var FastMCP = class extends FastMCPEventEmitter {
1977
2014
  });
1978
2015
  return;
1979
2016
  }
1980
- if (req.method === "POST" && url2.pathname === "/oauth/token") {
2017
+ if (req.method === "POST" && oauthPath === "/oauth/token") {
1981
2018
  await new Promise((resolve) => {
1982
2019
  let body = "";
1983
2020
  req.on("data", (chunk) => body += chunk);
@@ -2081,11 +2118,13 @@ var FastMCP = class extends FastMCPEventEmitter {
2081
2118
  const transportArg = getArg("transport");
2082
2119
  const portArg = getArg("port");
2083
2120
  const endpointArg = getArg("endpoint");
2121
+ const basePathArg = getArg("base-path");
2084
2122
  const statelessArg = getArg("stateless");
2085
2123
  const hostArg = getArg("host");
2086
2124
  const envTransport = process.env.FASTMCP_TRANSPORT;
2087
2125
  const envPort = process.env.FASTMCP_PORT;
2088
2126
  const envEndpoint = process.env.FASTMCP_ENDPOINT;
2127
+ const envBasePath = process.env.FASTMCP_BASE_PATH;
2089
2128
  const envStateless = process.env.FASTMCP_STATELESS;
2090
2129
  const envHost = process.env.FASTMCP_HOST;
2091
2130
  const transportType = _optionalChain([overrides, 'optionalAccess', _56 => _56.transportType]) || (transportArg === "http-stream" ? "httpStream" : transportArg) || envTransport || "stdio";
@@ -2095,15 +2134,19 @@ var FastMCP = class extends FastMCPEventEmitter {
2095
2134
  );
2096
2135
  const host = _optionalChain([overrides, 'optionalAccess', _61 => _61.httpStream, 'optionalAccess', _62 => _62.host]) || hostArg || envHost || "localhost";
2097
2136
  const endpoint = _optionalChain([overrides, 'optionalAccess', _63 => _63.httpStream, 'optionalAccess', _64 => _64.endpoint]) || endpointArg || envEndpoint || "/mcp";
2098
- const enableJsonResponse = _optionalChain([overrides, 'optionalAccess', _65 => _65.httpStream, 'optionalAccess', _66 => _66.enableJsonResponse]) || false;
2099
- const stateless = _optionalChain([overrides, 'optionalAccess', _67 => _67.httpStream, 'optionalAccess', _68 => _68.stateless]) || statelessArg === "true" || envStateless === "true" || false;
2100
- const cors = _optionalChain([overrides, 'optionalAccess', _69 => _69.httpStream, 'optionalAccess', _70 => _70.cors]);
2101
- const eventStore = _optionalChain([overrides, 'optionalAccess', _71 => _71.httpStream, 'optionalAccess', _72 => _72.eventStore]);
2102
- const sslCa = _optionalChain([overrides, 'optionalAccess', _73 => _73.httpStream, 'optionalAccess', _74 => _74.sslCa]);
2103
- const sslCert = _optionalChain([overrides, 'optionalAccess', _75 => _75.httpStream, 'optionalAccess', _76 => _76.sslCert]);
2104
- 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]);
2105
2147
  return {
2106
2148
  httpStream: {
2149
+ basePath,
2107
2150
  cors,
2108
2151
  enableJsonResponse,
2109
2152
  endpoint,
@@ -2173,4 +2216,4 @@ var FastMCP = class extends FastMCPEventEmitter {
2173
2216
 
2174
2217
 
2175
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;
2176
- //# sourceMappingURL=chunk-UYG7NPM6.cjs.map
2219
+ //# sourceMappingURL=chunk-3YTRRGCE.cjs.map