@@ -41,6 +41,12 @@ export type AuthRouterOptions = {
4141 */
4242 resourceName ?: string ;
4343
44+ /**
45+ * The URL of the protected resource (RS) whose metadata we advertise.
46+ * If not provided, falls back to `baseUrl` and then to `issuerUrl` (AS=RS).
47+ */
48+ resourceServerUrl ?: URL ;
49+
4450 // Individual options per route
4551 authorizationOptions ?: Omit < AuthorizationHandlerOptions , "provider" > ;
4652 clientRegistrationOptions ?: Omit < ClientRegistrationHandlerOptions , "clientsStore" > ;
@@ -130,8 +136,8 @@ export function mcpAuthRouter(options: AuthRouterOptions): RequestHandler {
130136
131137 router . use ( mcpAuthMetadataRouter ( {
132138 oauthMetadata,
133- // This router is used for AS+RS combo's, so the issuer is also the resource server
134- resourceServerUrl : new URL ( oauthMetadata . issuer ) ,
139+ // Prefer explicit RS; otherwise fall back to AS baseUrl, then to issuer (back-compat)
140+ resourceServerUrl : options . resourceServerUrl ?? options . baseUrl ?? new URL ( oauthMetadata . issuer ) ,
135141 serviceDocumentationUrl : options . serviceDocumentationUrl ,
136142 scopesSupported : options . scopesSupported ,
137143 resourceName : options . resourceName
@@ -185,7 +191,7 @@ export type AuthMetadataOptions = {
185191 resourceName ?: string ;
186192}
187193
188- export function mcpAuthMetadataRouter ( options : AuthMetadataOptions ) {
194+ export function mcpAuthMetadataRouter ( options : AuthMetadataOptions ) : express . Router {
189195 checkIssuerUrl ( new URL ( options . oauthMetadata . issuer ) ) ;
190196
191197 const router = express . Router ( ) ;
@@ -202,8 +208,15 @@ export function mcpAuthMetadataRouter(options: AuthMetadataOptions) {
202208 resource_documentation : options . serviceDocumentationUrl ?. href ,
203209 } ;
204210
211+ // Serve PRM at the base well-known URL…
205212 router . use ( "/.well-known/oauth-protected-resource" , metadataHandler ( protectedResourceMetadata ) ) ;
206213
214+ // …and also at the path-specific URL per RFC 9728 when the resource has a path (e.g., /mcp)
215+ const rsPath = new URL ( options . resourceServerUrl . href ) . pathname ;
216+ if ( rsPath && rsPath !== "/" ) {
217+ router . use ( `/.well-known/oauth-protected-resource${ rsPath } ` , metadataHandler ( protectedResourceMetadata ) ) ;
218+ }
219+
207220 // Always add this for backwards compatibility
208221 router . use ( "/.well-known/oauth-authorization-server" , metadataHandler ( options . oauthMetadata ) ) ;
209222
@@ -219,8 +232,10 @@ export function mcpAuthMetadataRouter(options: AuthMetadataOptions) {
219232 *
220233 * @example
221234 * getOAuthProtectedResourceMetadataUrl(new URL('https://api.example.com/mcp'))
222- * // Returns: 'https://api.example.com/.well-known/oauth-protected-resource'
235+ * // Returns: 'https://api.example.com/.well-known/oauth-protected-resource/mcp '
223236 */
224237export function getOAuthProtectedResourceMetadataUrl ( serverUrl : URL ) : string {
225- return new URL ( '/.well-known/oauth-protected-resource' , serverUrl ) . href ;
238+ const u = new URL ( serverUrl . href ) ;
239+ const rsPath = u . pathname && u . pathname !== '/' ? u . pathname : '' ;
240+ return new URL ( `/.well-known/oauth-protected-resource${ rsPath } ` , u ) . href ;
226241}
0 commit comments