@@ -35,7 +35,7 @@ import { RequestOptions } from "@modelcontextprotocol/sdk/shared/protocol.js";
3535import { useEffect , useState } from "react" ;
3636import { useToast } from "@/lib/hooks/useToast" ;
3737import { z } from "zod" ;
38- import { ConnectionStatus , CLIENT_IDENTITY } from "../constants" ;
38+ import { ConnectionStatus , CLIENT_IDENTITY , SESSION_KEYS } from "../constants" ;
3939import { Notification } from "../notificationTypes" ;
4040import {
4141 auth ,
@@ -47,16 +47,22 @@ import {
4747 saveClientInformationToSessionStorage ,
4848 saveScopeToSessionStorage ,
4949 clearScopeFromSessionStorage ,
50- discoverScopes ,
5150} from "../auth" ;
5251import {
5352 getMCPProxyAddress ,
5453 getMCPServerRequestMaxTotalTimeout ,
5554 resetRequestTimeoutOnProgress ,
5655 getMCPProxyAuthToken ,
56+ getMCPServerRequestTimeout ,
5757} from "@/utils/configUtils" ;
58- import { getMCPServerRequestTimeout } from "@/utils/configUtils" ;
5958import { InspectorConfig } from "../configurationTypes" ;
59+ import {
60+ createOAuthProviderForServer ,
61+ setOAuthMode ,
62+ } from "../oauth/provider-factory" ;
63+ import { OAuthStateMachine } from "../oauth-state-machine" ;
64+ import { AuthDebuggerState } from "../auth-types" ;
65+ import { validateRedirectUrl } from "@/utils/urlValidation" ;
6066import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js" ;
6167import { CustomHeaders } from "../types/customHeaders" ;
6268
@@ -153,6 +159,13 @@ export function useConnection({
153159 saveScopeToSessionStorage ( sseUrl , oauthScope ) ;
154160 } , [ oauthScope , sseUrl ] ) ;
155161
162+ // Sync OAuth mode with connection type
163+ useEffect ( ( ) => {
164+ // When connection type is set to proxy, ensure OAuth mode is also proxy
165+ // When connection type is direct, OAuth mode should be direct
166+ setOAuthMode ( connectionType , sseUrl ) ;
167+ } , [ connectionType , sseUrl ] ) ;
168+
156169 const pushHistory = ( request : object , response ?: object ) => {
157170 setRequestHistory ( ( prev ) => [
158171 ...prev ,
@@ -344,28 +357,131 @@ export function useConnection({
344357
345358 const handleAuthError = async ( error : unknown ) => {
346359 if ( is401Error ( error ) ) {
347- let scope = oauthScope ?. trim ( ) ;
348- if ( ! scope ) {
349- // Only discover resource metadata when we need to discover scopes
350- let resourceMetadata ;
351- try {
352- resourceMetadata = await discoverOAuthProtectedResourceMetadata (
353- new URL ( "/" , sseUrl ) ,
360+ const scope = oauthScope ?. trim ( ) ;
361+
362+ // Use connectionType directly instead of reading from session storage
363+ const oauthMode = connectionType ;
364+
365+ if ( scope ) {
366+ saveScopeToSessionStorage ( sseUrl , scope ) ;
367+ }
368+
369+ if ( oauthMode === "proxy" ) {
370+ // Use proxy mode with state machine approach (to avoid CORS)
371+ // Ensure OAuth mode is set in session storage for callback to use
372+ setOAuthMode ( "proxy" , sseUrl ) ;
373+
374+ const proxyAddress = getMCPProxyAddress ( config ) ;
375+ const proxyAuthObj = getMCPProxyAuthToken ( config ) ;
376+ const oauthProvider = createOAuthProviderForServer (
377+ sseUrl ,
378+ proxyAddress ,
379+ proxyAuthObj . token ,
380+ ) ;
381+
382+ // Use state machine to step through OAuth flow
383+ let currentState : AuthDebuggerState = {
384+ oauthStep : "metadata_discovery" ,
385+ authorizationUrl : null ,
386+ authorizationCode : "" ,
387+ oauthMetadata : null ,
388+ oauthClientInfo : null ,
389+ oauthTokens : null ,
390+ resourceMetadata : null ,
391+ resourceMetadataError : null ,
392+ authServerUrl : null ,
393+ resource : null ,
394+ validationError : null ,
395+ latestError : null ,
396+ statusMessage : null ,
397+ isInitiatingAuth : false ,
398+ } ;
399+
400+ // Use regular redirect URL (not debug) for Connect button
401+ // This will redirect to /oauth/callback which auto-connects
402+ const oauthMachine = new OAuthStateMachine (
403+ sseUrl ,
404+ ( updates ) => {
405+ currentState = { ...currentState , ...updates } ;
406+ } ,
407+ oauthProvider ,
408+ false , // useDebugRedirect = false
409+ ) ;
410+
411+ // Step through OAuth flow until we need to redirect
412+ while ( currentState . oauthStep !== "complete" ) {
413+ await oauthMachine . executeStep ( currentState ) ;
414+
415+ // When we reach authorization step, validate and redirect
416+ if (
417+ currentState . oauthStep === "authorization_code" &&
418+ currentState . authorizationUrl
419+ ) {
420+ try {
421+ validateRedirectUrl ( currentState . authorizationUrl ) ;
422+ } catch ( redirectError ) {
423+ console . error ( "Invalid authorization URL:" , redirectError ) ;
424+ return false ;
425+ }
426+
427+ // Store the current auth state before redirecting
428+ // This allows /oauth/callback to complete the token exchange via proxy
429+ sessionStorage . setItem (
430+ SESSION_KEYS . AUTH_STATE_FOR_CONNECT ,
431+ JSON . stringify ( currentState ) ,
432+ ) ;
433+
434+ // Redirect to authorization URL
435+ // Will redirect to /oauth/callback which calls onOAuthConnect
436+ // and auto-connects after OAuth completion
437+ window . location . href = currentState . authorizationUrl . toString ( ) ;
438+ return false ; // We're redirecting, so connection will be retried after OAuth
439+ }
440+ }
441+
442+ // If we completed the full flow (shouldn't happen on first connect)
443+ return currentState . oauthStep === "complete" ;
444+ } else {
445+ // Direct mode: Use SDK's auth() function
446+ let discoveredScope = scope ;
447+
448+ // Discover scopes if not provided
449+ if ( ! discoveredScope ) {
450+ const proxyAddress = getMCPProxyAddress ( config ) ;
451+ const proxyAuthObj = getMCPProxyAuthToken ( config ) ;
452+ const oauthProvider = createOAuthProviderForServer (
453+ sseUrl ,
454+ proxyAddress ,
455+ proxyAuthObj . token ,
456+ ) ;
457+
458+ // For direct mode, try to get resource metadata
459+ let resourceMetadata ;
460+ try {
461+ resourceMetadata = await discoverOAuthProtectedResourceMetadata (
462+ new URL ( "/" , sseUrl ) ,
463+ ) ;
464+ } catch {
465+ // Resource metadata is optional, continue without it
466+ }
467+
468+ discoveredScope = await oauthProvider . discoverScopes (
469+ sseUrl ,
470+ resourceMetadata ,
354471 ) ;
355- } catch {
356- // Resource metadata is optional, continue without it
472+ if ( discoveredScope ) {
473+ saveScopeToSessionStorage ( sseUrl , discoveredScope ) ;
474+ }
357475 }
358- scope = await discoverScopes ( sseUrl , resourceMetadata ) ;
359- }
360476
361- saveScopeToSessionStorage ( sseUrl , scope ) ;
362- const serverAuthProvider = new InspectorOAuthClientProvider ( sseUrl ) ;
477+ const serverAuthProvider = new InspectorOAuthClientProvider ( sseUrl ) ;
363478
364- const result = await auth ( serverAuthProvider , {
365- serverUrl : sseUrl ,
366- scope,
367- } ) ;
368- return result === "AUTHORIZED" ;
479+ const result = await auth ( serverAuthProvider , {
480+ serverUrl : sseUrl ,
481+ scope : discoveredScope ,
482+ } ) ;
483+ return result === "AUTHORIZED" ;
484+ }
369485 }
370486
371487 return false ;
0 commit comments