diff --git a/ui/litellm-dashboard/src/components/chat/MCPConnectPicker.tsx b/ui/litellm-dashboard/src/components/chat/MCPConnectPicker.tsx new file mode 100644 index 00000000000..234aa8a5281 --- /dev/null +++ b/ui/litellm-dashboard/src/components/chat/MCPConnectPicker.tsx @@ -0,0 +1,157 @@ +import React, { useEffect, useState } from "react"; +import { Switch, Spin, message } from "antd"; +import { fetchMCPServers, listMCPTools } from "../networking"; +import { MCPServer } from "../mcp_tools/types"; + +interface Props { + accessToken: string; + selectedServers: string[]; + onChange: (servers: string[]) => void; +} + +const MCPConnectPicker: React.FC = ({ accessToken, selectedServers, onChange }) => { + const [servers, setServers] = useState([]); + const [loadingServers, setLoadingServers] = useState(true); + // Track which individual servers are being toggled on (verifying tools) + const [togglingOn, setTogglingOn] = useState>(new Set()); + + useEffect(() => { + let cancelled = false; + + const load = async () => { + setLoadingServers(true); + try { + const data = await fetchMCPServers(accessToken); + if (cancelled) return; + // API returns { data: MCPServer[] } or MCPServer[] + const list: MCPServer[] = Array.isArray(data) ? data : (data?.data ?? []); + setServers(list); + } catch { + if (!cancelled) { + setServers([]); + } + } finally { + if (!cancelled) { + setLoadingServers(false); + } + } + }; + + load(); + + return () => { + cancelled = true; + }; + }, [accessToken]); + + const handleToggle = async (serverName: string, checked: boolean) => { + if (!checked) { + // Toggle OFF — remove immediately, no tool fetch needed + onChange(selectedServers.filter((s) => s !== serverName)); + return; + } + + // Toggle ON — verify tools are reachable first + setTogglingOn((prev) => new Set(prev).add(serverName)); + try { + const result = await listMCPTools(accessToken, serverName); + // listMCPTools never throws; it returns { tools, error, message } on failure + if (result?.error) { + message.warning( + `Could not load tools for ${serverName} — it will be excluded from this message.` + ); + // Do not add to selectedServers + return; + } + onChange([...selectedServers, serverName]); + } catch { + message.warning( + `Could not load tools for ${serverName} — it will be excluded from this message.` + ); + // Do not add to selectedServers + } finally { + setTogglingOn((prev) => { + const next = new Set(prev); + next.delete(serverName); + return next; + }); + } + }; + + return ( +
+ {loadingServers ? ( +
+ +
+ ) : servers.length === 0 ? ( +
+ No MCP servers configured +
+ ) : ( + servers.map((server) => { + const name = server.server_name ?? server.alias ?? server.server_id; + const isSelected = selectedServers.includes(name); + const isTogglingOn = togglingOn.has(name); + + return ( +
+
+
+ {name} +
+ {server.description && ( +
+ {server.description} +
+ )} +
+ handleToggle(name, checked)} + /> +
+ ); + }) + )} +
+ ); +}; + +export default MCPConnectPicker;