diff --git a/src/server/auth/router.ts b/src/server/auth/router.ts index a06bf73a1..727699e0c 100644 --- a/src/server/auth/router.ts +++ b/src/server/auth/router.ts @@ -41,6 +41,12 @@ export type AuthRouterOptions = { */ resourceName?: string; + /** + * The URL of the protected resource (RS) whose metadata we advertise. + * If not provided, falls back to `baseUrl` and then to `issuerUrl` (AS=RS). + */ + resourceServerUrl?: URL; + // Individual options per route authorizationOptions?: Omit; clientRegistrationOptions?: Omit; @@ -130,8 +136,8 @@ export function mcpAuthRouter(options: AuthRouterOptions): RequestHandler { router.use(mcpAuthMetadataRouter({ oauthMetadata, - // This router is used for AS+RS combo's, so the issuer is also the resource server - resourceServerUrl: new URL(oauthMetadata.issuer), + // Prefer explicit RS; otherwise fall back to AS baseUrl, then to issuer (back-compat) + resourceServerUrl: options.resourceServerUrl ?? options.baseUrl ?? new URL(oauthMetadata.issuer), serviceDocumentationUrl: options.serviceDocumentationUrl, scopesSupported: options.scopesSupported, resourceName: options.resourceName @@ -185,7 +191,7 @@ export type AuthMetadataOptions = { resourceName?: string; } -export function mcpAuthMetadataRouter(options: AuthMetadataOptions) { +export function mcpAuthMetadataRouter(options: AuthMetadataOptions): express.Router { checkIssuerUrl(new URL(options.oauthMetadata.issuer)); const router = express.Router(); @@ -202,9 +208,11 @@ export function mcpAuthMetadataRouter(options: AuthMetadataOptions) { resource_documentation: options.serviceDocumentationUrl?.href, }; - router.use("/.well-known/oauth-protected-resource", metadataHandler(protectedResourceMetadata)); + // Serve PRM at the path-specific URL per RFC 9728 + const rsPath = new URL(options.resourceServerUrl.href).pathname; + router.use(`/.well-known/oauth-protected-resource${rsPath === '/' ? '' : rsPath}`, metadataHandler(protectedResourceMetadata)); - // Always add this for backwards compatibility + // Always add this for OAuth Authorization Server metadata per RFC 8414 router.use("/.well-known/oauth-authorization-server", metadataHandler(options.oauthMetadata)); return router; @@ -219,8 +227,10 @@ export function mcpAuthMetadataRouter(options: AuthMetadataOptions) { * * @example * getOAuthProtectedResourceMetadataUrl(new URL('https://api.example.com/mcp')) - * // Returns: 'https://api.example.com/.well-known/oauth-protected-resource' + * // Returns: 'https://api.example.com/.well-known/oauth-protected-resource/mcp' */ export function getOAuthProtectedResourceMetadataUrl(serverUrl: URL): string { - return new URL('/.well-known/oauth-protected-resource', serverUrl).href; + const u = new URL(serverUrl.href); + const rsPath = u.pathname && u.pathname !== '/' ? u.pathname : ''; + return new URL(`/.well-known/oauth-protected-resource${rsPath}`, u).href; }