1+ import express , { RequestHandler } from "express" ;
2+ import { clientRegistrationHandler , ClientRegistrationHandlerOptions } from "./handlers/register.js" ;
3+ import { tokenHandler , TokenHandlerOptions } from "./handlers/token.js" ;
4+ import { authorizationHandler , AuthorizationHandlerOptions } from "./handlers/authorize.js" ;
5+ import { revocationHandler , RevocationHandlerOptions } from "./handlers/revoke.js" ;
6+ import { metadataHandler } from "./handlers/metadata.js" ;
7+
8+ export type MetadataOptions = {
9+ metadata : {
10+ /**
11+ * The authorization server's issuer identifier, which is a URL that uses the "https" scheme and has no query or fragment components.
12+ */
13+ issuerUrl : URL ;
14+
15+ /**
16+ * An optional URL of a page containing human-readable information that developers might want or need to know when using the authorization server.
17+ */
18+ serviceDocumentationUrl ?: URL ;
19+ } ;
20+ } ;
21+
22+ export type AuthRouterOptions =
23+ & AuthorizationHandlerOptions
24+ & Omit < ClientRegistrationHandlerOptions , "clientsStore" >
25+ & MetadataOptions
26+ & RevocationHandlerOptions
27+ & TokenHandlerOptions ;
28+
29+ /**
30+ * Installs standard MCP authorization endpoints, including dynamic client registration and token revocation (if supported). Also advertises standard authorization server metadata, for easier discovery of supported configurations by clients.
31+ *
32+ * This router MUST be installed at the application root, like so:
33+ *
34+ * const app = express();
35+ * app.use(mcpAuthRouter(...));
36+ */
37+ export function mcpAuthRouter ( options : AuthRouterOptions ) : RequestHandler {
38+ const issuer = options . metadata . issuerUrl ;
39+
40+ // Technically RFC 8414 does not permit a localhost HTTPS exemption, but this will be necessary for ease of testing
41+ if ( issuer . protocol !== "https:" && issuer . hostname !== "localhost" && issuer . hostname !== "127.0.0.1" ) {
42+ throw new Error ( "Issuer URL must be HTTPS" ) ;
43+ }
44+ if ( issuer . hash ) {
45+ throw new Error ( "Issuer URL must not have a fragment" ) ;
46+ }
47+ if ( issuer . search ) {
48+ throw new Error ( "Issuer URL must not have a query string" ) ;
49+ }
50+
51+ const authorization_endpoint = "/authorize" ;
52+ const token_endpoint = "/token" ;
53+ const registration_endpoint = options . provider . clientsStore . registerClient ? "/register" : undefined ;
54+ const revocation_endpoint = options . provider . revokeToken ? "/revoke" : undefined ;
55+
56+ const metadata = {
57+ issuer : issuer . href ,
58+ service_documentation : options . metadata . serviceDocumentationUrl ?. href ,
59+
60+ authorization_endpoint : new URL ( authorization_endpoint , issuer ) . href ,
61+ response_types_supported : [ "code" ] ,
62+ code_challenge_methods_supported : [ "S256" ] ,
63+
64+ token_endpoint : new URL ( token_endpoint , issuer ) . href ,
65+ token_endpoint_auth_methods_supported : [ "client_secret_post" ] ,
66+ grant_types_supported : [ "authorization_code" , "refresh_token" ] ,
67+
68+ revocation_endpoint : revocation_endpoint ? new URL ( revocation_endpoint , issuer ) . href : undefined ,
69+ revocation_endpoint_auth_methods_supported : revocation_endpoint ? [ "client_secret_post" ] : undefined ,
70+
71+ registration_endpoint : registration_endpoint ? new URL ( registration_endpoint , issuer ) . href : undefined ,
72+ } ;
73+
74+ const router = express . Router ( ) ;
75+ router . use ( authorization_endpoint , authorizationHandler ( options ) ) ;
76+ router . use ( token_endpoint , tokenHandler ( options ) ) ;
77+ router . use ( "/.well-known/oauth-authorization-server" , metadataHandler ( metadata ) ) ;
78+
79+ if ( registration_endpoint ) {
80+ router . use ( registration_endpoint , clientRegistrationHandler ( { clientsStore : options . provider . clientsStore , ...options } ) ) ;
81+ }
82+
83+ if ( revocation_endpoint ) {
84+ router . use ( revocation_endpoint , revocationHandler ( options ) ) ;
85+ }
86+
87+ return router ;
88+ }
0 commit comments