fastmcp 3.6.2 → 3.8.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
@@ -1164,6 +1164,110 @@ server.addTool({
1164
1164
  });
1165
1165
  ```
1166
1166
 
1167
+ #### OAuth Support
1168
+
1169
+ FastMCP includes built-in support for OAuth discovery endpoints, supporting both **MCP Specification 2025-03-26** and **MCP Specification 2025-06-18** for OAuth integration. This makes it easy to integrate with OAuth authorization flows by providing standard discovery endpoints that comply with RFC 8414 (OAuth 2.0 Authorization Server Metadata) and RFC 9470 (OAuth 2.0 Protected Resource Metadata):
1170
+
1171
+ ```ts
1172
+ import { FastMCP } from "fastmcp";
1173
+ import { buildGetJwks } from "get-jwks";
1174
+ import fastJwt from "fast-jwt";
1175
+
1176
+ const server = new FastMCP({
1177
+ name: "My Server",
1178
+ version: "1.0.0",
1179
+ oauth: {
1180
+ enabled: true,
1181
+ authorizationServer: {
1182
+ issuer: "https://auth.example.com",
1183
+ authorizationEndpoint: "https://auth.example.com/oauth/authorize",
1184
+ tokenEndpoint: "https://auth.example.com/oauth/token",
1185
+ jwksUri: "https://auth.example.com/.well-known/jwks.json",
1186
+ responseTypesSupported: ["code"],
1187
+ },
1188
+ protectedResource: {
1189
+ resource: "mcp://my-server",
1190
+ authorizationServers: ["https://auth.example.com"],
1191
+ },
1192
+ },
1193
+ authenticate: async (request) => {
1194
+ const authHeader = request.headers.authorization;
1195
+
1196
+ if (!authHeader?.startsWith("Bearer ")) {
1197
+ throw new Response(null, {
1198
+ status: 401,
1199
+ statusText: "Missing or invalid authorization header",
1200
+ });
1201
+ }
1202
+
1203
+ const token = authHeader.slice(7); // Remove 'Bearer ' prefix
1204
+
1205
+ // Validate OAuth JWT access token using OpenID Connect discovery
1206
+ try {
1207
+ // TODO: Cache the discovery document to avoid repeated requests
1208
+ // Discover OAuth/OpenID configuration from well-known endpoint
1209
+ const discoveryUrl =
1210
+ "https://auth.example.com/.well-known/openid-configuration";
1211
+ // Alternative: Use OAuth authorization server metadata endpoint
1212
+ // const discoveryUrl = 'https://auth.example.com/.well-known/oauth-authorization-server';
1213
+
1214
+ const discoveryResponse = await fetch(discoveryUrl);
1215
+ if (!discoveryResponse.ok) {
1216
+ throw new Error("Failed to fetch OAuth discovery document");
1217
+ }
1218
+
1219
+ const config = await discoveryResponse.json();
1220
+ const jwksUri = config.jwks_uri;
1221
+ const issuer = config.issuer;
1222
+
1223
+ // Create JWKS client for token verification using discovered endpoint
1224
+ const getJwks = buildGetJwks({
1225
+ jwksUrl: jwksUri,
1226
+ cache: true,
1227
+ rateLimit: true,
1228
+ });
1229
+
1230
+ // Create JWT verifier with JWKS and discovered issuer
1231
+ const verify = fastJwt.createVerifier({
1232
+ key: async (token) => {
1233
+ const { header } = fastJwt.decode(token, { complete: true });
1234
+ const jwk = await getJwks.getJwk({
1235
+ kid: header.kid,
1236
+ alg: header.alg,
1237
+ });
1238
+ return jwk;
1239
+ },
1240
+ algorithms: ["RS256", "ES256"],
1241
+ issuer: issuer,
1242
+ audience: "mcp://my-server",
1243
+ });
1244
+
1245
+ // Verify the JWT token
1246
+ const payload = await verify(token);
1247
+
1248
+ return {
1249
+ userId: payload.sub,
1250
+ scope: payload.scope,
1251
+ email: payload.email,
1252
+ // Include other claims as needed
1253
+ };
1254
+ } catch (error) {
1255
+ throw new Response(null, {
1256
+ status: 401,
1257
+ statusText: "Invalid OAuth token",
1258
+ });
1259
+ }
1260
+ },
1261
+ });
1262
+ ```
1263
+
1264
+ This configuration automatically exposes OAuth discovery endpoints:
1265
+
1266
+ - `/.well-known/oauth-authorization-server` - Authorization server metadata (RFC 8414)
1267
+ - `/.well-known/oauth-protected-resource` - Protected resource metadata (RFC 9470)
1268
+
1269
+ For JWT token validation, you can use libraries like [`get-jwks`](https://github.com/nearform/get-jwks) and [`@fastify/jwt`](https://github.com/fastify/fastify-jwt) for OAuth JWT tokens.
1270
+
1167
1271
  #### Passing Headers Through Context
1168
1272
 
1169
1273
  If you are exposing your MCP server via HTTP, you may wish to allow clients to supply sensitive keys via headers, which can then be passed along to APIs that your tools interact with, allowing each client to supply their own API keys. This can be done by capturing the HTTP headers in the `authenticate` section and storing them in the session to be referenced by the tools later.
package/dist/FastMCP.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { EventStore } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
2
3
  import { RequestOptions } from '@modelcontextprotocol/sdk/shared/protocol.js';
3
4
  import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
4
5
  import { ResourceLink, Root, ClientCapabilities, GetPromptResult, CreateMessageRequestSchema } from '@modelcontextprotocol/sdk/types.js';
@@ -116,44 +117,44 @@ type Completion = {
116
117
  total?: number;
117
118
  values: string[];
118
119
  };
119
- type ArgumentValueCompleter = (value: string) => Promise<Completion>;
120
- type InputPrompt<Arguments extends InputPromptArgument[] = InputPromptArgument[], Args = PromptArgumentsToObject<Arguments>> = {
121
- arguments?: InputPromptArgument[];
120
+ type ArgumentValueCompleter<T extends FastMCPSessionAuth = FastMCPSessionAuth> = (value: string, auth?: T) => Promise<Completion>;
121
+ type InputPrompt<T extends FastMCPSessionAuth = FastMCPSessionAuth, Arguments extends InputPromptArgument<T>[] = InputPromptArgument<T>[], Args = PromptArgumentsToObject<Arguments>> = {
122
+ arguments?: InputPromptArgument<T>[];
122
123
  description?: string;
123
- load: (args: Args) => Promise<PromptResult>;
124
+ load: (args: Args, auth?: T) => Promise<PromptResult>;
124
125
  name: string;
125
126
  };
126
- type InputPromptArgument = Readonly<{
127
- complete?: ArgumentValueCompleter;
127
+ type InputPromptArgument<T extends FastMCPSessionAuth = FastMCPSessionAuth> = Readonly<{
128
+ complete?: ArgumentValueCompleter<T>;
128
129
  description?: string;
129
130
  enum?: string[];
130
131
  name: string;
131
132
  required?: boolean;
132
133
  }>;
133
- type InputResourceTemplate<Arguments extends ResourceTemplateArgument[] = ResourceTemplateArgument[]> = {
134
+ type InputResourceTemplate<T extends FastMCPSessionAuth, Arguments extends InputResourceTemplateArgument<T>[] = InputResourceTemplateArgument<T>[]> = {
134
135
  arguments: Arguments;
135
136
  description?: string;
136
- load: (args: ResourceTemplateArgumentsToObject<Arguments>) => Promise<ResourceResult | ResourceResult[]>;
137
+ load: (args: ResourceTemplateArgumentsToObject<Arguments>, auth?: T) => Promise<ResourceResult | ResourceResult[]>;
137
138
  mimeType?: string;
138
139
  name: string;
139
140
  uriTemplate: string;
140
141
  };
141
- type InputResourceTemplateArgument = Readonly<{
142
- complete?: ArgumentValueCompleter;
142
+ type InputResourceTemplateArgument<T extends FastMCPSessionAuth = FastMCPSessionAuth> = Readonly<{
143
+ complete?: ArgumentValueCompleter<T>;
143
144
  description?: string;
144
145
  name: string;
145
146
  required?: boolean;
146
147
  }>;
147
148
  type LoggingLevel = "alert" | "critical" | "debug" | "emergency" | "error" | "info" | "notice" | "warning";
148
- type Prompt<Arguments extends PromptArgument[] = PromptArgument[], Args = PromptArgumentsToObject<Arguments>> = {
149
- arguments?: PromptArgument[];
150
- complete?: (name: string, value: string) => Promise<Completion>;
149
+ type Prompt<T extends FastMCPSessionAuth = FastMCPSessionAuth, Arguments extends PromptArgument<T>[] = PromptArgument<T>[], Args = PromptArgumentsToObject<Arguments>> = {
150
+ arguments?: PromptArgument<T>[];
151
+ complete?: (name: string, value: string, auth?: T) => Promise<Completion>;
151
152
  description?: string;
152
- load: (args: Args) => Promise<PromptResult>;
153
+ load: (args: Args, auth?: T) => Promise<PromptResult>;
153
154
  name: string;
154
155
  };
155
- type PromptArgument = Readonly<{
156
- complete?: ArgumentValueCompleter;
156
+ type PromptArgument<T extends FastMCPSessionAuth = FastMCPSessionAuth> = Readonly<{
157
+ complete?: ArgumentValueCompleter<T>;
157
158
  description?: string;
158
159
  enum?: string[];
159
160
  name: string;
@@ -168,10 +169,10 @@ type PromptArgumentsToObject<T extends {
168
169
  }>["required"] extends true ? string : string | undefined;
169
170
  };
170
171
  type PromptResult = Pick<GetPromptResult, "messages"> | string;
171
- type Resource = {
172
- complete?: (name: string, value: string) => Promise<Completion>;
172
+ type Resource<T extends FastMCPSessionAuth> = {
173
+ complete?: (name: string, value: string, auth?: T) => Promise<Completion>;
173
174
  description?: string;
174
- load: () => Promise<ResourceResult | ResourceResult[]>;
175
+ load: (auth?: T) => Promise<ResourceResult | ResourceResult[]>;
175
176
  mimeType?: string;
176
177
  name: string;
177
178
  uri: string;
@@ -185,17 +186,17 @@ type ResourceResult = {
185
186
  text: string;
186
187
  uri?: string;
187
188
  };
188
- type ResourceTemplate<Arguments extends ResourceTemplateArgument[] = ResourceTemplateArgument[]> = {
189
+ type ResourceTemplate<T extends FastMCPSessionAuth, Arguments extends ResourceTemplateArgument<T>[] = ResourceTemplateArgument<T>[]> = {
189
190
  arguments: Arguments;
190
- complete?: (name: string, value: string) => Promise<Completion>;
191
+ complete?: (name: string, value: string, auth?: T) => Promise<Completion>;
191
192
  description?: string;
192
- load: (args: ResourceTemplateArgumentsToObject<Arguments>) => Promise<ResourceResult | ResourceResult[]>;
193
+ load: (args: ResourceTemplateArgumentsToObject<Arguments>, auth?: T) => Promise<ResourceResult | ResourceResult[]>;
193
194
  mimeType?: string;
194
195
  name: string;
195
196
  uriTemplate: string;
196
197
  };
197
- type ResourceTemplateArgument = Readonly<{
198
- complete?: ArgumentValueCompleter;
198
+ type ResourceTemplateArgument<T extends FastMCPSessionAuth = FastMCPSessionAuth> = Readonly<{
199
+ complete?: ArgumentValueCompleter<T>;
199
200
  description?: string;
200
201
  name: string;
201
202
  required?: boolean;
@@ -247,6 +248,67 @@ type ServerOptions<T extends FastMCPSessionAuth> = {
247
248
  };
248
249
  instructions?: string;
249
250
  name: string;
251
+ /**
252
+ * Configuration for OAuth well-known discovery endpoints that can be exposed
253
+ * when the server is running using HTTP-based transports (SSE or HTTP Stream).
254
+ * When enabled, the server will respond to requests for OAuth discovery endpoints
255
+ * with the configured metadata.
256
+ *
257
+ * The endpoints are only added when the server is started with
258
+ * `transportType: "httpStream"` – they are ignored for the stdio transport.
259
+ * Both SSE and HTTP Stream transports support OAuth endpoints.
260
+ */
261
+ oauth?: {
262
+ /**
263
+ * OAuth Authorization Server metadata for /.well-known/oauth-authorization-server
264
+ *
265
+ * This endpoint follows RFC 8414 (OAuth 2.0 Authorization Server Metadata)
266
+ * and provides metadata about the OAuth 2.0 authorization server.
267
+ *
268
+ * Required by MCP Specification 2025-03-26
269
+ */
270
+ authorizationServer?: {
271
+ authorizationEndpoint: string;
272
+ codeChallengeMethodsSupported?: string[];
273
+ dpopSigningAlgValuesSupported?: string[];
274
+ grantTypesSupported?: string[];
275
+ introspectionEndpoint?: string;
276
+ issuer: string;
277
+ jwksUri?: string;
278
+ opPolicyUri?: string;
279
+ opTosUri?: string;
280
+ registrationEndpoint?: string;
281
+ responseModesSupported?: string[];
282
+ responseTypesSupported: string[];
283
+ revocationEndpoint?: string;
284
+ scopesSupported?: string[];
285
+ serviceDocumentation?: string;
286
+ tokenEndpoint: string;
287
+ tokenEndpointAuthMethodsSupported?: string[];
288
+ tokenEndpointAuthSigningAlgValuesSupported?: string[];
289
+ uiLocalesSupported?: string[];
290
+ };
291
+ /**
292
+ * Whether OAuth discovery endpoints should be enabled.
293
+ */
294
+ enabled: boolean;
295
+ /**
296
+ * OAuth Protected Resource metadata for /.well-known/oauth-protected-resource
297
+ *
298
+ * This endpoint follows RFC 9470 (OAuth 2.0 Protected Resource Metadata)
299
+ * and provides metadata about the OAuth 2.0 protected resource.
300
+ *
301
+ * Required by MCP Specification 2025-06-18
302
+ */
303
+ protectedResource?: {
304
+ authorizationServers: string[];
305
+ bearerMethodsSupported?: string[];
306
+ jwksUri?: string;
307
+ resource: string;
308
+ resourceDocumentation?: string;
309
+ resourcePolicyUri?: string;
310
+ };
311
+ };
250
312
  ping?: {
251
313
  /**
252
314
  * Whether ping should be enabled by default.
@@ -327,6 +389,7 @@ type ToolAnnotations = {
327
389
  declare const FastMCPSessionEventEmitterBase: {
328
390
  new (): StrictEventEmitter<EventEmitter, FastMCPSessionEvents>;
329
391
  };
392
+ type Authenticate<T> = (request: http.IncomingMessage) => Promise<T>;
330
393
  type FastMCPSessionAuth = Record<string, unknown> | undefined;
331
394
  declare class FastMCPSessionEventEmitter extends FastMCPSessionEventEmitterBase {
332
395
  }
@@ -342,9 +405,9 @@ declare class FastMCPSession<T extends FastMCPSessionAuth = FastMCPSessionAuth>
342
405
  instructions?: string;
343
406
  name: string;
344
407
  ping?: ServerOptions<T>["ping"];
345
- prompts: Prompt[];
346
- resources: Resource[];
347
- resourcesTemplates: InputResourceTemplate[];
408
+ prompts: Prompt<T>[];
409
+ resources: Resource<T>[];
410
+ resourcesTemplates: InputResourceTemplate<T>[];
348
411
  roots?: ServerOptions<T>["roots"];
349
412
  tools: Tool<T>[];
350
413
  transportType?: "httpStream" | "stdio";
@@ -369,10 +432,9 @@ declare class FastMCPSession<T extends FastMCPSessionAuth = FastMCPSessionAuth>
369
432
  declare const FastMCPEventEmitterBase: {
370
433
  new (): StrictEventEmitter<EventEmitter, FastMCPEvents<FastMCPSessionAuth>>;
371
434
  };
372
- type Authenticate<T> = (request: http.IncomingMessage) => Promise<T>;
373
435
  declare class FastMCPEventEmitter extends FastMCPEventEmitterBase {
374
436
  }
375
- declare class FastMCP<T extends Record<string, unknown> | undefined = undefined> extends FastMCPEventEmitter {
437
+ declare class FastMCP<T extends FastMCPSessionAuth = FastMCPSessionAuth> extends FastMCPEventEmitter {
376
438
  #private;
377
439
  options: ServerOptions<T>;
378
440
  get sessions(): FastMCPSession<T>[];
@@ -380,15 +442,15 @@ declare class FastMCP<T extends Record<string, unknown> | undefined = undefined>
380
442
  /**
381
443
  * Adds a prompt to the server.
382
444
  */
383
- addPrompt<const Args extends InputPromptArgument[]>(prompt: InputPrompt<Args>): void;
445
+ addPrompt<const Args extends InputPromptArgument<T>[]>(prompt: InputPrompt<T, Args>): void;
384
446
  /**
385
447
  * Adds a resource to the server.
386
448
  */
387
- addResource(resource: Resource): void;
449
+ addResource(resource: Resource<T>): void;
388
450
  /**
389
451
  * Adds a resource template to the server.
390
452
  */
391
- addResourceTemplate<const Args extends InputResourceTemplateArgument[]>(resource: InputResourceTemplate<Args>): void;
453
+ addResourceTemplate<const Args extends InputResourceTemplateArgument[]>(resource: InputResourceTemplate<T, Args>): void;
392
454
  /**
393
455
  * Adds a tool to the server.
394
456
  */
@@ -406,6 +468,7 @@ declare class FastMCP<T extends Record<string, unknown> | undefined = undefined>
406
468
  start(options?: Partial<{
407
469
  httpStream: {
408
470
  endpoint?: `/${string}`;
471
+ eventStore?: EventStore;
409
472
  port: number;
410
473
  };
411
474
  transportType: "httpStream" | "stdio";
package/dist/FastMCP.js CHANGED
@@ -456,9 +456,9 @@ ${e instanceof Error ? e.stack : JSON.stringify(e)}`
456
456
  }
