diff --git a/ui/litellm-dashboard/src/app/mcp/oauth/callback/page.tsx b/ui/litellm-dashboard/src/app/mcp/oauth/callback/page.tsx index 73ff8c51bad..390a5d6925c 100644 --- a/ui/litellm-dashboard/src/app/mcp/oauth/callback/page.tsx +++ b/ui/litellm-dashboard/src/app/mcp/oauth/callback/page.tsx @@ -3,7 +3,11 @@ import { Suspense, useEffect, useMemo } from "react"; import { useSearchParams } from "next/navigation"; -const RESULT_STORAGE_KEY = "litellm-mcp-oauth-result"; +// Written to sessionStorage so both the admin hook (useMcpOAuthFlow) and the +// user hook (useUserMcpOAuthFlow) can pick up the result. Each hook reads +// its own namespace to avoid cross-flow collisions. +const ADMIN_RESULT_KEY = "litellm-mcp-oauth-result"; +const USER_RESULT_KEY = "litellm-user-mcp-oauth-result"; const RETURN_URL_STORAGE_KEY = "litellm-mcp-oauth-return-url"; const resolveDefaultRedirect = () => { @@ -41,16 +45,16 @@ const McpOAuthCallbackContent = () => { } try { - // Store in both sessionStorage and localStorage for redundancy - window.sessionStorage.setItem(RESULT_STORAGE_KEY, JSON.stringify(payload)); - window.localStorage.setItem(RESULT_STORAGE_KEY, JSON.stringify(payload)); + // Write to both namespace keys (admin and user) so whichever hook is + // active can consume the result. sessionStorage only — no localStorage. + const serialized = JSON.stringify(payload); + window.sessionStorage.setItem(ADMIN_RESULT_KEY, serialized); + window.sessionStorage.setItem(USER_RESULT_KEY, serialized); } catch (err) { // Silently ignore storage errors } - // Check both sessionStorage and localStorage for return URL - const returnUrl = window.sessionStorage.getItem(RETURN_URL_STORAGE_KEY) || - window.localStorage.getItem(RETURN_URL_STORAGE_KEY); + const returnUrl = window.sessionStorage.getItem(RETURN_URL_STORAGE_KEY); const destination = returnUrl || resolveDefaultRedirect(); window.location.replace(destination); }, [payload]); diff --git a/ui/litellm-dashboard/src/components/chat/MCPCredentialsTab.tsx b/ui/litellm-dashboard/src/components/chat/MCPCredentialsTab.tsx index 551885c5215..9714da6d4d5 100644 --- a/ui/litellm-dashboard/src/components/chat/MCPCredentialsTab.tsx +++ b/ui/litellm-dashboard/src/components/chat/MCPCredentialsTab.tsx @@ -8,7 +8,7 @@ */ import React, { useCallback, useEffect, useState } from "react"; -import { Spin } from "antd"; +import { Spin, message } from "antd"; import { CheckCircleOutlined, DeleteOutlined, LinkOutlined } from "@ant-design/icons"; import { deleteMCPOAuthUserCredential, @@ -76,7 +76,7 @@ const MCPCredentialsTab: React.FC = ({ accessToken }) => { await deleteMCPOAuthUserCredential(accessToken, serverId); setCredentials((prev) => prev.filter((c) => c.server_id !== serverId)); } catch { - // silently fail — user can retry + message.error("Failed to revoke connection. Please try again."); } finally { setRevoking((prev) => { const n = new Set(prev); n.delete(serverId); return n; }); } diff --git a/ui/litellm-dashboard/src/hooks/useUserMcpOAuthFlow.tsx b/ui/litellm-dashboard/src/hooks/useUserMcpOAuthFlow.tsx index c88281b770a..26aab97b341 100644 --- a/ui/litellm-dashboard/src/hooks/useUserMcpOAuthFlow.tsx +++ b/ui/litellm-dashboard/src/hooks/useUserMcpOAuthFlow.tsx @@ -44,7 +44,9 @@ interface UseUserMcpOAuthFlowResult { } const FLOW_STATE_KEY = "litellm-user-mcp-oauth-flow-state"; -const RESULT_KEY = "litellm-mcp-oauth-result"; +// Use a user-flow-specific key to avoid collisions with the admin OAuth flow +// (useMcpOAuthFlow) which uses "litellm-mcp-oauth-result". +const RESULT_KEY = "litellm-user-mcp-oauth-result"; const RETURN_URL_KEY = "litellm-mcp-oauth-return-url"; type StoredFlowState = { @@ -88,7 +90,7 @@ const setStorage = (key: string, value: string) => { const getStorage = (key: string): string | null => { try { - return window.sessionStorage.getItem(key) || window.localStorage.getItem(key); + return window.sessionStorage.getItem(key); } catch (_) { return null; } @@ -98,7 +100,6 @@ const clearStorage = (...keys: string[]) => { keys.forEach((k) => { try { window.sessionStorage.removeItem(k); - window.localStorage.removeItem(k); } catch (_) {} }); };