Pass through rate limits and other options cleanly from router

This commit is contained in:
Justin Spahr-Summers 2025-02-18 11:25:42 +00:00
commit 48cddd9488
No known key found for this signature in database

View file

@ -4,38 +4,43 @@ import { tokenHandler, TokenHandlerOptions } from "./handlers/token.js";
import { authorizationHandler, AuthorizationHandlerOptions } from "./handlers/authorize.js";
import { revocationHandler, RevocationHandlerOptions } from "./handlers/revoke.js";
import { metadataHandler } from "./handlers/metadata.js";
import { OAuthServerProvider } from "./provider.js";
export type MetadataOptions = {
metadata: {
/**
* The authorization server's issuer identifier, which is a URL that uses the "https" scheme and has no query or fragment components.
*/
issuerUrl: URL;
export type AuthRouterOptions = {
/**
* A provider implementing the actual authorization logic for this router.
*/
provider: OAuthServerProvider;
/**
* An optional URL of a page containing human-readable information that developers might want or need to know when using the authorization server.
*/
serviceDocumentationUrl?: URL;
};
/**
* The authorization server's issuer identifier, which is a URL that uses the "https" scheme and has no query or fragment components.
*/
issuerUrl: URL;
/**
* An optional URL of a page containing human-readable information that developers might want or need to know when using the authorization server.
*/
serviceDocumentationUrl?: URL;
// Individual options per route
authorizationOptions?: Omit<AuthorizationHandlerOptions, "provider">;
clientRegistrationOptions?: Omit<ClientRegistrationHandlerOptions, "clientsStore">;
revocationOptions?: Omit<RevocationHandlerOptions, "provider">;
tokenOptions?: Omit<TokenHandlerOptions, "provider">;
};
export type AuthRouterOptions =
& AuthorizationHandlerOptions
& Omit<ClientRegistrationHandlerOptions, "clientsStore">
& MetadataOptions
& RevocationHandlerOptions
& TokenHandlerOptions;
/**
* 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.
*
* By default, rate limiting is applied to all endpoints to prevent abuse.
*
* This router MUST be installed at the application root, like so:
*
* const app = express();
* app.use(mcpAuthRouter(...));
*/
export function mcpAuthRouter(options: AuthRouterOptions): RequestHandler {
const issuer = options.metadata.issuerUrl;
const issuer = options.issuerUrl;
// Technically RFC 8414 does not permit a localhost HTTPS exemption, but this will be necessary for ease of testing
if (issuer.protocol !== "https:" && issuer.hostname !== "localhost" && issuer.hostname !== "127.0.0.1") {
@ -55,7 +60,7 @@ export function mcpAuthRouter(options: AuthRouterOptions): RequestHandler {
const metadata = {
issuer: issuer.href,
service_documentation: options.metadata.serviceDocumentationUrl?.href,
service_documentation: options.serviceDocumentationUrl?.href,
authorization_endpoint: new URL(authorization_endpoint, issuer).href,
response_types_supported: ["code"],
@ -72,16 +77,34 @@ export function mcpAuthRouter(options: AuthRouterOptions): RequestHandler {
};
const router = express.Router();
router.use(authorization_endpoint, authorizationHandler(options));
router.use(token_endpoint, tokenHandler(options));
router.use(
authorization_endpoint,
authorizationHandler({ provider: options.provider, ...options.authorizationOptions })
);
router.use(
token_endpoint,
tokenHandler({ provider: options.provider, ...options.tokenOptions })
);
router.use("/.well-known/oauth-authorization-server", metadataHandler(metadata));
if (registration_endpoint) {
router.use(registration_endpoint, clientRegistrationHandler({ clientsStore: options.provider.clientsStore, ...options }));
router.use(
registration_endpoint,
clientRegistrationHandler({
clientsStore: options.provider.clientsStore,
...options,
})
);
}
if (revocation_endpoint) {
router.use(revocation_endpoint, revocationHandler(options));
router.use(
revocation_endpoint,
revocationHandler({ provider: options.provider, ...options.revocationOptions })
);
}
return router;