@@ -8,14 +8,14 @@ import {
8
8
startAuthorization ,
9
9
exchangeAuthorization ,
10
10
} from "@modelcontextprotocol/sdk/client/auth.js" ;
11
- import { SESSION_KEYS } from "../lib/constants" ;
12
11
import {
13
12
OAuthMetadataSchema ,
14
13
OAuthMetadata ,
15
14
OAuthClientInformation ,
16
15
} from "@modelcontextprotocol/sdk/shared/auth.js" ;
17
16
import { CheckCircle2 , Circle , ExternalLink , AlertCircle } from "lucide-react" ;
18
17
import { AuthDebuggerState } from "../lib/auth-types" ;
18
+ import { SESSION_KEYS , getServerSpecificKey } from "../lib/constants" ;
19
19
20
20
interface AuthDebuggerProps {
21
21
sseUrl : string ;
@@ -24,38 +24,58 @@ interface AuthDebuggerProps {
24
24
updateAuthState : ( updates : Partial < AuthDebuggerState > ) => void ;
25
25
}
26
26
27
- // Enhanced version of the OAuth client provider specifically for debug flows
27
+ // Overrides debug URL and allows saving server OAuth metadata to
28
+ // display in debug UI.
28
29
class DebugInspectorOAuthClientProvider extends InspectorOAuthClientProvider {
29
30
get redirectUrl ( ) : string {
30
31
return `${ window . location . origin } /oauth/callback/debug` ;
31
32
}
33
+
34
+ saveServerMetadata ( metadata : OAuthMetadata ) {
35
+ const key = getServerSpecificKey (
36
+ SESSION_KEYS . SERVER_METADATA ,
37
+ this . serverUrl ,
38
+ ) ;
39
+ sessionStorage . setItem ( key , JSON . stringify ( metadata ) ) ;
40
+ }
41
+
42
+ getServerMetadata ( ) : OAuthMetadata | null {
43
+ const key = getServerSpecificKey (
44
+ SESSION_KEYS . SERVER_METADATA ,
45
+ this . serverUrl ,
46
+ ) ;
47
+ const metadata = sessionStorage . getItem ( key ) ;
48
+ if ( ! metadata ) {
49
+ return null ;
50
+ }
51
+ return JSON . parse ( metadata ) ;
52
+ }
32
53
}
33
54
34
- const AuthDebugger = ( { sseUrl, onBack, authState, updateAuthState } : AuthDebuggerProps ) => {
35
- // Load client info asynchronously when we have a debug code
36
- useEffect ( ( ) => {
37
- const debugCode = sessionStorage . getItem ( SESSION_KEYS . DEBUG_CODE ) ;
38
- if ( debugCode && sseUrl && authState . oauthStep === "token_request" ) {
39
- // Load client info asynchronously
40
- const provider = new DebugInspectorOAuthClientProvider ( sseUrl ) ;
41
- provider
42
- . clientInformation ( )
43
- . then ( ( info ) => {
44
- updateAuthState ( { oauthClientInfo : info || null } ) ;
45
- } )
46
- . catch ( ( error ) => {
47
- console . error ( "Failed to load client information:" , error ) ;
48
- } ) ;
55
+ const AuthDebugger = ( {
56
+ sseUrl,
57
+ onBack,
58
+ authState,
59
+ updateAuthState,
60
+ } : AuthDebuggerProps ) => {
61
+ // Load client info asynchronously when we're at the token_request step
62
+
63
+ const validateOAuthMetadata = async (
64
+ provider : DebugInspectorOAuthClientProvider ,
65
+ ) : Promise < OAuthMetadata > => {
66
+ const metadata = provider . getServerMetadata ( ) ;
67
+ if ( metadata ) {
68
+ return metadata ;
49
69
}
50
- } , [ sseUrl , authState . oauthStep , updateAuthState ] ) ;
51
70
52
- const validateOAuthMetadata = (
53
- metadata : OAuthMetadata | null ,
54
- ) : OAuthMetadata => {
55
- if ( ! metadata ) {
56
- throw new Error ( "Can't advance without successfully fetching metadata" ) ;
71
+ const fetchedMetadata = await discoverOAuthMetadata ( sseUrl ) ;
72
+ if ( ! fetchedMetadata ) {
73
+ throw new Error ( "Failed to discover OAuth metadata" ) ;
57
74
}
58
- return metadata ;
75
+ const parsedMetadata =
76
+ await OAuthMetadataSchema . parseAsync ( fetchedMetadata ) ;
77
+
78
+ return parsedMetadata ;
59
79
} ;
60
80
61
81
const validateClientInformation = async (
@@ -108,8 +128,9 @@ const AuthDebugger = ({ sseUrl, onBack, authState, updateAuthState }: AuthDebugg
108
128
}
109
129
const parsedMetadata = await OAuthMetadataSchema . parseAsync ( metadata ) ;
110
130
updateAuthState ( { oauthMetadata : parsedMetadata } ) ;
131
+ provider . saveServerMetadata ( parsedMetadata ) ;
111
132
} else if ( authState . oauthStep === "metadata_discovery" ) {
112
- const metadata = validateOAuthMetadata ( authState . oauthMetadata ) ;
133
+ const metadata = await validateOAuthMetadata ( provider ) ;
113
134
114
135
updateAuthState ( { oauthStep : "client_registration" } ) ;
115
136
@@ -127,7 +148,7 @@ const AuthDebugger = ({ sseUrl, onBack, authState, updateAuthState }: AuthDebugg
127
148
provider . saveClientInformation ( fullInformation ) ;
128
149
updateAuthState ( { oauthClientInfo : fullInformation } ) ;
129
150
} else if ( authState . oauthStep === "client_registration" ) {
130
- const metadata = validateOAuthMetadata ( authState . oauthMetadata ) ;
151
+ const metadata = await validateOAuthMetadata ( provider ) ;
131
152
const clientInformation = await validateClientInformation ( provider ) ;
132
153
updateAuthState ( { oauthStep : "authorization_redirect" } ) ;
133
154
try {
@@ -158,7 +179,10 @@ const AuthDebugger = ({ sseUrl, onBack, authState, updateAuthState }: AuthDebugg
158
179
) ;
159
180
}
160
181
} else if ( authState . oauthStep === "authorization_code" ) {
161
- if ( ! authState . authorizationCode || authState . authorizationCode . trim ( ) === "" ) {
182
+ if (
183
+ ! authState . authorizationCode ||
184
+ authState . authorizationCode . trim ( ) === ""
185
+ ) {
162
186
updateAuthState ( {
163
187
validationError : "You need to provide an authorization code" ,
164
188
} ) ;
@@ -167,7 +191,7 @@ const AuthDebugger = ({ sseUrl, onBack, authState, updateAuthState }: AuthDebugg
167
191
updateAuthState ( { validationError : null , oauthStep : "token_request" } ) ;
168
192
} else if ( authState . oauthStep === "token_request" ) {
169
193
const codeVerifier = provider . codeVerifier ( ) ;
170
- const metadata = validateOAuthMetadata ( authState . oauthMetadata ) ;
194
+ const metadata = await validateOAuthMetadata ( provider ) ;
171
195
const clientInformation = await validateClientInformation ( provider ) ;
172
196
173
197
const tokens = await exchangeAuthorization ( sseUrl , {
@@ -285,6 +309,7 @@ const AuthDebugger = ({ sseUrl, onBack, authState, updateAuthState }: AuthDebugg
285
309
} , [ authState . statusMessage ] ) ;
286
310
287
311
const renderOAuthFlow = useCallback ( ( ) => {
312
+ const provider = new DebugInspectorOAuthClientProvider ( sseUrl ) ;
288
313
const steps = [
289
314
{
290
315
key : "not_started" ,
@@ -294,14 +319,14 @@ const AuthDebugger = ({ sseUrl, onBack, authState, updateAuthState }: AuthDebugg
294
319
{
295
320
key : "metadata_discovery" ,
296
321
label : "Metadata Discovery" ,
297
- metadata : authState . oauthMetadata && (
322
+ metadata : provider . getServerMetadata ( ) && (
298
323
< details className = "text-xs mt-2" >
299
324
< summary className = "cursor-pointer text-muted-foreground font-medium" >
300
325
Retrieved OAuth Metadata from { sseUrl }
301
326
/.well-known/oauth-authorization-server
302
327
</ summary >
303
328
< pre className = "mt-2 p-2 bg-muted rounded-md overflow-auto max-h-[300px]" >
304
- { JSON . stringify ( authState . oauthMetadata , null , 2 ) }
329
+ { JSON . stringify ( provider . getServerMetadata ( ) , null , 2 ) }
305
330
</ pre >
306
331
</ details >
307
332
) ,
@@ -491,15 +516,17 @@ const AuthDebugger = ({ sseUrl, onBack, authState, updateAuthState }: AuthDebugg
491
516
authState . authorizationUrl && (
492
517
< Button
493
518
variant = "outline"
494
- onClick = { ( ) => window . open ( authState . authorizationUrl ! , "_blank" ) }
519
+ onClick = { ( ) =>
520
+ window . open ( authState . authorizationUrl ! , "_blank" )
521
+ }
495
522
>
496
523
Open in New Tab
497
524
</ Button >
498
525
) }
499
526
</ div >
500
527
</ div >
501
528
) ;
502
- } , [ authState , sseUrl ] ) ;
529
+ } , [ authState , sseUrl , proceedToNextStep , updateAuthState ] ) ;
503
530
504
531
return (
505
532
< div className = "w-full p-4" >
@@ -581,4 +608,4 @@ const AuthDebugger = ({ sseUrl, onBack, authState, updateAuthState }: AuthDebugg
581
608
) ;
582
609
} ;
583
610
584
- export default AuthDebugger ;
611
+ export default AuthDebugger ;
0 commit comments