diff --git a/ui/litellm-dashboard/src/components/mcp_tools/OpenAPIFormSection.tsx b/ui/litellm-dashboard/src/components/mcp_tools/OpenAPIFormSection.tsx index 316eacef80c..f9531bde50f 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/OpenAPIFormSection.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/OpenAPIFormSection.tsx @@ -65,6 +65,12 @@ const OpenAPIFormSection: React.FC = ({ { + // Clear the preset selection when the user manually edits the spec URL + // so stale suggested tools from a previous preset don't persist. + setSelectedPreset(null); + onKeyToolsChange?.([]); + }} /> diff --git a/ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.tsx b/ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.tsx index 85d3e4143f5..737f323da71 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/create_mcp_server.tsx @@ -15,6 +15,7 @@ import { isAdminRole } from "@/utils/roles"; import { validateMCPServerUrl, validateMCPServerName } from "./utils"; import NotificationsManager from "../molecules/notifications_manager"; import { useMcpOAuthFlow } from "@/hooks/useMcpOAuthFlow"; +import { useTestMCPConnection } from "@/hooks/useTestMCPConnection"; const asset_logos_folder = "../ui/assets/logos/"; export const mcpLogoImg = `${asset_logos_folder}mcp_logo.png`; @@ -53,7 +54,6 @@ const CreateMCPServer: React.FC = ({ transport?: string; } | null>(null); const [aliasManuallyEdited, setAliasManuallyEdited] = useState(false); - const [tools, setTools] = useState([]); const [allowedTools, setAllowedTools] = useState([]); const [toolNameToDisplayName, setToolNameToDisplayName] = useState>({}); const [toolNameToDescription, setToolNameToDescription] = useState>({}); @@ -61,6 +61,15 @@ const CreateMCPServer: React.FC = ({ const [keyTools, setKeyTools] = useState([]); const [searchValue, setSearchValue] = useState(""); const [oauthAccessToken, setOauthAccessToken] = useState(null); + + // Single hook call shared by MCPConnectionStatus and MCPToolConfiguration to avoid duplicate requests. + const { tools, isLoadingTools, toolsError, toolsErrorStackTrace, canFetchTools, fetchTools, clearTools } = useTestMCPConnection({ + accessToken, + oauthAccessToken, + formValues, + enabled: true, + }); + const authType = formValues.auth_type as string | undefined; const shouldShowAuthValueField = authType ? AUTH_TYPES_REQUIRING_AUTH_VALUE.includes(authType) : false; const isOAuthAuthType = authType === AUTH_TYPE.OAUTH2; @@ -380,7 +389,7 @@ const CreateMCPServer: React.FC = ({ NotificationsManager.success("MCP Server created successfully"); form.resetFields(); setCostConfig({}); - setTools([]); + clearTools(); setAllowedTools([]); setAliasManuallyEdited(false); setModalVisible(false); @@ -777,10 +786,13 @@ const CreateMCPServer: React.FC = ({ {/* Connection Status Section */}
@@ -798,6 +810,10 @@ const CreateMCPServer: React.FC = ({ onToolNameToDisplayNameChange={setToolNameToDisplayName} onToolNameToDescriptionChange={setToolNameToDescription} keyTools={keyTools} + externalTools={tools} + externalIsLoading={isLoadingTools} + externalError={toolsError} + externalCanFetch={canFetchTools} /> diff --git a/ui/litellm-dashboard/src/components/mcp_tools/mcp_connection_status.tsx b/ui/litellm-dashboard/src/components/mcp_tools/mcp_connection_status.tsx index 5a90a182b11..d6d6c07c1d7 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/mcp_connection_status.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/mcp_connection_status.tsx @@ -1,28 +1,27 @@ -import React, { useEffect } from "react"; +import React from "react"; import { Button, Spin, Alert, Collapse } from "antd"; import { CheckCircleOutlined, ExclamationCircleOutlined, ReloadOutlined, ToolOutlined } from "@ant-design/icons"; import { Card, Title, Text } from "@tremor/react"; -import { useTestMCPConnection } from "../../hooks/useTestMCPConnection"; interface MCPConnectionStatusProps { - accessToken: string | null; - oauthAccessToken?: string | null; formValues: Record; - onToolsLoaded?: (tools: any[]) => void; + tools: any[]; + isLoadingTools: boolean; + toolsError: string | null; + toolsErrorStackTrace: string | null; + canFetchTools: boolean; + fetchTools: () => Promise; } -const MCPConnectionStatus: React.FC = ({ accessToken, oauthAccessToken, formValues, onToolsLoaded }) => { - const { tools, isLoadingTools, toolsError, toolsErrorStackTrace, canFetchTools, fetchTools } = useTestMCPConnection({ - accessToken, - oauthAccessToken, - formValues, - enabled: true, // Auto-fetch when required fields are available - }); - - // Notify parent component when tools change - useEffect(() => { - onToolsLoaded?.(tools); - }, [tools, onToolsLoaded]); +const MCPConnectionStatus: React.FC = ({ + formValues, + tools, + isLoadingTools, + toolsError, + toolsErrorStackTrace, + canFetchTools, + fetchTools, +}) => { // Don't show anything if required fields aren't filled if (!canFetchTools && !formValues.url && !formValues.spec_path) { diff --git a/ui/litellm-dashboard/src/components/mcp_tools/mcp_tool_configuration.tsx b/ui/litellm-dashboard/src/components/mcp_tools/mcp_tool_configuration.tsx index 5fa209579d9..e6b867591e2 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/mcp_tool_configuration.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/mcp_tool_configuration.tsx @@ -22,6 +22,11 @@ interface MCPToolConfigurationProps { onToolNameToDescriptionChange: (map: Record) => void; /** Curated key tools from the OpenAPI registry preset (shown before spec loads). */ keyTools?: KeyTool[]; + /** External tool state lifted from parent to avoid duplicate fetch requests. */ + externalTools?: any[]; + externalIsLoading?: boolean; + externalError?: string | null; + externalCanFetch?: boolean; } interface ToolEntry { @@ -148,6 +153,10 @@ const MCPToolConfiguration: React.FC = ({ onToolNameToDisplayNameChange, onToolNameToDescriptionChange, keyTools, + externalTools, + externalIsLoading, + externalError, + externalCanFetch, }) => { const previousToolsRef = useRef([]); const [toolSearchTerm, setToolSearchTerm] = useState(""); @@ -155,12 +164,19 @@ const MCPToolConfiguration: React.FC = ({ const previousSuggestedToolNamesRef = useRef(""); const [expandedTools, setExpandedTools] = useState>(new Set()); - const { tools, isLoadingTools, toolsError, canFetchTools } = useTestMCPConnection({ + // Use external tool state when provided (avoids duplicate fetch with MCPConnectionStatus). + // Fall back to internal hook when used standalone (e.g., edit flow). + const hasExternalState = externalTools !== undefined; + const internalHook = useTestMCPConnection({ accessToken, oauthAccessToken, formValues, - enabled: true, + enabled: !hasExternalState, }); + const tools: ToolEntry[] = hasExternalState ? externalTools : internalHook.tools; + const isLoadingTools = hasExternalState ? (externalIsLoading ?? false) : internalHook.isLoadingTools; + const toolsError = hasExternalState ? (externalError ?? null) : internalHook.toolsError; + const canFetchTools = hasExternalState ? (externalCanFetch ?? false) : internalHook.canFetchTools; // Fuzzy-match curated key tool names against actual loaded tool names const suggestedTools = useMemo(() => {