@@ -36,7 +36,7 @@ const mockOAuthClientInfo = {
3636// Mock MCP SDK functions - must be before imports
3737jest . mock ( "@modelcontextprotocol/sdk/client/auth.js" , ( ) => ( {
3838 auth : jest . fn ( ) ,
39- discoverOAuthMetadata : jest . fn ( ) ,
39+ discoverAuthorizationServerMetadata : jest . fn ( ) ,
4040 registerClient : jest . fn ( ) ,
4141 startAuthorization : jest . fn ( ) ,
4242 exchangeAuthorization : jest . fn ( ) ,
@@ -46,7 +46,7 @@ jest.mock("@modelcontextprotocol/sdk/client/auth.js", () => ({
4646
4747// Import the functions to get their types
4848import {
49- discoverOAuthMetadata ,
49+ discoverAuthorizationServerMetadata ,
5050 registerClient ,
5151 startAuthorization ,
5252 exchangeAuthorization ,
@@ -57,9 +57,10 @@ import { OAuthMetadata } from "@modelcontextprotocol/sdk/shared/auth.js";
5757import { EMPTY_DEBUGGER_STATE } from "@/lib/auth-types" ;
5858
5959// Type the mocked functions properly
60- const mockDiscoverOAuthMetadata = discoverOAuthMetadata as jest . MockedFunction <
61- typeof discoverOAuthMetadata
62- > ;
60+ const mockDiscoverAuthorizationServerMetadata =
61+ discoverAuthorizationServerMetadata as jest . MockedFunction <
62+ typeof discoverAuthorizationServerMetadata
63+ > ;
6364const mockRegisterClient = registerClient as jest . MockedFunction <
6465 typeof registerClient
6566> ;
@@ -102,7 +103,9 @@ describe("AuthDebugger", () => {
102103 // Suppress console errors in tests to avoid JSDOM navigation noise
103104 jest . spyOn ( console , "error" ) . mockImplementation ( ( ) => { } ) ;
104105
105- mockDiscoverOAuthMetadata . mockResolvedValue ( mockOAuthMetadata ) ;
106+ mockDiscoverAuthorizationServerMetadata . mockResolvedValue (
107+ mockOAuthMetadata ,
108+ ) ;
106109 mockRegisterClient . mockResolvedValue ( mockOAuthClientInfo ) ;
107110 mockDiscoverOAuthProtectedResourceMetadata . mockRejectedValue (
108111 new Error ( "No protected resource metadata found" ) ,
@@ -203,7 +206,7 @@ describe("AuthDebugger", () => {
203206 } ) ;
204207
205208 // Should first discover and save OAuth metadata
206- expect ( mockDiscoverOAuthMetadata ) . toHaveBeenCalledWith (
209+ expect ( mockDiscoverAuthorizationServerMetadata ) . toHaveBeenCalledWith (
207210 new URL ( "https://example.com/" ) ,
208211 ) ;
209212
@@ -216,7 +219,7 @@ describe("AuthDebugger", () => {
216219 } ) ;
217220
218221 it ( "should show error when quick OAuth flow fails to discover metadata" , async ( ) => {
219- mockDiscoverOAuthMetadata . mockRejectedValue (
222+ mockDiscoverAuthorizationServerMetadata . mockRejectedValue (
220223 new Error ( "Metadata discovery failed" ) ,
221224 ) ;
222225
@@ -362,7 +365,7 @@ describe("AuthDebugger", () => {
362365 fireEvent . click ( screen . getByText ( "Continue" ) ) ;
363366 } ) ;
364367
365- expect ( mockDiscoverOAuthMetadata ) . toHaveBeenCalledWith (
368+ expect ( mockDiscoverAuthorizationServerMetadata ) . toHaveBeenCalledWith (
366369 new URL ( "https://example.com/" ) ,
367370 ) ;
368371 } ) ;
@@ -439,6 +442,103 @@ describe("AuthDebugger", () => {
439442 } ) ;
440443 } ) ;
441444
445+ describe ( "Client Registration behavior" , ( ) => {
446+ it ( "uses preregistered (static) client information without calling DCR" , async ( ) => {
447+ const preregClientInfo = {
448+ client_id : "static_client_id" ,
449+ client_secret : "static_client_secret" ,
450+ redirect_uris : [ "http://localhost:3000/oauth/callback/debug" ] ,
451+ } ;
452+
453+ // Return preregistered client info for the server-specific key
454+ sessionStorageMock . getItem . mockImplementation ( ( key ) => {
455+ if (
456+ key ===
457+ `[${ defaultProps . serverUrl } ] ${ SESSION_KEYS . PREREGISTERED_CLIENT_INFORMATION } `
458+ ) {
459+ return JSON . stringify ( preregClientInfo ) ;
460+ }
461+ return null ;
462+ } ) ;
463+
464+ const updateAuthState = jest . fn ( ) ;
465+
466+ await act ( async ( ) => {
467+ renderAuthDebugger ( {
468+ updateAuthState,
469+ authState : {
470+ ...defaultAuthState ,
471+ isInitiatingAuth : false ,
472+ oauthStep : "client_registration" ,
473+ oauthMetadata : mockOAuthMetadata as unknown as OAuthMetadata ,
474+ } ,
475+ } ) ;
476+ } ) ;
477+
478+ // Proceed from client_registration → authorization_redirect
479+ await act ( async ( ) => {
480+ fireEvent . click ( screen . getByText ( "Continue" ) ) ;
481+ } ) ;
482+
483+ // Should NOT attempt dynamic client registration
484+ expect ( mockRegisterClient ) . not . toHaveBeenCalled ( ) ;
485+
486+ // Should advance with the preregistered client info
487+ expect ( updateAuthState ) . toHaveBeenCalledWith (
488+ expect . objectContaining ( {
489+ oauthClientInfo : expect . objectContaining ( {
490+ client_id : "static_client_id" ,
491+ } ) ,
492+ oauthStep : "authorization_redirect" ,
493+ } ) ,
494+ ) ;
495+ } ) ;
496+
497+ it ( "falls back to DCR when no static client information is available" , async ( ) => {
498+ // No preregistered or dynamic client info present in session storage
499+ sessionStorageMock . getItem . mockImplementation ( ( ) => null ) ;
500+
501+ // DCR returns a new client
502+ mockRegisterClient . mockResolvedValueOnce ( mockOAuthClientInfo ) ;
503+
504+ const updateAuthState = jest . fn ( ) ;
505+
506+ await act ( async ( ) => {
507+ renderAuthDebugger ( {
508+ updateAuthState,
509+ authState : {
510+ ...defaultAuthState ,
511+ isInitiatingAuth : false ,
512+ oauthStep : "client_registration" ,
513+ oauthMetadata : mockOAuthMetadata as unknown as OAuthMetadata ,
514+ } ,
515+ } ) ;
516+ } ) ;
517+
518+ await act ( async ( ) => {
519+ fireEvent . click ( screen . getByText ( "Continue" ) ) ;
520+ } ) ;
521+
522+ expect ( mockRegisterClient ) . toHaveBeenCalledTimes ( 1 ) ;
523+
524+ // Should save and advance with the DCR client info
525+ expect ( updateAuthState ) . toHaveBeenCalledWith (
526+ expect . objectContaining ( {
527+ oauthClientInfo : expect . objectContaining ( {
528+ client_id : "test_client_id" ,
529+ } ) ,
530+ oauthStep : "authorization_redirect" ,
531+ } ) ,
532+ ) ;
533+
534+ // Verify the dynamically registered client info was persisted
535+ expect ( sessionStorage . setItem ) . toHaveBeenCalledWith (
536+ `[${ defaultProps . serverUrl } ] ${ SESSION_KEYS . CLIENT_INFORMATION } ` ,
537+ expect . any ( String ) ,
538+ ) ;
539+ } ) ;
540+ } ) ;
541+
442542 describe ( "OAuth State Persistence" , ( ) => {
443543 it ( "should store auth state to sessionStorage before redirect in Quick OAuth Flow" , async ( ) => {
444544 const updateAuthState = jest . fn ( ) ;
@@ -509,7 +609,9 @@ describe("AuthDebugger", () => {
509609 mockDiscoverOAuthProtectedResourceMetadata . mockResolvedValue (
510610 mockResourceMetadata ,
511611 ) ;
512- mockDiscoverOAuthMetadata . mockResolvedValue ( mockOAuthMetadata ) ;
612+ mockDiscoverAuthorizationServerMetadata . mockResolvedValue (
613+ mockOAuthMetadata ,
614+ ) ;
513615
514616 await act ( async ( ) => {
515617 renderAuthDebugger ( {
@@ -563,7 +665,9 @@ describe("AuthDebugger", () => {
563665 // Mock failed metadata discovery
564666 mockDiscoverOAuthProtectedResourceMetadata . mockRejectedValue ( mockError ) ;
565667 // But OAuth metadata should still work with the original URL
566- mockDiscoverOAuthMetadata . mockResolvedValue ( mockOAuthMetadata ) ;
668+ mockDiscoverAuthorizationServerMetadata . mockResolvedValue (
669+ mockOAuthMetadata ,
670+ ) ;
567671
568672 await act ( async ( ) => {
569673 renderAuthDebugger ( {
@@ -603,7 +707,7 @@ describe("AuthDebugger", () => {
603707 } ) ;
604708
605709 // Verify that regular OAuth metadata discovery was still called
606- expect ( mockDiscoverOAuthMetadata ) . toHaveBeenCalledWith (
710+ expect ( mockDiscoverAuthorizationServerMetadata ) . toHaveBeenCalledWith (
607711 new URL ( "https://example.com/" ) ,
608712 ) ;
609713 } ) ;
0 commit comments