fastmcp 3.28.0 → 3.30.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
@@ -1230,90 +1230,193 @@ server.addPrompt({
1230
1230
 
1231
1231
  ### Authentication
1232
1232
 
1233
- FastMCP supports session-based authentication, allowing you to secure your server and control access to its features.
1233
+ FastMCP supports OAuth 2.1 authentication with pre-configured providers, allowing you to secure your server with minimal setup.
1234
1234
 
1235
- > [!NOTE]
1236
- > For more granular control over which tools are available to authenticated users, see the [Tool Authorization](#tool-authorization) section.
1237
-
1238
- To enable authentication, provide an `authenticate` function in the server options. This function receives the incoming HTTP request and should return a promise that resolves with the authentication context.
1235
+ #### OAuth with Pre-configured Providers
1239
1236
 
1240
- If authentication fails, the function should throw a `Response` object, which will be sent to the client.
1237
+ Use the `auth` option with a provider to enable OAuth authentication:
1241
1238
 
1242
1239
  ```ts
1240
+ import { FastMCP, getAuthSession, GoogleProvider, requireAuth } from "fastmcp";
1241
+
1243
1242
  const server = new FastMCP({
1243
+ auth: new GoogleProvider({
1244
+ baseUrl: "https://your-server.com",
1245
+ clientId: process.env.GOOGLE_CLIENT_ID!,
1246
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
1247
+ }),
1244
1248
  name: "My Server",
1245
1249
  version: "1.0.0",
1246
- authenticate: (request) => {
1247
- const apiKey = request.headers["x-api-key"];
1248
-
1249
- if (apiKey !== "123") {
1250
- throw new Response(null, {
1251
- status: 401,
1252
- statusText: "Unauthorized",
1253
- });
1254
- }
1250
+ });
1255
1251
 
1256
- // Whatever you return here will be accessible in the `context.session` object.
1257
- return {
1258
- id: 1,
1259
- };
1252
+ server.addTool({
1253
+ canAccess: requireAuth,
1254
+ description: "Get user profile",
1255
+ execute: async (_args, { session }) => {
1256
+ const { accessToken } = getAuthSession(session);
1257
+ const response = await fetch(
1258
+ "https://www.googleapis.com/oauth2/v2/userinfo",
1259
+ {
1260
+ headers: { Authorization: `Bearer ${accessToken}` },
1261
+ },
1262
+ );
1263
+ return JSON.stringify(await response.json());
1260
1264
  },
1265
+ name: "get-profile",
1261
1266
  });
1262
1267
  ```
1263
1268
 
1264
- Now you can access the authenticated session data in your tools:
1269
+ **Available Providers:**
1270
+
1271
+ | Provider | Import | Use Case |
1272
+ | :--------------- | :-------- | :--------------------- |
1273
+ | `GoogleProvider` | `fastmcp` | Google OAuth |
1274
+ | `GitHubProvider` | `fastmcp` | GitHub OAuth |
1275
+ | `AzureProvider` | `fastmcp` | Azure/Entra ID |
1276
+ | `OAuthProvider` | `fastmcp` | Any OAuth 2.0 provider |
1277
+
1278
+ **Generic OAuth Provider** (for SAP, Auth0, Okta, etc.):
1265
1279
 
1266
1280
  ```ts
1267
- server.addTool({
1268
- name: "sayHello",
1269
- execute: async (args, { session }) => {
1270
- return `Hello, ${session.id}!`;
1271
- },
1281
+ import { FastMCP, OAuthProvider } from "fastmcp";
1282
+
1283
+ const server = new FastMCP({
1284
+ auth: new OAuthProvider({
1285
+ authorizationEndpoint: process.env.OAUTH_AUTH_ENDPOINT!,
1286
+ baseUrl: "https://your-server.com",
1287
+ clientId: process.env.OAUTH_CLIENT_ID!,
1288
+ clientSecret: process.env.OAUTH_CLIENT_SECRET!,
1289
+ scopes: ["openid", "profile"],
1290
+ tokenEndpoint: process.env.OAUTH_TOKEN_ENDPOINT!,
1291
+ }),
1292
+ name: "My Server",
1293
+ version: "1.0.0",
1272
1294
  });
1273
1295
  ```
1274
1296
 
1275
1297
  #### Tool Authorization
1276
1298
 
1277
- You can control which tools are available to authenticated users by adding an optional `canAccess` function to a tool's definition. This function receives the authentication context and should return `true` if the user is allowed to access the tool.
1299
+ Control tool access using the `canAccess` property with built-in helper functions:
1278
1300
 
1279
- If `canAccess` is not provided, the tool is accessible to all authenticated users by default. If no authentication is configured on the server, all tools are available to all clients.
1301
+ ```ts
1302
+ import {
1303
+ requireAuth,
1304
+ requireScopes,
1305
+ requireRole,
1306
+ requireAll,
1307
+ requireAny,
1308
+ getAuthSession,
1309
+ } from "fastmcp";
1310
+
1311
+ // Require any authenticated user
1312
+ server.addTool({
1313
+ canAccess: requireAuth,
1314
+ name: "user-tool",
1315
+ // ...
1316
+ });
1280
1317
 
1281
- **Example:**
1318
+ // Require specific OAuth scopes
1319
+ server.addTool({
1320
+ canAccess: requireScopes("read:user", "write:data"),
1321
+ name: "scoped-tool",
1322
+ // ...
1323
+ });
1324
+
1325
+ // Require specific role
1326
+ server.addTool({
1327
+ canAccess: requireRole("admin"),
1328
+ name: "admin-tool",
1329
+ // ...
1330
+ });
1331
+
1332
+ // Combine with AND logic
1333
+ server.addTool({
1334
+ canAccess: requireAll(requireAuth, requireRole("admin")),
1335
+ name: "admin-only",
1336
+ // ...
1337
+ });
1338
+
1339
+ // Combine with OR logic
1340
+ server.addTool({
1341
+ canAccess: requireAny(requireRole("admin"), requireRole("moderator")),
1342
+ name: "staff-tool",
1343
+ // ...
1344
+ });
1345
+ ```
1346
+
1347
+ **Custom Authorization:**
1348
+
1349
+ For custom logic, pass a function directly:
1282
1350
 
1283
1351
  ```typescript
1284
- const server = new FastMCP<{ role: "admin" | "user" }>({
1285
- authenticate: async (request) => {
1286
- const role = request.headers["x-role"] as string;
1287
- return { role: role === "admin" ? "admin" : "user" };
1352
+ server.addTool({
1353
+ name: "custom-auth-tool",
1354
+ canAccess: (auth) =>
1355
+ auth?.role === "admin" && auth?.department === "engineering",
1356
+ execute: async () => "Access granted!",
1357
+ });
1358
+ ```
1359
+
1360
+ **Extracting Session Data:**
1361
+
1362
+ Use `getAuthSession` for type-safe access to the OAuth session in your tool execute functions:
1363
+
1364
+ ```typescript
1365
+ import { getAuthSession, GoogleSession } from "fastmcp";
1366
+
1367
+ server.addTool({
1368
+ canAccess: requireAuth,
1369
+ name: "get-profile",
1370
+ execute: async (_args, { session }) => {
1371
+ // Type-safe destructuring (throws if not authenticated)
1372
+ const { accessToken } = getAuthSession(session);
1373
+
1374
+ // Or with provider-specific typing:
1375
+ // const { accessToken } = getAuthSession<GoogleSession>(session);
1376
+
1377
+ const response = await fetch("https://api.example.com/user", {
1378
+ headers: { Authorization: `Bearer ${accessToken}` },
1379
+ });
1380
+ return JSON.stringify(await response.json());
1288
1381
  },
1382
+ });
1383
+ ```
1384
+
1385
+ > **Note:** You can also access `session.accessToken` directly, but you must handle the case where `session` is undefined. The `getAuthSession` helper throws a clear error if the session is not authenticated, making it safer when used with `canAccess: requireAuth`.
1386
+
1387
+ #### Custom Authentication
1388
+
1389
+ For non-OAuth scenarios (API keys, custom tokens), use the `authenticate` option:
1390
+
1391
+ ```ts
1392
+ const server = new FastMCP({
1289
1393
  name: "My Server",
1290
1394
  version: "1.0.0",
1291
- });
1395
+ authenticate: (request) => {
1396
+ const apiKey = request.headers["x-api-key"];
1292
1397
 
1293
- server.addTool({
1294
- name: "admin-dashboard",
1295
- description: "An admin-only tool",
1296
- // Only users with the 'admin' role can see and execute this tool
1297
- canAccess: (auth) => auth?.role === "admin",
1298
- execute: async () => {
1299
- return "Welcome to the admin dashboard!";
1398
+ if (apiKey !== "123") {
1399
+ throw new Response(null, {
1400
+ status: 401,
1401
+ statusText: "Unauthorized",
1402
+ });
1403
+ }
1404
+
1405
+ return { id: 1, role: "user" };
1300
1406
  },
1301
1407
  });
1302
1408
 
1303
1409
  server.addTool({
1304
- name: "public-info",
1305
- description: "A tool available to everyone",
1306
- execute: async () => {
1307
- return "This is public information.";
1410
+ name: "sayHello",
1411
+ execute: async (args, { session }) => {
1412
+ return `Hello, ${session.id}!`;
1308
1413
  },
1309
1414
  });
1310
1415
  ```
1311
1416
 
1312
- In this example, only clients authenticating with the `admin` role will be able to list or call the `admin-dashboard` tool. The `public-info` tool will be available to all authenticated users.
1313
-
1314
1417
  #### OAuth Proxy
1315
1418
 
1316
- FastMCP includes a built-in **OAuth Proxy** that acts as a secure intermediary between MCP clients and upstream OAuth providers. The proxy handles the complete OAuth 2.1 authorization flow, including Dynamic Client Registration (DCR), PKCE, consent management, and token management with encryption and token swap patterns enabled by default.
1419
+ The `auth` option uses FastMCP's built-in **OAuth Proxy** that acts as a secure intermediary between MCP clients and upstream OAuth providers. The proxy handles the complete OAuth 2.1 authorization flow, including Dynamic Client Registration (DCR), PKCE, consent management, and token management with encryption and token swap patterns enabled by default.
1317
1420
 
1318
1421
  **Key Features:**
1319
1422
 
@@ -1325,6 +1428,34 @@ FastMCP includes a built-in **OAuth Proxy** that acts as a secure intermediary b
1325
1428
 
1326
1429
  **Quick Start:**
1327
1430
 
1431
+ ```ts
1432
+ import { FastMCP, getAuthSession, GoogleProvider, requireAuth } from "fastmcp";
1433
+
1434
+ const server = new FastMCP({
1435
+ auth: new GoogleProvider({
1436
+ baseUrl: "https://your-server.com",
1437
+ clientId: process.env.GOOGLE_CLIENT_ID!,
1438
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
1439
+ }),
1440
+ name: "My Server",
1441
+ version: "1.0.0",
1442
+ });
1443
+
1444
+ server.addTool({
1445
+ canAccess: requireAuth,
1446
+ name: "protected-tool",
1447
+ execute: async (_args, { session }) => {
1448
+ const { accessToken } = getAuthSession(session);
1449
+ // Use accessToken to call upstream APIs
1450
+ return "Authenticated!";
1451
+ },
1452
+ });
1453
+ ```
1454
+
1455
+ **Advanced Configuration:**
1456
+
1457
+ For more control over OAuth behavior, you can use the `oauth` option directly:
1458
+
1328
1459
  ```ts
1329
1460
  import { FastMCP } from "fastmcp";
1330
1461
  import { GoogleProvider } from "fastmcp/auth";
@@ -1341,7 +1472,7 @@ const server = new FastMCP({
1341
1472
  oauth: {
1342
1473
  enabled: true,
1343
1474
  authorizationServer: authProxy.getAuthorizationServerMetadata(),
1344
- proxy: authProxy, // Routes automatically registered!
1475
+ proxy: authProxy,
1345
1476
  },
1346
1477
  });
1347
1478
  ```
package/dist/FastMCP.cjs CHANGED
@@ -1,4 +1,18 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/FastMCP.ts
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+ var _chunk3MN6Z6V4cjs = require('./chunk-3MN6Z6V4.cjs');
14
+
15
+ // src/FastMCP.ts
2
16
  var _indexjs = require('@modelcontextprotocol/sdk/server/index.js');
3
17
  var _stdiojs = require('@modelcontextprotocol/sdk/server/stdio.js');
4
18
 
@@ -560,6 +574,13 @@ ${error instanceof Error ? error.stack : JSON.stringify(error)}`
560
574
  );
561
575
  }
562
576
  }
577
+ /**
578
+ * Update the session's authentication context.
579
+ * Called by mcp-proxy when a new token is validated on subsequent requests.
580
+ */
581
+ updateAuth(auth) {
582
+ this.#auth = auth;
583
+ }
563
584
  waitForReady() {
564
585
  if (this.isReady) {
565
586
  return Promise.resolve();
@@ -1183,8 +1204,22 @@ var FastMCP = class extends FastMCPEventEmitter {
1183
1204
  super();
1184
1205
  this.options = options;
1185
1206
  this.#options = options;
1186
- this.#authenticate = options.authenticate;
1187
1207
  this.#logger = options.logger || console;
1208
+ if (options.auth) {
1209
+ if (!options.authenticate) {
1210
+ this.#authenticate = ((request) => options.auth.authenticate(request));
1211
+ } else {
1212
+ this.#authenticate = options.authenticate;
1213
+ }
1214
+ if (!options.oauth) {
1215
+ this.#options = {
1216
+ ...options,
1217
+ oauth: options.auth.getOAuthConfig()
1218
+ };
1219
+ }
1220
+ } else {
1221
+ this.#authenticate = options.authenticate;
1222
+ }
1188
1223
  }
1189
1224
  get serverState() {
1190
1225
  return this.#serverState;
@@ -1958,5 +1993,16 @@ var FastMCP = class extends FastMCPEventEmitter {
1958
1993
 
1959
1994
 
1960
1995
 
1961
- exports.DiscoveryDocumentCache = DiscoveryDocumentCache; exports.FastMCP = FastMCP; exports.FastMCPSession = FastMCPSession; exports.ServerState = ServerState; exports.UnexpectedStateError = UnexpectedStateError; exports.UserError = UserError; exports.audioContent = audioContent; exports.imageContent = imageContent;
1996
+
1997
+
1998
+
1999
+
2000
+
2001
+
2002
+
2003
+
2004
+
2005
+
2006
+
2007
+ exports.AuthProvider = _chunk3MN6Z6V4cjs.AuthProvider; exports.AzureProvider = _chunk3MN6Z6V4cjs.AzureProvider; exports.DiscoveryDocumentCache = DiscoveryDocumentCache; exports.FastMCP = FastMCP; exports.FastMCPSession = FastMCPSession; exports.GitHubProvider = _chunk3MN6Z6V4cjs.GitHubProvider; exports.GoogleProvider = _chunk3MN6Z6V4cjs.GoogleProvider; exports.OAuthProvider = _chunk3MN6Z6V4cjs.OAuthProvider; exports.ServerState = ServerState; exports.UnexpectedStateError = UnexpectedStateError; exports.UserError = UserError; exports.audioContent = audioContent; exports.getAuthSession = _chunk3MN6Z6V4cjs.getAuthSession; exports.imageContent = imageContent; exports.requireAll = _chunk3MN6Z6V4cjs.requireAll; exports.requireAny = _chunk3MN6Z6V4cjs.requireAny; exports.requireAuth = _chunk3MN6Z6V4cjs.requireAuth; exports.requireRole = _chunk3MN6Z6V4cjs.requireRole; exports.requireScopes = _chunk3MN6Z6V4cjs.requireScopes;
1962
2008
  //# sourceMappingURL=FastMCP.cjs.map