fastmcp 4.7.0 → 4.7.2

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
@@ -267,87 +267,108 @@ The `CorsOptions` type is exported from `fastmcp` for convenience.
267
267
  FastMCP allows you to add custom HTTP routes alongside MCP endpoints, enabling you to build comprehensive HTTP services that include REST APIs, webhooks, admin interfaces, and more - all within the same server process.
268
268
 
269
269
  ```ts
270
- // Add REST API endpoints
271
- server.addRoute("GET", "/api/users", async (req, res) => {
272
- res.json({ users: [] });
270
+ const app = server.getApp();
271
+
272
+ // Add REST API endpoints with Hono's native API
273
+ app.get("/api/users", async (c) => {
274
+ return c.json({ users: [] });
273
275
  });
274
276
 
275
277
  // Handle path parameters
276
- server.addRoute("GET", "/api/users/:id", async (req, res) => {
277
- res.json({
278
- userId: req.params.id,
279
- query: req.query, // Access query parameters
278
+ app.get("/api/users/:id", async (c) => {
279
+ return c.json({
280
+ userId: c.req.param("id"),
281
+ query: c.req.query(), // Access query parameters
280
282
  });
281
283
  });
282
284
 
283
285
  // Handle POST requests with body parsing
284
- server.addRoute("POST", "/api/users", async (req, res) => {
285
- const body = await req.json();
286
- res.status(201).json({ created: body });
286
+ app.post("/api/users", async (c) => {
287
+ const body = await c.req.json();
288
+ return c.json({ created: body }, 201);
287
289
  });
288
290
 
289
291
  // Serve HTML content
290
- server.addRoute("GET", "/admin", async (req, res) => {
291
- res.send("<html><body><h1>Admin Panel</h1></body></html>");
292
+ app.get("/admin", async (c) => {
293
+ return c.html("<html><body><h1>Admin Panel</h1></body></html>");
292
294
  });
293
295
 
294
296
  // Handle webhooks
295
- server.addRoute("POST", "/webhook/github", async (req, res) => {
296
- const payload = await req.json();
297
- const event = req.headers["x-github-event"];
297
+ app.post("/webhook/github", async (c) => {
298
+ const payload = await c.req.json();
299
+ const event = c.req.header("x-github-event");
298
300
 
299
301
  // Process webhook...
300
- res.json({ received: true });
302
+ return c.json({ received: true });
301
303
  });
302
304
  ```
303
305
 
