Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => ({
Expand Down
46 changes: 15 additions & 31 deletions ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -37,6 +37,7 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
const [costConfig, setCostConfig] = useState<MCPServerCostInfo>({});
const [tools, setTools] = useState<any[]>([]);
const [isLoadingTools, setIsLoadingTools] = useState(false);
const [toolsError, setToolsError] = useState<string | null>(null);
const [searchValue, setSearchValue] = useState<string>("");
const [aliasManuallyEdited, setAliasManuallyEdited] = useState(false);
const [allowedTools, setAllowedTools] = useState<string[]>([]);
Expand Down Expand Up @@ -272,57 +273,36 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
}
}, [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]);
Comment on lines 277 to +282

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 OAuth interactive flow: tools no longer refresh after token receive

Removing oauthAccessToken from the dep array is correct for the GET-based fetch, but as a side effect, interactive OAuth servers that complete the in-page OAuth flow (via "Authorize & Fetch Token") will no longer see an automatic tool refresh after the token arrives. Previously the effect re-ran with the new token and called the POST endpoint; now the user would need to save the server and re-open the edit page. This is a minor UX regression worth documenting, even if the "Test Connection" button covers the live-test use case.


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);
setToolsError(null);

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);
} 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);
}
Expand Down Expand Up @@ -1122,6 +1102,10 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
toolNameToDescription={toolNameToDescription}
onToolNameToDisplayNameChange={setToolNameToDisplayName}
onToolNameToDescriptionChange={setToolNameToDescription}
externalTools={tools}
externalIsLoading={isLoadingTools}
externalError={toolsError}
externalCanFetch={!!mcpServer.server_id}
/>
</div>

Expand Down
Loading