@@ -19,7 +19,8 @@ import {
19
19
} from "@modelcontextprotocol/sdk/types.js" ;
20
20
import { OAuthTokensSchema } from "@modelcontextprotocol/sdk/shared/auth.js" ;
21
21
import { SESSION_KEYS , getServerSpecificKey } from "./lib/constants" ;
22
- import { AuthDebuggerState } from "./lib/auth-types" ;
22
+ import { AuthDebuggerState , EMPTY_DEBUGGER_STATE } from "./lib/auth-types" ;
23
+ import { OAuthStateMachine } from "./lib/oauth-state-machine" ;
23
24
import { cacheToolOutputSchemas } from "./utils/schemaUtils" ;
24
25
import React , {
25
26
Suspense ,
@@ -29,7 +30,10 @@ import React, {
29
30
useState ,
30
31
} from "react" ;
31
32
import { useConnection } from "./lib/hooks/useConnection" ;
32
- import { useDraggablePane } from "./lib/hooks/useDraggablePane" ;
33
+ import {
34
+ useDraggablePane ,
35
+ useDraggableSidebar ,
36
+ } from "./lib/hooks/useDraggablePane" ;
33
37
import { StdErrNotification } from "./lib/notificationTypes" ;
34
38
35
39
import { Tabs , TabsContent , TabsList , TabsTrigger } from "@/components/ui/tabs" ;
@@ -121,19 +125,8 @@ const App = () => {
121
125
const [ isAuthDebuggerVisible , setIsAuthDebuggerVisible ] = useState ( false ) ;
122
126
123
127
// Auth debugger state
124
- const [ authState , setAuthState ] = useState < AuthDebuggerState > ( {
125
- isInitiatingAuth : false ,
126
- oauthTokens : null ,
127
- loading : true ,
128
- oauthStep : "metadata_discovery" ,
129
- oauthMetadata : null ,
130
- oauthClientInfo : null ,
131
- authorizationUrl : null ,
132
- authorizationCode : "" ,
133
- latestError : null ,
134
- statusMessage : null ,
135
- validationError : null ,
136
- } ) ;
128
+ const [ authState , setAuthState ] =
129
+ useState < AuthDebuggerState > ( EMPTY_DEBUGGER_STATE ) ;
137
130
138
131
// Helper function to update specific auth state properties
139
132
const updateAuthState = ( updates : Partial < AuthDebuggerState > ) => {
@@ -164,6 +157,11 @@ const App = () => {
164
157
const progressTokenRef = useRef ( 0 ) ;
165
158
166
159
const { height : historyPaneHeight , handleDragStart } = useDraggablePane ( 300 ) ;
160
+ const {
161
+ width : sidebarWidth ,
162
+ isDragging : isSidebarDragging ,
163
+ handleDragStart : handleSidebarDragStart ,
164
+ } = useDraggableSidebar ( 320 ) ;
167
165
168
166
const {
169
167
connectionStatus,
@@ -243,27 +241,81 @@ const App = () => {
243
241
244
242
// Update OAuth debug state during debug callback
245
243
const onOAuthDebugConnect = useCallback (
246
- ( {
244
+ async ( {
247
245
authorizationCode,
248
246
errorMsg,
247
+ restoredState,
249
248
} : {
250
249
authorizationCode ?: string ;
251
250
errorMsg ?: string ;
251
+ restoredState ?: AuthDebuggerState ;
252
252
} ) => {
253
253
setIsAuthDebuggerVisible ( true ) ;
254
- if ( authorizationCode ) {
254
+
255
+ if ( errorMsg ) {
255
256
updateAuthState ( {
256
- authorizationCode,
257
- oauthStep : "token_request" ,
257
+ latestError : new Error ( errorMsg ) ,
258
258
} ) ;
259
+ return ;
259
260
}
260
- if ( errorMsg ) {
261
+
262
+ if ( restoredState && authorizationCode ) {
263
+ // Restore the previous auth state and continue the OAuth flow
264
+ let currentState : AuthDebuggerState = {
265
+ ...restoredState ,
266
+ authorizationCode,
267
+ oauthStep : "token_request" ,
268
+ isInitiatingAuth : true ,
269
+ statusMessage : null ,
270
+ latestError : null ,
271
+ } ;
272
+
273
+ try {
274
+ // Create a new state machine instance to continue the flow
275
+ const stateMachine = new OAuthStateMachine ( sseUrl , ( updates ) => {
276
+ currentState = { ...currentState , ...updates } ;
277
+ } ) ;
278
+
279
+ // Continue stepping through the OAuth flow from where we left off
280
+ while (
281
+ currentState . oauthStep !== "complete" &&
282
+ currentState . oauthStep !== "authorization_code"
283
+ ) {
284
+ await stateMachine . executeStep ( currentState ) ;
285
+ }
286
+
287
+ if ( currentState . oauthStep === "complete" ) {
288
+ // After the flow completes or reaches a user-input step, update the app state
289
+ updateAuthState ( {
290
+ ...currentState ,
291
+ statusMessage : {
292
+ type : "success" ,
293
+ message : "Authentication completed successfully" ,
294
+ } ,
295
+ isInitiatingAuth : false ,
296
+ } ) ;
297
+ }
298
+ } catch ( error ) {
299
+ console . error ( "OAuth continuation error:" , error ) ;
300
+ updateAuthState ( {
301
+ latestError :
302
+ error instanceof Error ? error : new Error ( String ( error ) ) ,
303
+ statusMessage : {
304
+ type : "error" ,
305
+ message : `Failed to complete OAuth flow: ${ error instanceof Error ? error . message : String ( error ) } ` ,
306
+ } ,
307
+ isInitiatingAuth : false ,
308
+ } ) ;
309
+ }
310
+ } else if ( authorizationCode ) {
311
+ // Fallback to the original behavior if no state was restored
261
312
updateAuthState ( {
262
- latestError : new Error ( errorMsg ) ,
313
+ authorizationCode,
314
+ oauthStep : "token_request" ,
263
315
} ) ;
264
316
}
265
317
} ,
266
- [ ] ,
318
+ [ sseUrl ] ,
267
319
) ;
268
320
269
321
// Load OAuth tokens when sseUrl changes
@@ -285,8 +337,6 @@ const App = () => {
285
337
}
286
338
} catch ( error ) {
287
339
console . error ( "Error loading OAuth tokens:" , error ) ;
288
- } finally {
289
- updateAuthState ( { loading : false } ) ;
290
340
}
291
341
} ;
292
342
@@ -565,32 +615,58 @@ const App = () => {
565
615
566
616
return (
567
617
< div className = "flex h-screen bg-background" >
568
- < Sidebar
569
- connectionStatus = { connectionStatus }
570
- transportType = { transportType }
571
- setTransportType = { setTransportType }
572
- command = { command }
573
- setCommand = { setCommand }
574
- args = { args }
575
- setArgs = { setArgs }
576
- sseUrl = { sseUrl }
577
- setSseUrl = { setSseUrl }
578
- env = { env }
579
- setEnv = { setEnv }
580
- config = { config }
581
- setConfig = { setConfig }
582
- bearerToken = { bearerToken }
583
- setBearerToken = { setBearerToken }
584
- headerName = { headerName }
585
- setHeaderName = { setHeaderName }
586
- onConnect = { connectMcpServer }
587
- onDisconnect = { disconnectMcpServer }
588
- stdErrNotifications = { stdErrNotifications }
589
- logLevel = { logLevel }
590
- sendLogLevelRequest = { sendLogLevelRequest }
591
- loggingSupported = { ! ! serverCapabilities ?. logging || false }
592
- clearStdErrNotifications = { clearStdErrNotifications }
593
- />
618
+ < div
619
+ style = { {
620
+ width : sidebarWidth ,
621
+ minWidth : 200 ,
622
+ maxWidth : 600 ,
623
+ transition : isSidebarDragging ? "none" : "width 0.15s" ,
624
+ } }
625
+ className = "bg-card border-r border-border flex flex-col h-full relative"
626
+ >
627
+ < Sidebar
628
+ connectionStatus = { connectionStatus }
629
+ transportType = { transportType }
630
+ setTransportType = { setTransportType }
631
+ command = { command }
632
+ setCommand = { setCommand }
633
+ args = { args }
634
+ setArgs = { setArgs }
635
+ sseUrl = { sseUrl }
636
+ setSseUrl = { setSseUrl }
637
+ env = { env }
638
+ setEnv = { setEnv }
639
+ config = { config }
640
+ setConfig = { setConfig }
641
+ bearerToken = { bearerToken }
642
+ setBearerToken = { setBearerToken }
643
+ headerName = { headerName }
644
+ setHeaderName = { setHeaderName }
645
+ onConnect = { connectMcpServer }
646
+ onDisconnect = { disconnectMcpServer }
647
+ stdErrNotifications = { stdErrNotifications }
648
+ logLevel = { logLevel }
649
+ sendLogLevelRequest = { sendLogLevelRequest }
650
+ loggingSupported = { ! ! serverCapabilities ?. logging || false }
651
+ clearStdErrNotifications = { clearStdErrNotifications }
652
+ />
653
+ { /* Drag handle for resizing sidebar */ }
654
+ < div
655
+ onMouseDown = { handleSidebarDragStart }
656
+ style = { {
657
+ cursor : "col-resize" ,
658
+ position : "absolute" ,
659
+ top : 0 ,
660
+ right : 0 ,
661
+ width : 6 ,
662
+ height : "100%" ,
663
+ zIndex : 10 ,
664
+ background : isSidebarDragging ? "rgba(0,0,0,0.08)" : "transparent" ,
665
+ } }
666
+ aria-label = "Resize sidebar"
667
+ data-testid = "sidebar-drag-handle"
668
+ />
669
+ </ div >
594
670
< div className = "flex-1 flex flex-col overflow-hidden" >
595
671
< div className = "flex-1 overflow-auto" >
596
672
{ mcpClient ? (
0 commit comments