304
- Custom routes support:
306
+ Custom routes use the underlying [Hono](https://hono.dev/) app returned by `server.getApp()` and support:
305
307
 
306
- - All HTTP methods: GET, POST, PUT, DELETE, PATCH, OPTIONS
308
+ - Hono's HTTP methods: `get`, `post`, `put`, `delete`, `patch`, `options`, and more
307
309
  - Path parameters (`:param`) and wildcards (`*`)
308
310
  - Query string parsing
309
- - JSON and text body parsing
311
+ - JSON, text, form, and other body helpers from `c.req`
310
312
  - Custom status codes and headers
311
- - Authentication via the same `authenticate` function as MCP
312
- - **Public routes** that bypass authentication
313
+ - Middleware and route groups through Hono
313
314
 
314
315
  Routes are matched in the order they are registered, allowing you to define specific routes before catch-all patterns.
315
316
 
316
- ##### Public Routes
317
+ ##### Public and Protected Routes
317
318
 
318
- By default, custom routes require authentication (if configured). You can make routes public by adding the `{ public: true }` option:
319
+ Custom Hono routes are public unless you add your own route middleware or authentication checks. For protected custom routes, put your auth logic in a reusable helper and call it from both FastMCP's `authenticate` option and your Hono route handlers:
319
320
 
320
321
  ```ts
322
+ import type { IncomingMessage } from "node:http";
323
+ import type { Context } from "hono";
324
+ import { FastMCP } from "fastmcp";
325
+
326
+ async function authenticateRequest(request: IncomingMessage) {
327
+ const apiKey = request.headers["x-api-key"];
328
+ return apiKey === "123" ? { userId: "123" } : undefined;
329
+ }
330
+
331
+ const server = new FastMCP({
332
+ name: "My Server",
333
+ version: "1.0.0",
334
+ authenticate: authenticateRequest,
335
+ });
336
+
337
+ const app = server.getApp();
338
+
339
+ async function requireAuth(c: Context) {
340
+ const auth = await authenticateRequest(c.env.incoming);
341
+
342
+ if (!auth) {
343
+ return c.json({ error: "Authentication required" }, 401);
344
+ }
345
+
346
+ return auth;
347
+ }
348
+
321
349
  // Public route - no authentication required
322
- server.addRoute(
323
- "GET",
324
- "/.well-known/openid-configuration",
325
- async (req, res) => {
326
- res.json({
327
- issuer: "https://example.com",
328
- authorization_endpoint: "https://example.com/auth",
329
- token_endpoint: "https://example.com/token",
330
- });
331
- },
332
- { public: true },
333
- );
350
+ app.get("/.well-known/openid-configuration", async (c) => {
351
+ return c.json({
352
+ issuer: "https://example.com",
353
+ authorization_endpoint: "https://example.com/auth",
354
+ token_endpoint: "https://example.com/token",
355
+ });
356
+ });
334
357
 
335
358
  // Private route - requires authentication
336
- server.addRoute("GET", "/api/users", async (req, res) => {
337
- // req.auth contains authenticated user data
338
- res.json({ users: [] });
359
+ app.get("/api/users", async (c) => {
360
+ const auth = await requireAuth(c);
361
+ if (auth instanceof Response) {
362
+ return auth;
363
+ }
364
+
365
+ return c.json({ users: [] });
339
366
  });
340
367
 
341
368
  // Public static files
342
- server.addRoute(
343
- "GET",
344
- "/public/*",
345
- async (req, res) => {
346
- // Serve static files without authentication
347
- res.send(`File: ${req.url}`);
348
- },
349
- { public: true },
350
- );
369
+ app.get("/public/*", async (c) => {
370
+ return c.text(`File: ${c.req.path}`);
371
+ });
351
372
  ```
352
373
 
353
374
  Public routes are perfect for:
package/dist/FastMCP.cjs CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
 
9
9
 
10
- var _chunkMLFG3TOKcjs = require('./chunk-MLFG3TOK.cjs');
10
+ var _chunk553XY3CAcjs = require('./chunk-553XY3CA.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 = _chunkMLFG3TOKcjs.DiscoveryDocumentCache; exports.FastMCP = _chunkMLFG3TOKcjs.FastMCP; exports.FastMCPSession = _chunkMLFG3TOKcjs.FastMCPSession; exports.GitHubProvider = _chunkIX3HKAX4cjs.GitHubProvider; exports.GoogleProvider = _chunkIX3HKAX4cjs.GoogleProvider; exports.OAuthProvider = _chunkIX3HKAX4cjs.OAuthProvider; exports.ServerState = _chunkMLFG3TOKcjs.ServerState; exports.UnexpectedStateError = _chunkMLFG3TOKcjs.UnexpectedStateError; exports.UserError = _chunkMLFG3TOKcjs.UserError; exports.audioContent = _chunkMLFG3TOKcjs.audioContent; exports.getAuthSession = _chunkIX3HKAX4cjs.getAuthSession; exports.imageContent = _chunkMLFG3TOKcjs.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 = _chunk553XY3CAcjs.DiscoveryDocumentCache; exports.FastMCP = _chunk553XY3CAcjs.FastMCP; exports.FastMCPSession = _chunk553XY3CAcjs.FastMCPSession; exports.GitHubProvider = _chunkIX3HKAX4cjs.GitHubProvider; exports.GoogleProvider = _chunkIX3HKAX4cjs.GoogleProvider; exports.OAuthProvider = _chunkIX3HKAX4cjs.OAuthProvider; exports.ServerState = _chunk553XY3CAcjs.ServerState; exports.UnexpectedStateError = _chunk553XY3CAcjs.UnexpectedStateError; exports.UserError = _chunk553XY3CAcjs.UserError; exports.audioContent = _chunk553XY3CAcjs.audioContent; exports.getAuthSession = _chunkIX3HKAX4cjs.getAuthSession; exports.imageContent = _chunk553XY3CAcjs.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
package/dist/FastMCP.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  UserError,
8
8
  audioContent,
9
9
  imageContent
10
- } from "./chunk-YMUXVDGJ.js";
10
+ } from "./chunk-KAGSBU3Z.js";
11
11
  import {
12
12
  AuthProvider,
13
13
  AzureProvider,
@@ -1676,7 +1676,9 @@ var FastMCP = class extends FastMCPEventEmitter {
1676
1676
  if (this.#authenticate) {
1677
1677
  auth = await this.#authenticate(request);
1678
1678
  if (auth === void 0 || auth === null) {
1679
- throw new Error("Authentication required");
1679
+ throw this.#createUnauthorizedResponse(
1680
+ "Authentication required"
1681
+ );
1680
1682
  }
1681
1683
  }
1682
1684
  const sessionId = Array.isArray(request.headers["mcp-session-id"]) ? request.headers["mcp-session-id"][0] : request.headers["mcp-session-id"];
@@ -1795,7 +1797,7 @@ var FastMCP = class extends FastMCPEventEmitter {
1795
1797
  #createSession(auth, sessionId) {
1796
1798
  if (auth && typeof auth === "object" && "authenticated" in auth && !auth.authenticated) {
1797
1799
  const errorMessage = "error" in auth && typeof auth.error === "string" ? auth.error : "Authentication failed";
1798
- throw new Error(errorMessage);
1800
+ throw this.#createUnauthorizedResponse(errorMessage);
1799
1801
  }
1800
1802
  const allowedTools = auth ? this.#tools.filter(
1801
1803
  (tool) => tool.canAccess ? tool.canAccess(auth) : true
@@ -1818,6 +1820,46 @@ var FastMCP = class extends FastMCPEventEmitter {
1818
1820
  version: this.#options.version
1819
1821
  });
1820
1822
  }
