@@ -39,6 +39,7 @@ mockFetch.mockResolvedValue({
3939 issuer : "https://auth.example.com" ,
4040 authorization_endpoint : "https://auth.example.com/authorize" ,
4141 token_endpoint : "https://auth.example.com/token" ,
42+ registration_endpoint : "https://auth.example.com/register" ,
4243 response_types_supported : [ "code" ] ,
4344 token_endpoint_auth_methods_supported : [ "none" ] ,
4445 grant_types_supported : [ "authorization_code" , "refresh_token" ] ,
@@ -543,4 +544,72 @@ describe("McpOAuthClientProvider", () => {
543544 expect ( stopCallbackServer ) . toHaveBeenCalledTimes ( 1 )
544545 } )
545546 } )
547+
548+ describe ( "registerClientIfNeeded" , ( ) => {
549+ it ( "should reuse cached client_id when redirect_uri matches" , async ( ) => {
550+ setupCallbackServerMock ( )
551+ const secretStorage = createMockSecretStorage ( )
552+
553+ // Pre-populate storage with cached data
554+ await secretStorage . saveOAuthData ( "https://example.com/mcp" , {
555+ tokens : { access_token : "cached-token" , token_type : "Bearer" } ,
556+ expires_at : Date . now ( ) + 3600000 ,
557+ client_id : "cached-client-id" ,
558+ redirect_uri : "http://localhost:12345/callback" ,
559+ } )
560+
561+ const provider = await McpOAuthClientProvider . create ( "https://example.com/mcp" , secretStorage )
562+ await provider . registerClientIfNeeded ( )
563+
564+ expect ( ( await provider . clientInformation ( ) ) ?. client_id ) . toBe ( "cached-client-id" )
565+ await provider . close ( )
566+ } )
567+
568+ it ( "should not reuse cached client_id when redirect_uri does not match" , async ( ) => {
569+ setupCallbackServerMock ( )
570+ const secretStorage = createMockSecretStorage ( )
571+
572+ // Clear previous mocks and set up for this test
573+ mockFetch . mockClear ( )
574+ mockFetch . mockResolvedValueOnce ( {
575+ ok : true ,
576+ json : ( ) =>
577+ Promise . resolve ( {
578+ issuer : "https://auth.example.com" ,
579+ authorization_endpoint : "https://auth.example.com/authorize" ,
580+ token_endpoint : "https://auth.example.com/token" ,
581+ registration_endpoint : "https://auth.example.com/register" ,
582+ response_types_supported : [ "code" ] ,
583+ token_endpoint_auth_methods_supported : [ "none" ] ,
584+ grant_types_supported : [ "authorization_code" , "refresh_token" ] ,
585+ } ) ,
586+ } )
587+ mockFetch . mockResolvedValueOnce ( {
588+ ok : true ,
589+ json : ( ) =>
590+ Promise . resolve ( {
591+ client_id : "new-client-id" ,
592+ redirect_uris : [ "http://localhost:12345/callback" ] ,
593+ client_name : "Roo Code" ,
594+ grant_types : [ "authorization_code" , "refresh_token" ] ,
595+ response_types : [ "code" ] ,
596+ token_endpoint_auth_method : "none" ,
597+ } ) ,
598+ } )
599+
600+ // Pre-populate storage with cached data with different redirect_uri
601+ await secretStorage . saveOAuthData ( "https://example.com/mcp" , {
602+ tokens : { access_token : "cached-token" , token_type : "Bearer" } ,
603+ expires_at : Date . now ( ) + 3600000 ,
604+ client_id : "cached-client-id" ,
605+ redirect_uri : "http://localhost:99999/callback" , // different port
606+ } )
607+
608+ const provider = await McpOAuthClientProvider . create ( "https://example.com/mcp" , secretStorage )
609+ await provider . registerClientIfNeeded ( )
610+
611+ expect ( ( await provider . clientInformation ( ) ) ?. client_id ) . toBe ( "new-client-id" )
612+ await provider . close ( )
613+ } )
614+ } )
546615} )
0 commit comments