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 +104 -0
- package/dist/FastMCP.d.ts +95 -32
- package/dist/FastMCP.js +48 -9
- package/dist/FastMCP.js.map +1 -1
- package/eslint.config.ts +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/FastMCP.oauth.test.ts +177 -0
- package/src/FastMCP.test.ts +577 -0
- package/src/FastMCP.ts +231 -71
- package/src/examples/oauth-server.ts +101 -0
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
|
|
386
|
+
type ArgumentValueCompleter<T extends FastMCPSessionAuth = FastMCPSessionAuth> =
|
|
387
|
+
(value: string, auth?: T) => Promise<Completion>;
|
|
386
388
|
|
|
387
389
|
type InputPrompt<
|
|
388
|
-
|
|
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 =
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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
|
-
|
|
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
|
|
419
|
-
|
|
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
|
-
|
|
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 =
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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
|
-
|
|
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
|
|
502
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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: {
|
|
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: {
|
|
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
|
+
`);
|