1823
+ /**
1824
+ * Builds a 401 Unauthorized HTTP Response for authentication failures.
1825
+ *
1826
+ * Throwing a `Response` (rather than a plain `Error`) guarantees that the
1827
+ * transport (e.g. mcp-proxy) surfaces the correct status code directly,
1828
+ * instead of relying on heuristics that infer the status code from the
1829
+ * error message's text (see https://github.com/punkpeye/fastmcp/issues/180).
1830
+ *
1831
+ * The response body matches the JSON-RPC error envelope FastMCP otherwise
1832
+ * produces, and a `WWW-Authenticate` header is included per RFC 7235 (and
1833
+ * RFC 9728 when protected-resource metadata is configured), so HTTP-aware
1834
+ * clients can distinguish "unauthenticated" from a malformed request.
1835
+ */
1836
+ #createUnauthorizedResponse(message) {
1837
+ const oauth = this.#options.oauth;
1838
+ const resource = _optionalChain([oauth, 'optionalAccess', _48 => _48.enabled]) ? _optionalChain([oauth, 'access', _49 => _49.protectedResource, 'optionalAccess', _50 => _50.resource]) : void 0;
1839
+ const wwwAuthenticateParts = [
1840
+ 'error="invalid_token"',
1841
+ `error_description="${message.replace(/"/g, '\\"')}"`
1842
+ ];
1843
+ if (resource) {
1844
+ wwwAuthenticateParts.push(
1845
+ `resource_metadata="${resource}/.well-known/oauth-protected-resource"`
1846
+ );
1847
+ }
1848
+ return new Response(
1849
+ JSON.stringify({
1850
+ error: { code: -32e3, message },
1851
+ id: null,
1852
+ jsonrpc: "2.0"
1853
+ }),
1854
+ {
1855
+ headers: {
1856
+ "Content-Type": "application/json",
1857
+ "WWW-Authenticate": `Bearer ${wwwAuthenticateParts.join(", ")}`
1858
+ },
1859
+ status: 401
1860
+ }
1861
+ );
1862
+ }
1821
1863
  /**
1822
1864
  * Handles unhandled HTTP requests with health, readiness, OAuth endpoints, and custom routes
1823
1865
  */
