@@ -20,6 +20,9 @@ import {
20
20
} from "@modelcontextprotocol/sdk/shared/auth.js" ;
21
21
import { CheckCircle2 , Circle , ExternalLink } from "lucide-react" ;
22
22
23
+ // Type for the toast function from the useToast hook
24
+ type ToastFunction = ReturnType < typeof useToast > [ "toast" ] ;
25
+
23
26
interface AuthDebuggerProps {
24
27
sseUrl : string ;
25
28
onBack : ( ) => void ;
@@ -37,14 +40,14 @@ type OAuthStep =
37
40
38
41
// Enhanced version of the OAuth client provider specifically for debug flows
39
42
class DebugInspectorOAuthClientProvider extends InspectorOAuthClientProvider {
40
- get redirectUrl ( ) {
41
- return window . location . origin + " /oauth/callback/debug" ;
43
+ get redirectUrl ( ) : string {
44
+ return ` ${ window . location . origin } /oauth/callback/debug` ;
42
45
}
43
46
}
44
47
45
48
const validateOAuthMetadata = (
46
49
metadata : OAuthMetadata | null ,
47
- toast : ( arg0 : object ) => void ,
50
+ toast : ToastFunction ,
48
51
) : OAuthMetadata => {
49
52
if ( ! metadata ) {
50
53
toast ( {
@@ -59,7 +62,7 @@ const validateOAuthMetadata = (
59
62
60
63
const validateClientInformation = async (
61
64
provider : DebugInspectorOAuthClientProvider ,
62
- toast : ( arg0 : object ) => void ,
65
+ toast : ToastFunction ,
63
66
) : Promise < OAuthClientInformation > => {
64
67
const clientInformation = await provider . clientInformation ( ) ;
65
68
@@ -115,25 +118,29 @@ const AuthDebugger = ({ sseUrl, onBack }: AuthDebuggerProps) => {
115
118
loadOAuthTokens ( ) ;
116
119
} , [ sseUrl ] ) ; // Check for debug callback code
117
120
121
+ // Check for debug callback code and load client info
118
122
useEffect ( ( ) => {
119
- const loadSessionInfo = async ( ) => {
120
- const debugCode = sessionStorage . getItem ( SESSION_KEYS . DEBUG_CODE ) ;
121
- if ( debugCode && sseUrl ) {
122
- // We've returned from a debug OAuth callback with a code
123
- setAuthorizationCode ( debugCode ) ;
124
- setOAuthFlowVisible ( true ) ;
125
-
126
- // Set the OAuth flow step to token request
127
- setOAuthStep ( "token_request" ) ;
128
- const provider = new DebugInspectorOAuthClientProvider ( sseUrl ) ;
129
- setOAuthClientInfo ( ( await provider . clientInformation ( ) ) || null ) ;
130
-
131
- // Now that we've processed it, clear the debug code
132
- sessionStorage . removeItem ( SESSION_KEYS . DEBUG_CODE ) ;
133
- }
134
- } ;
123
+ const debugCode = sessionStorage . getItem ( SESSION_KEYS . DEBUG_CODE ) ;
124
+ if ( debugCode && sseUrl ) {
125
+ // We've returned from a debug OAuth callback with a code
126
+ setAuthorizationCode ( debugCode ) ;
127
+ setOAuthFlowVisible ( true ) ;
128
+ setOAuthStep ( "token_request" ) ;
129
+
130
+ // Load client info asynchronously
131
+ const provider = new DebugInspectorOAuthClientProvider ( sseUrl ) ;
132
+ provider
133
+ . clientInformation ( )
134
+ . then ( ( info ) => {
135
+ setOAuthClientInfo ( info || null ) ;
136
+ } )
137
+ . catch ( ( error ) => {
138
+ console . error ( "Failed to load client information:" , error ) ;
139
+ } ) ;
135
140
136
- loadSessionInfo ( ) ;
141
+ // Now that we've processed it, clear the debug code
142
+ sessionStorage . removeItem ( SESSION_KEYS . DEBUG_CODE ) ;
143
+ }
137
144
} , [ sseUrl ] ) ;
138
145
139
146
const startOAuthFlow = ( ) => {
@@ -177,39 +184,56 @@ const AuthDebugger = ({ sseUrl, onBack }: AuthDebuggerProps) => {
177
184
178
185
setOAuthStep ( "client_registration" ) ;
179
186
187
+ const clientMetadata = provider . clientMetadata ;
188
+ // Add all supported scopes to client registration.
189
+ // This is the maximal set of scopes the client can request, the
190
+ // scope of the actual token is specified below
191
+ if ( metadata . scopes_supported ) {
192
+ // TODO: add this to schema
193
+ clientMetadata [ "scope" ] = metadata . scopes_supported . join ( " " ) ;
194
+ }
195
+
180
196
const fullInformation = await registerClient ( sseUrl , {
181
197
metadata,
182
- clientMetadata : provider . clientMetadata ,
198
+ clientMetadata,
183
199
} ) ;
184
200
185
201
provider . saveClientInformation ( fullInformation ) ;
202
+ // save it here to be more convenient for display
203
+ setOAuthClientInfo ( fullInformation ) ;
186
204
} else if ( oauthStep === "client_registration" ) {
187
205
const metadata = validateOAuthMetadata ( oauthMetadata , toast ) ;
188
206
const clientInformation = await validateClientInformation (
189
207
provider ,
190
208
toast ,
191
209
) ;
192
210
setOAuthStep ( "authorization_redirect" ) ;
193
- // This custom implementation captures the OAuth flow step by step
194
- // First, get or register the client
195
211
try {
196
212
const { authorizationUrl, codeVerifier } = await startAuthorization (
197
213
sseUrl ,
198
214
{
199
215
metadata,
200
216
clientInformation,
201
217
redirectUrl : provider . redirectUrl ,
218
+ // TODO: fix this once SDK PR is merged
219
+ // scope: metadata.scopes_supported,
202
220
} ,
203
221
) ;
204
222
205
223
provider . saveCodeVerifier ( codeVerifier ) ;
206
- // Save this so the debug callback knows what to do
207
- // await sessionStorage.setItem(SESSION_KEYS.SERVER_URL, sseUrl);
208
- setAuthorizationUrl ( authorizationUrl . toString ( ) ) ;
209
- // await provider.redirectToAuthorization(authorizationUrl);
210
- setOAuthStep ( "authorization_code" ) ;
211
224
212
- // await auth(serverAuthProvider, { serverUrl: sseUrl });
225
+ // TODO: remove this once scope is valid parameter above
226
+ // Modify the authorization URL to include all supported scopes
227
+ if ( metadata . scopes_supported ) {
228
+ //Add all supported scopes to the authorization URL
229
+ const url = new URL ( authorizationUrl . toString ( ) ) ;
230
+ url . searchParams . set ( "scope" , metadata . scopes_supported . join ( " " ) ) ;
231
+ setAuthorizationUrl ( url . toString ( ) ) ;
232
+ } else {
233
+ setAuthorizationUrl ( authorizationUrl . toString ( ) ) ;
234
+ }
235
+
236
+ setOAuthStep ( "authorization_code" ) ;
213
237
} catch ( error ) {
214
238
console . error ( "OAuth flow step error:" , error ) ;
215
239
toast ( {
@@ -237,7 +261,6 @@ const AuthDebugger = ({ sseUrl, onBack }: AuthDebuggerProps) => {
237
261
toast ,
238
262
) ;
239
263
240
- // const clientInformation = await provider.clientInformation();
241
264
const tokens = await exchangeAuthorization ( sseUrl , {
242
265
metadata,
243
266
clientInformation,
@@ -308,6 +331,7 @@ const AuthDebugger = ({ sseUrl, onBack }: AuthDebuggerProps) => {
308
331
setOAuthStep ( "not_started" ) ;
309
332
setOAuthFlowVisible ( false ) ;
310
333
setLatestError ( null ) ;
334
+ setOAuthClientInfo ( null ) ;
311
335
setAuthorizationCode ( "" ) ;
312
336
toast ( {
313
337
title : "Success" ,
@@ -329,7 +353,8 @@ const AuthDebugger = ({ sseUrl, onBack }: AuthDebuggerProps) => {
329
353
metadata : oauthMetadata && (
330
354
< details className = "text-xs mt-2" >
331
355
< summary className = "cursor-pointer text-muted-foreground font-medium" >
332
- Retrieved OAuth Metadata
356
+ Retrieved OAuth Metadata from { sseUrl }
357
+ /.well-known/oauth-authorization-server
333
358
</ summary >
334
359
< pre className = "mt-2 p-2 bg-muted rounded-md overflow-auto max-h-[300px]" >
335
360
{ JSON . stringify ( oauthMetadata , null , 2 ) }
@@ -364,6 +389,8 @@ const AuthDebugger = ({ sseUrl, onBack }: AuthDebuggerProps) => {
364
389
target = "_blank"
365
390
rel = "noopener noreferrer"
366
391
className = "flex items-center text-blue-500 hover:text-blue-700"
392
+ aria-label = "Open authorization URL in new tab"
393
+ title = "Open authorization URL"
367
394
>
368
395
< ExternalLink className = "h-4 w-4" />
369
396
</ a >
@@ -428,7 +455,11 @@ const AuthDebugger = ({ sseUrl, onBack }: AuthDebuggerProps) => {
428
455
< summary className = "cursor-pointer text-muted-foreground font-medium" >
429
456
Access Tokens
430
457
</ summary >
431
- < p > Try listTools to use these credentials</ p >
458
+ < p className = "mt-1 text-sm" >
459
+ Authentication successful! You can now use the authenticated
460
+ connection. These tokens will be used automatically for server
461
+ requests.
462
+ </ p >
432
463
< pre className = "mt-2 p-2 bg-muted rounded-md overflow-auto max-h-[300px]" >
433
464
{ JSON . stringify ( oauthTokens , null , 2 ) }
434
465
</ pre >
0 commit comments