From a125ae697e6ce96f933c09ddaf779006d4a4b59e Mon Sep 17 00:00:00 2001 From: Ryan Crabbe Date: Fri, 17 Apr 2026 22:42:07 -0700 Subject: [PATCH 1/2] fix(ui): use stored-credentials endpoint for tools fetch on MCP edit page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The edit page was calling POST /mcp-rest/test/tools/list (the temp-session endpoint that requires inline credentials) on mount. Since fetchTools deliberately omits credentials from the request body, any server with auth_type api_key/bearer_token/basic/authorization would 422. Switch to GET /mcp-rest/tools/list?server_id=... which looks up stored credentials on the backend — no inline creds needed for saved servers. --- .../mcp_tools/mcp_server_edit.test.tsx | 2 +- .../components/mcp_tools/mcp_server_edit.tsx | 38 ++++--------------- 2 files changed, 8 insertions(+), 32 deletions(-) diff --git a/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.test.tsx b/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.test.tsx index aba2a3d9222..760504d5797 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.test.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.test.tsx @@ -7,7 +7,7 @@ import NotificationsManager from "../molecules/notifications_manager"; vi.mock("../networking", () => ({ updateMCPServer: vi.fn(), - testMCPToolsListRequest: vi.fn().mockResolvedValue({ tools: [], error: null }), + listMCPTools: vi.fn().mockResolvedValue({ tools: [], error: null }), })); vi.mock("../molecules/notifications_manager", () => ({ diff --git a/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.tsx b/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.tsx index 574e7871759..d04fdefb1a9 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.tsx @@ -3,7 +3,7 @@ import { Form, Select, Button as AntdButton, Tooltip, Input, InputNumber } from import { InfoCircleOutlined } from "@ant-design/icons"; import { Button, TabGroup, TabList, Tab, TabPanels, TabPanel } from "@tremor/react"; import { AUTH_TYPE, OAUTH_FLOW, MCPServer, MCPServerCostInfo, TRANSPORT } from "./types"; -import { updateMCPServer, testMCPToolsListRequest } from "../networking"; +import { updateMCPServer, listMCPTools } from "../networking"; import MCPServerCostConfig from "./mcp_server_cost_config"; import MCPPermissionManagement from "./MCPPermissionManagement"; import MCPToolConfiguration from "./mcp_tool_configuration"; @@ -271,47 +271,23 @@ const MCPServerEdit: React.FC = ({ } }, [mcpServer]); - // Fetch tools when component mounts or when OAuth token is received - // But only if the server has been properly saved (has a permanent server_id) + // Fetch tools when component mounts for a saved server useEffect(() => { - // Don't fetch if server hasn't been saved yet (no permanent server_id) if (!mcpServer.server_id || mcpServer.server_id.trim() === "") { return; } fetchTools(); - }, [mcpServer, accessToken, oauthAccessToken]); + }, [mcpServer, accessToken]); const fetchTools = async () => { - if (!accessToken) return; - - // HTTP/SSE requires a URL (unless spec_path is set); stdio does not. - if (mcpServer.transport !== "stdio" && !mcpServer.url && !mcpServer.spec_path) return; - - const isM2M = mcpServer.auth_type === AUTH_TYPE.OAUTH2 && !!mcpServer.token_url; - if (mcpServer.auth_type === AUTH_TYPE.OAUTH2 && !isM2M && !oauthAccessToken) { - return; - } + if (!accessToken || !mcpServer.server_id) return; setIsLoadingTools(true); try { - // Prepare the MCP server config from existing server data - const mcpServerConfig = { - server_id: mcpServer.server_id, - server_name: mcpServer.server_name, - url: mcpServer.url, - transport: mcpServer.transport, - auth_type: mcpServer.auth_type, - mcp_info: mcpServer.mcp_info, - authorization_url: mcpServer.authorization_url, - token_url: mcpServer.token_url, - registration_url: mcpServer.registration_url, - command: mcpServer.command, - args: mcpServer.args, - env: mcpServer.env, - }; - - const toolsResponse = await testMCPToolsListRequest(accessToken, mcpServerConfig, oauthAccessToken); + // Use the GET endpoint which looks up stored credentials by server_id, + // rather than POST /test/tools/list which requires inline credentials. + const toolsResponse = await listMCPTools(accessToken, mcpServer.server_id); if (toolsResponse.tools && !toolsResponse.error) { setTools(toolsResponse.tools); From ebffbd1affd6727789cba46ade57857aaf3eac9e Mon Sep 17 00:00:00 2001 From: Ryan Crabbe Date: Sat, 25 Apr 2026 17:09:42 -0700 Subject: [PATCH 2/2] fix(ui): wire MCP tool config panel to GET-based tools fetch Pass externalTools/externalIsLoading/externalError/externalCanFetch from the edit page so MCPToolConfiguration consumes the parent's GET fetch instead of firing its own POST /test/tools/list via useTestMCPConnection. Eliminates the spurious POST that caused the user-visible "Unable to load tools" error for api_key/bearer_token/basic/authorization servers. --- .../src/components/mcp_tools/mcp_server_edit.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.tsx b/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.tsx index d04fdefb1a9..36db674fd1a 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.tsx @@ -37,6 +37,7 @@ const MCPServerEdit: React.FC = ({ const [costConfig, setCostConfig] = useState({}); const [tools, setTools] = useState([]); const [isLoadingTools, setIsLoadingTools] = useState(false); + const [toolsError, setToolsError] = useState(null); const [searchValue, setSearchValue] = useState(""); const [aliasManuallyEdited, setAliasManuallyEdited] = useState(false); const [allowedTools, setAllowedTools] = useState([]); @@ -283,6 +284,7 @@ const MCPServerEdit: React.FC = ({ if (!accessToken || !mcpServer.server_id) return; setIsLoadingTools(true); + setToolsError(null); try { // Use the GET endpoint which looks up stored credentials by server_id, @@ -294,10 +296,12 @@ const MCPServerEdit: React.FC = ({ } else { console.error("Failed to fetch tools:", toolsResponse.message); setTools([]); + setToolsError(toolsResponse.message || "Failed to load tools"); } } catch (error) { console.error("Tools fetch error:", error); setTools([]); + setToolsError(error instanceof Error ? error.message : "Failed to load tools"); } finally { setIsLoadingTools(false); } @@ -1097,6 +1101,10 @@ const MCPServerEdit: React.FC = ({ toolNameToDescription={toolNameToDescription} onToolNameToDisplayNameChange={setToolNameToDisplayName} onToolNameToDescriptionChange={setToolNameToDescription} + externalTools={tools} + externalIsLoading={isLoadingTools} + externalError={toolsError} + externalCanFetch={!!mcpServer.server_id} />