@@ -1905,7 +1947,7 @@ var FastMCP = class extends FastMCPEventEmitter {
1905
1947
  }
1906
1948
  }
1907
1949
  const oauthConfig = this.#options.oauth;
1908
- if (_optionalChain([oauthConfig, 'optionalAccess', _48 => _48.enabled]) && req.method === "GET") {
1950
+ if (_optionalChain([oauthConfig, 'optionalAccess', _51 => _51.enabled]) && req.method === "GET") {
1909
1951
  const url2 = new URL(req.url || "", `http://${host}`);
1910
1952
  const authorizationServerMetadataPath = joinPaths(
1911
1953
  "",
@@ -1939,8 +1981,8 @@ var FastMCP = class extends FastMCPEventEmitter {
1939
1981
  }
1940
1982
  }
1941
1983
  }
1942
- const oauthProxy = _optionalChain([oauthConfig, 'optionalAccess', _49 => _49.proxy]);
1943
- if (oauthProxy && _optionalChain([oauthConfig, 'optionalAccess', _50 => _50.enabled])) {
1984
+ const oauthProxy = _optionalChain([oauthConfig, 'optionalAccess', _52 => _52.proxy]);
1985
+ if (oauthProxy && _optionalChain([oauthConfig, 'optionalAccess', _53 => _53.enabled])) {
1944
1986
  const url2 = new URL(req.url || "", `http://${host}`);
1945
1987
  const oauthPath = basePathRelativePath;
1946
1988
  try {
@@ -1957,7 +1999,7 @@ var FastMCP = class extends FastMCPEventEmitter {
1957
1999
  const statusCode = error.statusCode || 400;
1958
2000
  res.writeHead(statusCode, { "Content-Type": "application/json" }).end(
1959
2001
  JSON.stringify(
1960
- _optionalChain([error, 'access', _51 => _51.toJSON, 'optionalCall', _52 => _52()]) || {
2002
+ _optionalChain([error, 'access', _54 => _54.toJSON, 'optionalCall', _55 => _55()]) || {
1961
2003
  error: "invalid_request"
1962
2004
  }
1963
2005
  )
@@ -1984,7 +2026,7 @@ var FastMCP = class extends FastMCPEventEmitter {
1984
2026
  } catch (error) {
1985
2027
  res.writeHead(400, { "Content-Type": "application/json" }).end(
1986
2028
  JSON.stringify(
1987
- _optionalChain([error, 'access', _53 => _53.toJSON, 'optionalCall', _54 => _54()]) || {
2029
+ _optionalChain([error, 'access', _56 => _56.toJSON, 'optionalCall', _57 => _57()]) || {
1988
2030
  error: "invalid_request"
1989
2031
  }
1990
2032
  )
@@ -2006,7 +2048,7 @@ var FastMCP = class extends FastMCPEventEmitter {
2006
2048
  } catch (error) {
2007
2049
  res.writeHead(400, { "Content-Type": "application/json" }).end(
2008
2050
  JSON.stringify(
2009
- _optionalChain([error, 'access', _55 => _55.toJSON, 'optionalCall', _56 => _56()]) || {
2051
+ _optionalChain([error, 'access', _58 => _58.toJSON, 'optionalCall', _59 => _59()]) || {
2010
2052
  error: "server_error"
2011
2053
  }
2012
2054
  )
@@ -2041,7 +2083,7 @@ var FastMCP = class extends FastMCPEventEmitter {
2041
2083
  } catch (error) {
2042
2084
  res.writeHead(400, { "Content-Type": "application/json" }).end(
2043
2085
  JSON.stringify(
2044
- _optionalChain([error, 'access', _57 => _57.toJSON, 'optionalCall', _58 => _58()]) || {
2086
+ _optionalChain([error, 'access', _60 => _60.toJSON, 'optionalCall', _61 => _61()]) || {
2045
2087
  error: "server_error"
2046
2088
  }
2047
2089
  )
@@ -2063,8 +2105,8 @@ var FastMCP = class extends FastMCPEventEmitter {
2063
2105
  const basicAuth = parseBasicAuthHeader(
2064
2106
  req.headers.authorization
2065
2107
  );
2066
- const clientId = _optionalChain([basicAuth, 'optionalAccess', _59 => _59.clientId]) || params.get("client_id") || "";
2067
- const clientSecret = _nullishCoalesce(_nullishCoalesce(_optionalChain([basicAuth, 'optionalAccess', _60 => _60.clientSecret]), () => ( params.get("client_secret"))), () => ( void 0));
2108
+ const clientId = _optionalChain([basicAuth, 'optionalAccess', _62 => _62.clientId]) || params.get("client_id") || "";
2109
+ const clientSecret = _nullishCoalesce(_nullishCoalesce(_optionalChain([basicAuth, 'optionalAccess', _63 => _63.clientSecret]), () => ( params.get("client_secret"))), () => ( void 0));
2068
2110
  let response;
2069
2111
  if (grantType === "authorization_code") {
2070
2112
  response = await oauthProxy.exchangeAuthorizationCode({
@@ -2094,7 +2136,7 @@ var FastMCP = class extends FastMCPEventEmitter {
2094
2136
  const statusCode = error.statusCode || 400;
2095
2137
  res.writeHead(statusCode, { "Content-Type": "application/json" }).end(
2096
2138
  JSON.stringify(
2097
- _optionalChain([error, 'access', _61 => _61.toJSON, 'optionalCall', _62 => _62()]) || {
2139
+ _optionalChain([error, 'access', _64 => _64.toJSON, 'optionalCall', _65 => _65()]) || {
2098
2140
  error: "invalid_request"
2099
2141
  }
2100
2142
  )
@@ -2165,23 +2207,23 @@ var FastMCP = class extends FastMCPEventEmitter {
2165
2207
  const envBasePath = process.env.FASTMCP_BASE_PATH;
2166
2208
  const envStateless = process.env.FASTMCP_STATELESS;
2167
2209
  const envHost = process.env.FASTMCP_HOST;
2168
- const transportType = _optionalChain([overrides, 'optionalAccess', _63 => _63.transportType]) || (transportArg === "http-stream" ? "httpStream" : transportArg) || envTransport || "stdio";
2210
+ const transportType = _optionalChain([overrides, 'optionalAccess', _66 => _66.transportType]) || (transportArg === "http-stream" ? "httpStream" : transportArg) || envTransport || "stdio";
2169
2211
  if (transportType === "httpStream") {
2170
2212
  const port = parseInt(
2171
- _optionalChain([overrides, 'optionalAccess', _64 => _64.httpStream, 'optionalAccess', _65 => _65.port, 'optionalAccess', _66 => _66.toString, 'call', _67 => _67()]) || portArg || envPort || "8080"
2213
+ _optionalChain([overrides, 'optionalAccess', _67 => _67.httpStream, 'optionalAccess', _68 => _68.port, 'optionalAccess', _69 => _69.toString, 'call', _70 => _70()]) || portArg || envPort || "8080"
2172
2214
  );
2173
- const host = _optionalChain([overrides, 'optionalAccess', _68 => _68.httpStream, 'optionalAccess', _69 => _69.host]) || hostArg || envHost || "localhost";
2174
- const endpoint = _optionalChain([overrides, 'optionalAccess', _70 => _70.httpStream, 'optionalAccess', _71 => _71.endpoint]) || endpointArg || envEndpoint || "/mcp";
2215
+ const host = _optionalChain([overrides, 'optionalAccess', _71 => _71.httpStream, 'optionalAccess', _72 => _72.host]) || hostArg || envHost || "localhost";
2216
+ const endpoint = _optionalChain([overrides, 'optionalAccess', _73 => _73.httpStream, 'optionalAccess', _74 => _74.endpoint]) || endpointArg || envEndpoint || "/mcp";
2175
2217
  const basePath = normalizeBasePath(
2176
- _optionalChain([overrides, 'optionalAccess', _72 => _72.httpStream, 'optionalAccess', _73 => _73.basePath]) || basePathArg || envBasePath
2218
+ _optionalChain([overrides, 'optionalAccess', _75 => _75.httpStream, 'optionalAccess', _76 => _76.basePath]) || basePathArg || envBasePath
2177
2219
  );
2178
- const enableJsonResponse = _optionalChain([overrides, 'optionalAccess', _74 => _74.httpStream, 'optionalAccess', _75 => _75.enableJsonResponse]) || false;
2179
- const stateless = _optionalChain([overrides, 'optionalAccess', _76 => _76.httpStream, 'optionalAccess', _77 => _77.stateless]) || statelessArg === "true" || envStateless === "true" || false;
2180
- const cors = _optionalChain([overrides, 'optionalAccess', _78 => _78.httpStream, 'optionalAccess', _79 => _79.cors]);
2181
- const eventStore = _optionalChain([overrides, 'optionalAccess', _80 => _80.httpStream, 'optionalAccess', _81 => _81.eventStore]);
2182
- const sslCa = _optionalChain([overrides, 'optionalAccess', _82 => _82.httpStream, 'optionalAccess', _83 => _83.sslCa]);
2183
- const sslCert = _optionalChain([overrides, 'optionalAccess', _84 => _84.httpStream, 'optionalAccess', _85 => _85.sslCert]);
2184
- const sslKey = _optionalChain([overrides, 'optionalAccess', _86 => _86.httpStream, 'optionalAccess', _87 => _87.sslKey]);
2220
+ const enableJsonResponse = _optionalChain([overrides, 'optionalAccess', _77 => _77.httpStream, 'optionalAccess', _78 => _78.enableJsonResponse]) || false;
2221
+ const stateless = _optionalChain([overrides, 'optionalAccess', _79 => _79.httpStream, 'optionalAccess', _80 => _80.stateless]) || statelessArg === "true" || envStateless === "true" || false;
2222
+ const cors = _optionalChain([overrides, 'optionalAccess', _81 => _81.httpStream, 'optionalAccess', _82 => _82.cors]);
2223
+ const eventStore = _optionalChain([overrides, 'optionalAccess', _83 => _83.httpStream, 'optionalAccess', _84 => _84.eventStore]);
2224
+ const sslCa = _optionalChain([overrides, 'optionalAccess', _85 => _85.httpStream, 'optionalAccess', _86 => _86.sslCa]);
2225
+ const sslCert = _optionalChain([overrides, 'optionalAccess', _87 => _87.httpStream, 'optionalAccess', _88 => _88.sslCert]);
2226
+ const sslKey = _optionalChain([overrides, 'optionalAccess', _89 => _89.httpStream, 'optionalAccess', _90 => _90.sslKey]);
2185
2227
  return {
2186
2228
  httpStream: {
2187
2229
  basePath,
@@ -2254,4 +2296,4 @@ var FastMCP = class extends FastMCPEventEmitter {
2254
2296
 
2255
2297
 
2256
2298
  exports.DiscoveryDocumentCache = DiscoveryDocumentCache; exports.imageContent = imageContent; exports.audioContent = audioContent; exports.UnexpectedStateError = UnexpectedStateError; exports.UserError = UserError; exports.ServerState = ServerState; exports.FastMCPSession = FastMCPSession; exports.FastMCP = FastMCP;
2257
- //# sourceMappingURL=chunk-MLFG3TOK.cjs.map
2299
+ //# sourceMappingURL=chunk-553XY3CA.cjs.map