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/src/FastMCP.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { EventStore } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
3
4
  import { RequestOptions } from "@modelcontextprotocol/sdk/shared/protocol.js";
4
5
  import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
5
6
  import {
@@ -382,41 +383,49 @@ const CompletionZodSchema = z.object({
382
383
  values: z.array(z.string()).max(100),
383
384
  }) satisfies z.ZodType<Completion>;
384
385
 
385
- type ArgumentValueCompleter = (value: string) => Promise<Completion>;
386
+ type ArgumentValueCompleter<T extends FastMCPSessionAuth = FastMCPSessionAuth> =
387
+ (value: string, auth?: T) => Promise<Completion>;
386
388
 
387
389
  type InputPrompt<
388
- Arguments extends InputPromptArgument[] = InputPromptArgument[],
390
+ T extends FastMCPSessionAuth = FastMCPSessionAuth,
391
+ Arguments extends InputPromptArgument<T>[] = InputPromptArgument<T>[],
389
392
  Args = PromptArgumentsToObject<Arguments>,
390
393
  > = {
391
- arguments?: InputPromptArgument[];
394
+ arguments?: InputPromptArgument<T>[];
392
395
  description?: string;
393
- load: (args: Args) => Promise<PromptResult>;
396
+ load: (args: Args, auth?: T) => Promise<PromptResult>;
394
397
  name: string;
395
398
  };
396
399
 
397
- type InputPromptArgument = Readonly<{
398
- complete?: ArgumentValueCompleter;
399
- description?: string;
400
- enum?: string[];
401
- name: string;
402
- required?: boolean;
403
- }>;
400
+ type InputPromptArgument<T extends FastMCPSessionAuth = FastMCPSessionAuth> =
401
+ Readonly<{
402
+ complete?: ArgumentValueCompleter<T>;
403
+ description?: string;
404
+ enum?: string[];
405
+ name: string;
406
+ required?: boolean;
407
+ }>;
404
408
 
405
409
  type InputResourceTemplate<
406
- Arguments extends ResourceTemplateArgument[] = ResourceTemplateArgument[],
410
+ T extends FastMCPSessionAuth,
411
+ Arguments extends
412
+ InputResourceTemplateArgument<T>[] = InputResourceTemplateArgument<T>[],
407
413
  > = {
408
414
  arguments: Arguments;
409
415
  description?: string;
410
416
  load: (
411
417
  args: ResourceTemplateArgumentsToObject<Arguments>,
418
+ auth?: T,
412
419
  ) => Promise<ResourceResult | ResourceResult[]>;
413
420
  mimeType?: string;
414
421
  name: string;
415
422
  uriTemplate: string;
416
423
  };
417
424
 
418
- type InputResourceTemplateArgument = Readonly<{
419
- complete?: ArgumentValueCompleter;
425
+ type InputResourceTemplateArgument<
426
+ T extends FastMCPSessionAuth = FastMCPSessionAuth,
427
+ > = Readonly<{
428
+ complete?: ArgumentValueCompleter<T>;
420
429
  description?: string;
421
430
  name: string;
422
431
  required?: boolean;
@@ -433,23 +442,25 @@ type LoggingLevel =
433
442
  | "warning";
434
443
 
435
444
  type Prompt<
436
- Arguments extends PromptArgument[] = PromptArgument[],
445
+ T extends FastMCPSessionAuth = FastMCPSessionAuth,
446
+ Arguments extends PromptArgument<T>[] = PromptArgument<T>[],
437
447
  Args = PromptArgumentsToObject<Arguments>,
438
448
  > = {
439
- arguments?: PromptArgument[];
440
- complete?: (name: string, value: string) => Promise<Completion>;
449
+ arguments?: PromptArgument<T>[];
450
+ complete?: (name: string, value: string, auth?: T) => Promise<Completion>;
441
451
  description?: string;
442
- load: (args: Args) => Promise<PromptResult>;
452
+ load: (args: Args, auth?: T) => Promise<PromptResult>;
443
453
  name: string;
444
454
  };
445
455
 
446
- type PromptArgument = Readonly<{
447
- complete?: ArgumentValueCompleter;
448
- description?: string;
449
- enum?: string[];
450
- name: string;
451
- required?: boolean;
452
- }>;
456
+ type PromptArgument<T extends FastMCPSessionAuth = FastMCPSessionAuth> =
457
+ Readonly<{
458
+ complete?: ArgumentValueCompleter<T>;
459
+ description?: string;
460
+ enum?: string[];
461
+ name: string;
462
+ required?: boolean;
463
+ }>;
453
464
 
454
465
  type PromptArgumentsToObject<T extends { name: string; required?: boolean }[]> =
455
466
  {
@@ -463,10 +474,10 @@ type PromptArgumentsToObject<T extends { name: string; required?: boolean }[]> =
463
474
 
464
475
  type PromptResult = Pick<GetPromptResult, "messages"> | string;
465
476
 
466
- type Resource = {
467
- complete?: (name: string, value: string) => Promise<Completion>;
477
+ type Resource<T extends FastMCPSessionAuth> = {
478
+ complete?: (name: string, value: string, auth?: T) => Promise<Completion>;
468
479
  description?: string;
469
- load: () => Promise<ResourceResult | ResourceResult[]>;
480
+ load: (auth?: T) => Promise<ResourceResult | ResourceResult[]>;
470
481
  mimeType?: string;
471
482
  name: string;
472
483
  uri: string;
@@ -485,21 +496,26 @@ type ResourceResult =
485
496
  };
486
497
 
487
498
  type ResourceTemplate<
488
- Arguments extends ResourceTemplateArgument[] = ResourceTemplateArgument[],
499
+ T extends FastMCPSessionAuth,
500
+ Arguments extends
501
+ ResourceTemplateArgument<T>[] = ResourceTemplateArgument<T>[],
489
502
  > = {
490
503
  arguments: Arguments;
491
- complete?: (name: string, value: string) => Promise<Completion>;
504
+ complete?: (name: string, value: string, auth?: T) => Promise<Completion>;
492
505
  description?: string;
493
506
  load: (
494
507
  args: ResourceTemplateArgumentsToObject<Arguments>,
508
+ auth?: T,
495
509
  ) => Promise<ResourceResult | ResourceResult[]>;
496
510
  mimeType?: string;
497
511
  name: string;
498
512
  uriTemplate: string;
499
513
  };
500
514
 
501
- type ResourceTemplateArgument = Readonly<{
502
- complete?: ArgumentValueCompleter;
515
+ type ResourceTemplateArgument<
516
+ T extends FastMCPSessionAuth = FastMCPSessionAuth,
517
+ > = Readonly<{
518
+ complete?: ArgumentValueCompleter<T>;
503
519
  description?: string;
504
520
  name: string;
505
521
  required?: boolean;
@@ -556,6 +572,75 @@ type ServerOptions<T extends FastMCPSessionAuth> = {
556
572
  instructions?: string;
557
573
  name: string;
558
574
 
575
+ /**
576
+ * Configuration for OAuth well-known discovery endpoints that can be exposed
577
+ * when the server is running using HTTP-based transports (SSE or HTTP Stream).
578
+ * When enabled, the server will respond to requests for OAuth discovery endpoints
579
+ * with the configured metadata.
580
+ *
581
+ * The endpoints are only added when the server is started with
582
+ * `transportType: "httpStream"` – they are ignored for the stdio transport.
583
+ * Both SSE and HTTP Stream transports support OAuth endpoints.
584
+ */
585
+ oauth?: {
586
+ /**
587
+ * OAuth Authorization Server metadata for /.well-known/oauth-authorization-server
588
+ *
589
+ * This endpoint follows RFC 8414 (OAuth 2.0 Authorization Server Metadata)
590
+ * and provides metadata about the OAuth 2.0 authorization server.
591
+ *
592
+ * Required by MCP Specification 2025-03-26
593
+ */
594
+ authorizationServer?: {
595
+ authorizationEndpoint: string;
596
+ codeChallengeMethodsSupported?: string[];
597
+ // DPoP support
598
+ dpopSigningAlgValuesSupported?: string[];
599
+ grantTypesSupported?: string[];
600
+
601
+ introspectionEndpoint?: string;
602
+ // Required
603
+ issuer: string;
604
+ // Common optional
605
+ jwksUri?: string;
606
+ opPolicyUri?: string;
607
+ opTosUri?: string;
608
+ registrationEndpoint?: string;
609
+ responseModesSupported?: string[];
610
+ responseTypesSupported: string[];
611
+ revocationEndpoint?: string;
612
+ scopesSupported?: string[];
613
+ serviceDocumentation?: string;
614
+ tokenEndpoint: string;
615
+ tokenEndpointAuthMethodsSupported?: string[];
616
+ tokenEndpointAuthSigningAlgValuesSupported?: string[];
617
+
618
+ uiLocalesSupported?: string[];
619
+ };
620
+
621
+ /**
622
+ * Whether OAuth discovery endpoints should be enabled.
623
+ */
624
+ enabled: boolean;
625
+
626
+ /**
627
+ * OAuth Protected Resource metadata for /.well-known/oauth-protected-resource
628
+ *
629
+ * This endpoint follows RFC 9470 (OAuth 2.0 Protected Resource Metadata)
630
+ * and provides metadata about the OAuth 2.0 protected resource.
631
+ *
632
+ * Required by MCP Specification 2025-06-18
633
+ */
634
+ protectedResource?: {
635
+ authorizationServers: string[];
636
+ bearerMethodsSupported?: string[];
637
+ jwksUri?: string;
638
+ resource: string;
639
+ resourceDocumentation?: string;
640
+ resourcePolicyUri?: string;
641
+ };
642
+ };
643
+
559
644
  ping?: {
560
645
  /**
561
646
  * Whether ping should be enabled by default.
@@ -659,6 +744,8 @@ const FastMCPSessionEventEmitterBase: {
659
744
  new (): StrictEventEmitter<EventEmitter, FastMCPSessionEvents>;
660
745
  } = EventEmitter;
661
746
 
747
+ type Authenticate<T> = (request: http.IncomingMessage) => Promise<T>;
748
+
662
749
  type FastMCPSessionAuth = Record<string, unknown> | undefined;
663
750
 
664
751
  class FastMCPSessionEventEmitter extends FastMCPSessionEventEmitterBase {}
@@ -691,11 +778,11 @@ export class FastMCPSession<
691
778
 
692
779
  #pingInterval: null | ReturnType<typeof setInterval> = null;
693
780
 
694
- #prompts: Prompt[] = [];
781
+ #prompts: Prompt<T>[] = [];
695
782
 
696
- #resources: Resource[] = [];
783
+ #resources: Resource<T>[] = [];
697
784
 
698
- #resourceTemplates: ResourceTemplate[] = [];
785
+ #resourceTemplates: ResourceTemplate<T>[] = [];
699
786
 
700
787
  #roots: Root[] = [];
701
788
 
@@ -720,9 +807,9 @@ export class FastMCPSession<
720
807
  instructions?: string;
721
808
  name: string;
722
809
  ping?: ServerOptions<T>["ping"];
723
- prompts: Prompt[];
724
- resources: Resource[];
725
- resourcesTemplates: InputResourceTemplate[];
810
+ prompts: Prompt<T>[];
811
+ resources: Resource<T>[];
812
+ resourcesTemplates: InputResourceTemplate<T>[];
726
813
  roots?: ServerOptions<T>["roots"];
727
814
  tools: Tool<T>[];
728
815
  transportType?: "httpStream" | "stdio";
@@ -965,8 +1052,8 @@ export class FastMCPSession<
965
1052
  };
966
1053
  }
967
1054
 
968
- private addPrompt(inputPrompt: InputPrompt) {
969
- const completers: Record<string, ArgumentValueCompleter> = {};
1055
+ private addPrompt(inputPrompt: InputPrompt<T>) {
1056
+ const completers: Record<string, ArgumentValueCompleter<T>> = {};
970
1057
  const enums: Record<string, string[]> = {};
971
1058
  const fuseInstances: Record<string, Fuse<string>> = {};
972
1059
 
@@ -986,9 +1073,9 @@ export class FastMCPSession<
986
1073
 
987
1074
  const prompt = {
988
1075
  ...inputPrompt,
989
- complete: async (name: string, value: string) => {
1076
+ complete: async (name: string, value: string, auth?: T) => {
990
1077
  if (completers[name]) {
991
- return await completers[name](value);
1078
+ return await completers[name](value, auth);
992
1079
  }
993
1080
 
994
1081
  if (fuseInstances[name]) {
@@ -1009,12 +1096,12 @@ export class FastMCPSession<
1009
1096
  this.#prompts.push(prompt);
1010
1097
  }
1011
1098
 
1012
- private addResource(inputResource: Resource) {
1099
+ private addResource(inputResource: Resource<T>) {
1013
1100
  this.#resources.push(inputResource);
1014
1101
  }
1015
1102
 
1016
- private addResourceTemplate(inputResourceTemplate: InputResourceTemplate) {
1017
- const completers: Record<string, ArgumentValueCompleter> = {};
1103
+ private addResourceTemplate(inputResourceTemplate: InputResourceTemplate<T>) {
1104
+ const completers: Record<string, ArgumentValueCompleter<T>> = {};
1018
1105
 
1019
1106
  for (const argument of inputResourceTemplate.arguments ?? []) {
1020
1107
  if (argument.complete) {
@@ -1024,9 +1111,9 @@ export class FastMCPSession<
1024
1111
 
1025
1112
  const resourceTemplate = {
1026
1113
  ...inputResourceTemplate,
1027
- complete: async (name: string, value: string) => {
1114
+ complete: async (name: string, value: string, auth?: T) => {
1028
1115
  if (completers[name]) {
1029
- return await completers[name](value);
1116
+ return await completers[name](value, auth);
1030
1117
  }
1031
1118
 
1032
1119
  return {
@@ -1061,6 +1148,7 @@ export class FastMCPSession<
1061
1148
  await prompt.complete(
1062
1149
  request.params.argument.name,
1063
1150
  request.params.argument.value,
1151
+ this.#auth,
1064
1152
  ),
1065
1153
  );
1066
1154
 
@@ -1097,6 +1185,7 @@ export class FastMCPSession<
1097
1185
  await resource.complete(
1098
1186
  request.params.argument.name,
1099
1187
  request.params.argument.value,
1188
+ this.#auth,
1100
1189
  ),
1101
1190
  );
1102
1191
 
@@ -1125,7 +1214,7 @@ export class FastMCPSession<
1125
1214
  });
1126
1215
  }
1127
1216
 
1128
- private setupPromptHandlers(prompts: Prompt[]) {
1217
+ private setupPromptHandlers(prompts: Prompt<T>[]) {
1129
1218
  this.#server.setRequestHandler(ListPromptsRequestSchema, async () => {
1130
1219
  return {
1131
1220
  prompts: prompts.map((prompt) => {
@@ -1164,10 +1253,13 @@ export class FastMCPSession<
1164
1253
  }
1165
1254
  }
1166
1255
 
1167
- let result: Awaited<ReturnType<Prompt["load"]>>;
1256
+ let result: Awaited<ReturnType<Prompt<T>["load"]>>;
1168
1257
 
1169
1258
  try {
1170
- result = await prompt.load(args as Record<string, string | undefined>);
1259
+ result = await prompt.load(
1260
+ args as Record<string, string | undefined>,
1261
+ this.#auth,
1262
+ );
1171
1263
  } catch (error) {
1172
1264
  const errorMessage =
1173
1265
  error instanceof Error ? error.message : String(error);
@@ -1196,7 +1288,7 @@ export class FastMCPSession<
1196
1288
  });
1197
1289
  }
1198
1290
 
1199
- private setupResourceHandlers(resources: Resource[]) {
1291
+ private setupResourceHandlers(resources: Resource<T>[]) {
1200
1292
  this.#server.setRequestHandler(ListResourcesRequestSchema, async () => {
1201
1293
  return {
1202
1294
  resources: resources.map((resource) => ({
@@ -1231,7 +1323,7 @@ export class FastMCPSession<
1231
1323
 
1232
1324
  const uri = uriTemplate.fill(match);
1233
1325
 
1234
- const result = await resourceTemplate.load(match);
1326
+ const result = await resourceTemplate.load(match, this.#auth);
1235
1327
 
1236
1328
  const resources = Array.isArray(result) ? result : [result];
1237
1329
  return {
@@ -1257,10 +1349,10 @@ export class FastMCPSession<
1257
1349
  throw new UnexpectedStateError("Resource does not support reading");
1258
1350
  }
1259
1351
 
1260
- let maybeArrayResult: Awaited<ReturnType<Resource["load"]>>;
1352
+ let maybeArrayResult: Awaited<ReturnType<Resource<T>["load"]>>;
1261
1353
 
1262
1354
  try {
1263
- maybeArrayResult = await resource.load();
1355
+ maybeArrayResult = await resource.load(this.#auth);
1264
1356
  } catch (error) {
1265
1357
  const errorMessage =
1266
1358
  error instanceof Error ? error.message : String(error);
@@ -1294,7 +1386,9 @@ export class FastMCPSession<
1294
1386
  );
1295
1387
  }
1296
1388
 
1297
- private setupResourceTemplateHandlers(resourceTemplates: ResourceTemplate[]) {
1389
+ private setupResourceTemplateHandlers(
1390
+ resourceTemplates: ResourceTemplate<T>[],
1391
+ ) {
1298
1392
  this.#server.setRequestHandler(
1299
1393
  ListResourceTemplatesRequestSchema,
1300
1394
  async () => {
@@ -1580,16 +1674,37 @@ export class FastMCPSession<
1580
1674
  }
1581
1675
  }
1582
1676
 
1677
+ /**
1678
+ * Converts camelCase to snake_case for OAuth endpoint responses
1679
+ */
1680
+ function camelToSnakeCase(str: string): string {
1681
+ return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
1682
+ }
1683
+
1684
+ /**
1685
+ * Converts an object with camelCase keys to snake_case keys
1686
+ */
1687
+ function convertObjectToSnakeCase(
1688
+ obj: Record<string, unknown>,
1689
+ ): Record<string, unknown> {
1690
+ const result: Record<string, unknown> = {};
1691
+
1692
+ for (const [key, value] of Object.entries(obj)) {
1693
+ const snakeKey = camelToSnakeCase(key);
1694
+ result[snakeKey] = value;
1695
+ }
1696
+
1697
+ return result;
1698
+ }
1699
+
1583
1700
  const FastMCPEventEmitterBase: {
1584
1701
  new (): StrictEventEmitter<EventEmitter, FastMCPEvents<FastMCPSessionAuth>>;
1585
1702
  } = EventEmitter;
1586
1703
 
1587
- type Authenticate<T> = (request: http.IncomingMessage) => Promise<T>;
1588
-
1589
1704
  class FastMCPEventEmitter extends FastMCPEventEmitterBase {}
1590
1705
 
1591
1706
  export class FastMCP<
1592
- T extends Record<string, unknown> | undefined = undefined,
1707
+ T extends FastMCPSessionAuth = FastMCPSessionAuth,
1593
1708
  > extends FastMCPEventEmitter {
1594
1709
  public get sessions(): FastMCPSession<T>[] {
1595
1710
  return this.#sessions;
@@ -1597,9 +1712,9 @@ export class FastMCP<
1597
1712
  #authenticate: Authenticate<T> | undefined;
1598
1713
  #httpStreamServer: null | SSEServer = null;
1599
1714
  #options: ServerOptions<T>;
1600
- #prompts: InputPrompt[] = [];
1601
- #resources: Resource[] = [];
1602
- #resourcesTemplates: InputResourceTemplate[] = [];
1715
+ #prompts: InputPrompt<T>[] = [];
1716
+ #resources: Resource<T>[] = [];
1717
+ #resourcesTemplates: InputResourceTemplate<T>[] = [];
1603
1718
  #sessions: FastMCPSession<T>[] = [];
1604
1719
 
1605
1720
  #tools: Tool<T>[] = [];
@@ -1614,8 +1729,8 @@ export class FastMCP<
1614
1729
  /**
1615
1730
  * Adds a prompt to the server.
1616
1731
  */
1617
- public addPrompt<const Args extends InputPromptArgument[]>(
1618
- prompt: InputPrompt<Args>,
1732
+ public addPrompt<const Args extends InputPromptArgument<T>[]>(
1733
+ prompt: InputPrompt<T, Args>,
1619
1734
  ) {
1620
1735
  this.#prompts.push(prompt);
1621
1736
  }
@@ -1623,7 +1738,7 @@ export class FastMCP<
1623
1738
  /**
1624
1739
  * Adds a resource to the server.
1625
1740
  */
1626
- public addResource(resource: Resource) {
1741
+ public addResource(resource: Resource<T>) {
1627
1742
  this.#resources.push(resource);
1628
1743
  }
1629
1744
 
@@ -1632,7 +1747,7 @@ export class FastMCP<
1632
1747
  */
1633
1748
  public addResourceTemplate<
1634
1749
  const Args extends InputResourceTemplateArgument[],
1635
- >(resource: InputResourceTemplate<Args>) {
1750
+ >(resource: InputResourceTemplate<T, Args>) {
1636
1751
  this.#resourcesTemplates.push(resource);
1637
1752
  }
1638
1753
 
@@ -1730,7 +1845,11 @@ export class FastMCP<
1730
1845
  */
1731
1846
  public async start(
1732
1847
  options?: Partial<{
1733
- httpStream: { endpoint?: `/${string}`; port: number };
1848
+ httpStream: {
1849
+ endpoint?: `/${string}`;
1850
+ eventStore?: EventStore;
1851
+ port: number;
1852
+ };
1734
1853
  transportType: "httpStream" | "stdio";
1735
1854
  }>,
1736
1855
  ) {
@@ -1756,7 +1875,7 @@ export class FastMCP<
1756
1875
  this.#sessions.push(session);
1757
1876
 
1758
1877
  this.emit("connect", {
1759
- session,
1878
+ session: session as FastMCPSession<FastMCPSessionAuth>,
1760
1879
  });
1761
1880
  } else if (config.transportType === "httpStream") {
1762
1881
  const httpConfig = config.httpStream;
@@ -1782,9 +1901,10 @@ export class FastMCP<
1782
1901
  version: this.#options.version,
1783
1902
  });
1784
1903
  },
1904
+ eventStore: httpConfig.eventStore,
1785
1905
  onClose: async (session) => {
1786
1906
  this.emit("disconnect", {
1787
- session,
1907
+ session: session as FastMCPSession<FastMCPSessionAuth>,
1788
1908
  });
1789
1909
  },
1790
1910
  onConnect: async (session) => {
@@ -1793,9 +1913,10 @@ export class FastMCP<
1793
1913
  console.info(`[FastMCP info] HTTP Stream session established`);
1794
1914
 
1795
1915
  this.emit("connect", {
1796
- session,
1916
+ session: session as FastMCPSession<FastMCPSessionAuth>,
1797
1917
  });
1798
1918
  },
1919
+
1799
1920
  onUnhandledRequest: async (req, res) => {
1800
1921
  const healthConfig = this.#options.health ?? {};
1801
1922
 
@@ -1849,10 +1970,45 @@ export class FastMCP<
1849
1970
  }
1850
1971
  }
1851
1972
 
1973
+ // Handle OAuth well-known endpoints
1974
+ const oauthConfig = this.#options.oauth;
1975
+ if (oauthConfig?.enabled && req.method === "GET") {
1976
+ const url = new URL(req.url || "", "http://localhost");
1977
+
1978
+ if (
1979
+ url.pathname === "/.well-known/oauth-authorization-server" &&
1980
+ oauthConfig.authorizationServer
1981
+ ) {
1982
+ const metadata = convertObjectToSnakeCase(
1983
+ oauthConfig.authorizationServer,
1984
+ );
1985
+ res
1986
+ .writeHead(200, {
1987
+ "Content-Type": "application/json",
1988
+ })
1989
+ .end(JSON.stringify(metadata));
1990
+ return;
1991
+ }
1992
+
1993
+ if (
1994
+ url.pathname === "/.well-known/oauth-protected-resource" &&
1995
+ oauthConfig.protectedResource
1996
+ ) {
1997
+ const metadata = convertObjectToSnakeCase(
1998
+ oauthConfig.protectedResource,
1999
+ );
2000
+ res
2001
+ .writeHead(200, {
2002
+ "Content-Type": "application/json",
2003
+ })
2004
+ .end(JSON.stringify(metadata));
2005
+ return;
2006
+ }
2007
+ }
2008
+
1852
2009
  // If the request was not handled above, return 404
1853
2010
  res.writeHead(404).end();
1854
2011
  },
1855
-
1856
2012
  port: httpConfig.port,
1857
2013
  streamEndpoint: httpConfig.endpoint,
1858
2014
  });
@@ -1884,7 +2040,11 @@ export class FastMCP<
1884
2040
  }>,
1885
2041
  ):
1886
2042
  | {
1887
- httpStream: { endpoint: `/${string}`; port: number };
2043
+ httpStream: {
2044
+ endpoint: `/${string}`;
2045
+ eventStore?: EventStore;
2046
+ port: number;
2047
+ };
1888
2048
  transportType: "httpStream";
1889
2049
  }
1890
2050
  | { transportType: "stdio" } {
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Example FastMCP server demonstrating OAuth well-known endpoint support.
3
+ *
4
+ * This example shows how to configure FastMCP to serve OAuth discovery endpoints
5
+ * for both authorization server metadata and protected resource metadata.
6
+ *
7
+ * Run with: node dist/examples/oauth-server.js --transport http-stream --port 4111
8
+ * Then visit:
9
+ * - http://localhost:4111/.well-known/oauth-authorization-server
10
+ * - http://localhost:4111/.well-known/oauth-protected-resource
11
+ */
12
+
13
+ import { FastMCP } from "../FastMCP.js";
14
+
15
+ const server = new FastMCP({
16
+ name: "OAuth Example Server",
17
+ oauth: {
18
+ authorizationServer: {
19
+ authorizationEndpoint: "https://auth.example.com/oauth/authorize",
20
+ codeChallengeMethodsSupported: ["S256"],
21
+ // DPoP support
22
+ dpopSigningAlgValuesSupported: ["ES256", "RS256"],
23
+ grantTypesSupported: ["authorization_code", "refresh_token"],
24
+
25
+ introspectionEndpoint: "https://auth.example.com/oauth/introspect",
26
+ // Required fields
27
+ issuer: "https://auth.example.com",
28
+ // Optional fields
29
+ jwksUri: "https://auth.example.com/.well-known/jwks.json",
30
+ opPolicyUri: "https://example.com/policy",
31
+ opTosUri: "https://example.com/terms",
32
+ registrationEndpoint: "https://auth.example.com/oauth/register",
33
+ responseModesSupported: ["query", "fragment"],
34
+ responseTypesSupported: ["code"],
35
+ revocationEndpoint: "https://auth.example.com/oauth/revoke",
36
+ scopesSupported: ["read", "write", "admin"],
37
+ serviceDocumentation: "https://docs.example.com/oauth",
38
+ tokenEndpoint: "https://auth.example.com/oauth/token",
39
+ tokenEndpointAuthMethodsSupported: [
40
+ "client_secret_basic",
41
+ "client_secret_post",
42
+ ],
43
+ tokenEndpointAuthSigningAlgValuesSupported: ["RS256", "ES256"],
44
+
45
+ uiLocalesSupported: ["en-US", "es-ES"],
46
+ },
47
+ enabled: true,
48
+ protectedResource: {
49
+ authorizationServers: ["https://auth.example.com"],
50
+ bearerMethodsSupported: ["header"],
51
+ jwksUri: "https://oauth-example-server.example.com/.well-known/jwks.json",
52
+ resource: "mcp://oauth-example-server",
53
+ resourceDocumentation: "https://docs.example.com/mcp-api",
54
+ resourcePolicyUri: "https://example.com/resource-policy",
55
+ },
56
+ },
57
+ version: "1.0.0",
58
+ });
59
+
60
+ // Add a simple tool to demonstrate the server functionality
61
+ server.addTool({
62
+ description: "Get information about this OAuth-enabled MCP server",
63
+ execute: async () => {
64
+ return {
65
+ content: [
66
+ {
67
+ text: `This is an OAuth-enabled FastMCP server!
68
+
69
+ OAuth Discovery Endpoints:
70
+ - Authorization Server: /.well-known/oauth-authorization-server
71
+ - Protected Resource: /.well-known/oauth-protected-resource
72
+
73
+ The server demonstrates how to configure OAuth metadata for MCP servers
74
+ that need to integrate with OAuth 2.0 authorization flows.`,
75
+ type: "text",
76
+ },
77
+ ],
78
+ };
79
+ },
80
+ name: "get-server-info",
81
+ });
82
+
83
+ // Start the server
84
+ await server.start({
85
+ httpStream: { port: 4111 },
86
+ transportType: "httpStream",
87
+ });
88
+
89
+ console.log(`
90
+ 🚀 OAuth Example Server is running!
91
+
92
+ Try these endpoints:
93
+ - MCP (HTTP Stream): http://localhost:4111/mcp
94
+ - MCP (SSE): http://localhost:4111/sse
95
+ - Health: http://localhost:4111/health
96
+ - OAuth Authorization Server: http://localhost:4111/.well-known/oauth-authorization-server
97
+ - OAuth Protected Resource: http://localhost:4111/.well-known/oauth-protected-resource
98
+
99
+ The OAuth endpoints work with both SSE and HTTP Stream transports and return
100
+ JSON metadata following RFC 8414 standards.
101
+ `);