@@ -41,6 +41,12 @@ export type AuthRouterOptions = {
41
41
*/
42
42
resourceName ?: string ;
43
43
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
+
44
50
// Individual options per route
45
51
authorizationOptions ?: Omit < AuthorizationHandlerOptions , "provider" > ;
46
52
clientRegistrationOptions ?: Omit < ClientRegistrationHandlerOptions , "clientsStore" > ;
@@ -130,8 +136,8 @@ export function mcpAuthRouter(options: AuthRouterOptions): RequestHandler {
130
136
131
137
router . use ( mcpAuthMetadataRouter ( {
132
138
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 ) ,
135
141
serviceDocumentationUrl : options . serviceDocumentationUrl ,
136
142
scopesSupported : options . scopesSupported ,
137
143
resourceName : options . resourceName
@@ -185,7 +191,7 @@ export type AuthMetadataOptions = {
185
191
resourceName ?: string ;
186
192
}
187
193
188
- export function mcpAuthMetadataRouter ( options : AuthMetadataOptions ) {
194
+ export function mcpAuthMetadataRouter ( options : AuthMetadataOptions ) : express . Router {
189
195
checkIssuerUrl ( new URL ( options . oauthMetadata . issuer ) ) ;
190
196
191
197
const router = express . Router ( ) ;
@@ -202,9 +208,11 @@ export function mcpAuthMetadataRouter(options: AuthMetadataOptions) {
202
208
resource_documentation : options . serviceDocumentationUrl ?. href ,
203
209
} ;
204
210
205
- router . use ( "/.well-known/oauth-protected-resource" , metadataHandler ( protectedResourceMetadata ) ) ;
211
+ // Serve PRM at the path-specific URL per RFC 9728
212
+ const rsPath = new URL ( options . resourceServerUrl . href ) . pathname ;
213
+ router . use ( `/.well-known/oauth-protected-resource${ rsPath === '/' ? '' : rsPath } ` , metadataHandler ( protectedResourceMetadata ) ) ;
206
214
207
- // Always add this for backwards compatibility
215
+ // Always add this for OAuth Authorization Server metadata per RFC 8414
208
216
router . use ( "/.well-known/oauth-authorization-server" , metadataHandler ( options . oauthMetadata ) ) ;
209
217
210
218
return router ;
@@ -219,8 +227,10 @@ export function mcpAuthMetadataRouter(options: AuthMetadataOptions) {
219
227
*
220
228
* @example
221
229
* getOAuthProtectedResourceMetadataUrl(new URL('https://api.example.com/mcp'))
222
- * // Returns: 'https://api.example.com/.well-known/oauth-protected-resource'
230
+ * // Returns: 'https://api.example.com/.well-known/oauth-protected-resource/mcp '
223
231
*/
224
232
export function getOAuthProtectedResourceMetadataUrl ( serverUrl : URL ) : string {
225
- return new URL ( '/.well-known/oauth-protected-resource' , serverUrl ) . href ;
233
+ const u = new URL ( serverUrl . href ) ;
234
+ const rsPath = u . pathname && u . pathname !== '/' ? u . pathname : '' ;
235
+ return new URL ( `/.well-known/oauth-protected-resource${ rsPath } ` , u ) . href ;
226
236
}
0 commit comments