457
457
  const prompt = {
458
458
  ...inputPrompt,
459
- complete: async (name, value) => {
459
+ complete: async (name, value, auth) => {
460
460
  if (completers[name]) {
461
- return await completers[name](value);
461
+ return await completers[name](value, auth);
462
462
  }
463
463
  if (fuseInstances[name]) {
464
464
  const result = fuseInstances[name].search(value);
@@ -486,9 +486,9 @@ ${e instanceof Error ? e.stack : JSON.stringify(e)}`
486
486
  }
487
487
  const resourceTemplate = {
488
488
  ...inputResourceTemplate,
489
- complete: async (name, value) => {
489
+ complete: async (name, value, auth) => {
490
490
  if (completers[name]) {
491
- return await completers[name](value);
491
+ return await completers[name](value, auth);
492
492
  }
493
493
  return {
494
494
  values: []
@@ -516,7 +516,8 @@ ${e instanceof Error ? e.stack : JSON.stringify(e)}`
516
516
  const completion = CompletionZodSchema.parse(
517
517
  await prompt.complete(
518
518
  request.params.argument.name,
519
- request.params.argument.value
519
+ request.params.argument.value,
520
+ this.#auth
520
521
  )
521
522
  );
522
523
  return {
@@ -546,7 +547,8 @@ ${e instanceof Error ? e.stack : JSON.stringify(e)}`
546
547
  const completion = CompletionZodSchema.parse(
547
548
  await resource.complete(
548
549
  request.params.argument.name,
549
- request.params.argument.value
550
+ request.params.argument.value,
551
+ this.#auth
550
552
  )
551
553
  );
552
554
  return {
@@ -603,7 +605,10 @@ ${e instanceof Error ? e.stack : JSON.stringify(e)}`
603
605
  }
604
606
  let result;
605
607
  try {
606
- result = await prompt.load(args);
608
+ result = await prompt.load(
609
+ args,
610
+ this.#auth
611
+ );
607
612
  } catch (error) {
608
613
  const errorMessage = error instanceof Error ? error.message : String(error);
609
614
  throw new McpError(
@@ -657,7 +662,7 @@ ${e instanceof Error ? e.stack : JSON.stringify(e)}`
657
662
  continue;
658
663
  }
659
664
  const uri = uriTemplate.fill(match);
660
- const result = await resourceTemplate.load(match);
665
+ const result = await resourceTemplate.load(match, this.#auth);
661
666
  const resources2 = Array.isArray(result) ? result : [result];
662
667
  return {
663
668
  contents: resources2.map((resource2) => ({
@@ -679,7 +684,7 @@ ${e instanceof Error ? e.stack : JSON.stringify(e)}`
679
684
  }
680
685
  let maybeArrayResult;
681
686
  try {
682
- maybeArrayResult = await resource.load();
687
+ maybeArrayResult = await resource.load(this.#auth);
683
688
  } catch (error) {
684
689
  const errorMessage = error instanceof Error ? error.message : String(error);
685
690
  throw new McpError(
@@ -935,6 +940,17 @@ ${e instanceof Error ? e.stack : JSON.stringify(e)}`
935
940
  });
936
941
  }
937
942
  };
943
+ function camelToSnakeCase(str) {
944
+ return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
945
+ }
946
+ function convertObjectToSnakeCase(obj) {
947
+ const result = {};
948
+ for (const [key, value] of Object.entries(obj)) {
949
+ const snakeKey = camelToSnakeCase(key);
950
+ result[snakeKey] = value;
951
+ }
952
+ return result;
953
+ }
938
954
  var FastMCPEventEmitterBase = EventEmitter;
939
955
  var FastMCPEventEmitter = class extends FastMCPEventEmitterBase {
940
956
  };
@@ -1085,6 +1101,7 @@ var FastMCP = class extends FastMCPEventEmitter {
1085
1101
  version: this.#options.version
1086
1102
  });
1087
1103
  },
1104
+ eventStore: httpConfig.eventStore,
1088
1105
  onClose: async (session) => {
1089
1106
  this.emit("disconnect", {
1090
1107
  session
@@ -1130,6 +1147,28 @@ var FastMCP = class extends FastMCPEventEmitter {
1130
1147
  console.error("[FastMCP error] health endpoint error", error);
1131
1148
  }
1132
1149
  }
1150
+ const oauthConfig = this.#options.oauth;
1151
+ if (oauthConfig?.enabled && req.method === "GET") {
1152
+ const url = new URL(req.url || "", "http://localhost");
1153
+ if (url.pathname === "/.well-known/oauth-authorization-server" && oauthConfig.authorizationServer) {
1154
+ const metadata = convertObjectToSnakeCase(
1155
+ oauthConfig.authorizationServer
1156
+ );
1157
+ res.writeHead(200, {
1158
+ "Content-Type": "application/json"
1159
+ }).end(JSON.stringify(metadata));
1160
+ return;
1161
+ }
1162
+ if (url.pathname === "/.well-known/oauth-protected-resource" && oauthConfig.protectedResource) {
1163
+ const metadata = convertObjectToSnakeCase(
1164
+ oauthConfig.protectedResource
1165
+ );
1166
+ res.writeHead(200, {
1167
+ "Content-Type": "application/json"
1168
+ }).end(JSON.stringify(metadata));
1169
+ return;
1170
+ }
1171
+ }
1133
1172
  res.writeHead(404).end();
1134
1173
  },
1135
1174
  port: httpConfig.port,