From 95fbe59c46adfa7fc5356d94b0d0f4377e0a515a Mon Sep 17 00:00:00 2001 From: "Jugal D. Bhatt" <55304795+jugaldb@users.noreply.github.com> Date: Sat, 9 Aug 2025 16:13:56 -0700 Subject: [PATCH] Add local storage auth (#13473) --- .../components/mcp_tools/mcp_auth_storage.ts | 111 +++++++++++++++++ .../src/components/mcp_tools/mcp_tools.tsx | 115 ++++++++++++++---- .../src/components/navbar.tsx | 2 + .../src/components/user_dashboard.tsx | 3 + 4 files changed, 210 insertions(+), 21 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/mcp_tools/mcp_auth_storage.ts diff --git a/ui/litellm-dashboard/src/components/mcp_tools/mcp_auth_storage.ts b/ui/litellm-dashboard/src/components/mcp_tools/mcp_auth_storage.ts new file mode 100644 index 00000000000..751f25ea629 --- /dev/null +++ b/ui/litellm-dashboard/src/components/mcp_tools/mcp_auth_storage.ts @@ -0,0 +1,111 @@ +// Utility functions for managing MCP server authentication tokens in localStorage + +const MCP_AUTH_STORAGE_KEY = 'litellm_mcp_auth_tokens'; + +export interface MCPAuthToken { + serverId: string; + serverAlias?: string; + authValue: string; + authType: string; + timestamp: number; +} + +export interface MCPAuthStorage { + [serverId: string]: MCPAuthToken; +} + +/** + * Get all stored MCP authentication tokens + */ +export const getMCPAuthTokens = (): MCPAuthStorage => { + try { + const stored = localStorage.getItem(MCP_AUTH_STORAGE_KEY); + return stored ? JSON.parse(stored) : {}; + } catch (error) { + console.error('Error reading MCP auth tokens from localStorage:', error); + return {}; + } +}; + +/** + * Get authentication token for a specific MCP server + */ +export const getMCPAuthToken = (serverId: string, serverAlias?: string): string | null => { + try { + const tokens = getMCPAuthTokens(); + const token = tokens[serverId]; + + // If token exists, check if serverAlias matches (both can be undefined) + if (token && token.serverAlias === serverAlias) { + return token.authValue; + } + + // If no serverAlias was provided and token exists without serverAlias, return it + if (token && !serverAlias && !token.serverAlias) { + return token.authValue; + } + + return null; + } catch (error) { + console.error('Error getting MCP auth token:', error); + return null; + } +}; + +/** + * Store authentication token for an MCP server + */ +export const setMCPAuthToken = ( + serverId: string, + authValue: string, + authType: string, + serverAlias?: string +): void => { + try { + const tokens = getMCPAuthTokens(); + + tokens[serverId] = { + serverId, + serverAlias, + authValue, + authType, + timestamp: Date.now(), + }; + + localStorage.setItem(MCP_AUTH_STORAGE_KEY, JSON.stringify(tokens)); + } catch (error) { + console.error('Error storing MCP auth token:', error); + } +}; + +/** + * Remove authentication token for an MCP server + */ +export const removeMCPAuthToken = (serverId: string): void => { + try { + const tokens = getMCPAuthTokens(); + delete tokens[serverId]; + localStorage.setItem(MCP_AUTH_STORAGE_KEY, JSON.stringify(tokens)); + } catch (error) { + console.error('Error removing MCP auth token:', error); + } +}; + +/** + * Clear all MCP authentication tokens (useful for logout) + */ +export const clearMCPAuthTokens = (): void => { + try { + localStorage.removeItem(MCP_AUTH_STORAGE_KEY); + } catch (error) { + console.error('Error clearing MCP auth tokens:', error); + } +}; + +/** + * Check if a token exists for a server + */ +export const hasMCPAuthToken = (serverId: string, serverAlias?: string): boolean => { + const token = getMCPAuthToken(serverId, serverAlias); + return token !== null; +}; \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/mcp_tools/mcp_tools.tsx b/ui/litellm-dashboard/src/components/mcp_tools/mcp_tools.tsx index 7031679af92..b2ba26875fb 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/mcp_tools.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/mcp_tools.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { useQuery, useMutation } from "@tanstack/react-query"; import { ToolTestPanel } from "./ToolTestPanel"; import { @@ -8,8 +8,14 @@ import { mcpServerHasAuth, } from "./types"; import { listMCPTools, callMCPTool } from "../networking"; +import { + getMCPAuthToken, + setMCPAuthToken, + removeMCPAuthToken, + hasMCPAuthToken +} from "./mcp_auth_storage"; -import { Modal, Input, Form } from "antd"; +import { Modal, Input, Form, message } from "antd"; import { Button, Card, Title, Text } from "@tremor/react"; import { RobotOutlined, ApiOutlined, KeyOutlined, SafetyOutlined, ToolOutlined } from "@ant-design/icons"; @@ -92,10 +98,12 @@ export const AuthModal = ({ const AuthSection = ({ authType, onAuthSubmit, + onClearAuth, hasAuth }: { authType: string | null | undefined; onAuthSubmit: (value: string) => void; + onClearAuth: () => void; hasAuth: boolean; }) => { const [modalVisible, setModalVisible] = useState(false); @@ -109,23 +117,39 @@ const AuthSection = ({ const handleModalCancel = () => setModalVisible(false); + const handleClearAuth = () => { + onClearAuth(); + }; + return (