diff --git a/client/src/components/AuthDebugger.tsx b/client/src/components/AuthDebugger.tsx index 6252c116..b72b9c17 100644 --- a/client/src/components/AuthDebugger.tsx +++ b/client/src/components/AuthDebugger.tsx @@ -1,6 +1,9 @@ import { useCallback, useMemo, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { DebugInspectorOAuthClientProvider } from "../lib/auth"; +import { ProxyOAuthServerProvider } from "@modelcontextprotocol/sdk/server/auth/providers/proxyProvider.js"; +import { discoverAuthorizationServerMetadata } from "@modelcontextprotocol/sdk/client/auth.js"; +import { OAuthClientInformationFull } from "@modelcontextprotocol/sdk/shared/auth.js"; import { AlertCircle } from "lucide-react"; import { AuthDebuggerState, EMPTY_DEBUGGER_STATE } from "../lib/auth-types"; import { OAuthFlowProgress } from "./OAuthFlowProgress"; @@ -216,6 +219,94 @@ const AuthDebugger = ({ } }, [serverUrl, updateAuthState, authState]); + const handleQuickRefreshToken = useCallback(async () => { + if (!serverUrl) { + updateAuthState({ + statusMessage: { + type: "error", + message: + "Please enter a server URL in the sidebar before authenticating", + }, + }); + return; + } + if (!authState.oauthTokens || !authState.oauthTokens.refresh_token) { + updateAuthState({ + statusMessage: { + type: "error", + message: "Refresh Token is not available", + }, + }); + return; + } + + updateAuthState({ isInitiatingAuth: true, statusMessage: null }); + + try { + const debugProvider = new DebugInspectorOAuthClientProvider(serverUrl); + const clientInfo = await debugProvider.clientInformation(); + const validClient: OAuthClientInformationFull = { + client_id: clientInfo!.client_id, + redirect_uris: debugProvider.redirect_uris, + }; + + const authServerMeta = + await discoverAuthorizationServerMetadata(serverUrl); + if (!authServerMeta) { + updateAuthState({ + statusMessage: { + type: "error", + message: "Could not reach the OAuth server", + }, + }); + return; + } + + const tokenUrl = authServerMeta.token_endpoint; + const authorizationUrl = authServerMeta.authorization_endpoint; + const scopes = authState.oauthTokens.scope?.split(" ") || []; + const expiresAt = authState.oauthTokens.expires_in || 0; + + const provider = new ProxyOAuthServerProvider({ + endpoints: { authorizationUrl, tokenUrl }, + verifyAccessToken: async (token) => { + return { + token, + clientId: validClient.client_id, + scopes, + expiresAt: Date.now() / 1000 + expiresAt, + }; + }, + getClient: async () => validClient, + }); + const newOauthTokens = await provider.exchangeRefreshToken( + validClient, + authState.oauthTokens.refresh_token, + scopes, + ); + + updateAuthState({ + ...authState, + oauthTokens: newOauthTokens, + statusMessage: { + type: "info", + message: "Authentication completed successfully", + }, + }); + debugProvider.saveTokens(newOauthTokens); + } catch (error) { + console.error("OAuth refresh error:", error); + updateAuthState({ + statusMessage: { + type: "error", + message: `Failed to refresh OAuth tokens: ${error instanceof Error ? error.message : String(error)}`, + }, + }); + } finally { + updateAuthState({ isInitiatingAuth: false }); + } + }, [serverUrl, updateAuthState, authState]); + const handleClearOAuth = useCallback(() => { if (serverUrl) { const serverAuthProvider = new DebugInspectorOAuthClientProvider( @@ -284,6 +375,16 @@ const AuthDebugger = ({ : "Guided OAuth Flow"} + {authState.oauthTokens && + authState.oauthTokens.refresh_token && ( + + )} +