diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json
index f52288ea72a..cbd64a178b8 100644
--- a/litellm/model_prices_and_context_window_backup.json
+++ b/litellm/model_prices_and_context_window_backup.json
@@ -16289,7 +16289,7 @@
"cache_read_input_token_cost": 3e-08,
"input_cost_per_audio_token": 1e-06,
"input_cost_per_token": 3e-07,
- "litellm_provider": "vertex_ai-language-models",
+ "litellm_provider": "gemini",
"max_audio_length_hours": 8.4,
"max_audio_per_prompt": 1,
"supports_reasoning": false,
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 e33e2fff491..eb546378776 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
@@ -33,8 +33,9 @@ vi.mock("./MCPPermissionManagement", () => ({
default: () =>
,
}));
+const MockMCPToolConfiguration = vi.fn(() => );
vi.mock("./mcp_tool_configuration", () => ({
- default: () => ,
+ default: (props: any) => MockMCPToolConfiguration(props),
}));
describe("MCPServerEdit (stdio)", () => {
@@ -152,3 +153,99 @@ describe("MCPServerEdit (stdio)", () => {
expect(payload.env).toEqual({ CIRCLECI_TOKEN: "new-token", CIRCLECI_BASE_URL: "https://circleci.com" });
});
});
+
+describe("MCPServerEdit (URL change clears allowed tools)", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it("should pass live URL to MCPToolConfiguration when URL field changes", async () => {
+ render(
+ ,
+ );
+
+ // Initially, MCPToolConfiguration should receive the original URL
+ const initialCall = MockMCPToolConfiguration.mock.calls.find(
+ (call) => call[0]?.formValues?.url === "https://exa.example.com/mcp"
+ );
+ expect(initialCall).toBeDefined();
+
+ // Change the URL field
+ const urlInput = screen.getByLabelText("MCP Server URL");
+ await act(async () => {
+ fireEvent.change(urlInput, {
+ target: { value: "https://seolinkmap.com/mcp" },
+ });
+ });
+
+ // After URL change, MCPToolConfiguration should receive the new URL
+ await waitFor(() => {
+ const latestCall = MockMCPToolConfiguration.mock.calls[MockMCPToolConfiguration.mock.calls.length - 1];
+ expect(latestCall[0].formValues.url).toBe("https://seolinkmap.com/mcp");
+ });
+ });
+
+ it("should clear allowedTools when URL changes from original", async () => {
+ render(
+ ,
+ );
+
+ // Initially, allowedTools should be the existing tools
+ const initialCall = MockMCPToolConfiguration.mock.calls[MockMCPToolConfiguration.mock.calls.length - 1];
+ expect(initialCall[0].allowedTools).toEqual(["web_search_exa", "get_code_context_exa"]);
+
+ // Change the URL field to a different server
+ const urlInput = screen.getByLabelText("MCP Server URL");
+ await act(async () => {
+ fireEvent.change(urlInput, {
+ target: { value: "https://seolinkmap.com/mcp" },
+ });
+ });
+
+ // After URL change, allowedTools should be cleared
+ await waitFor(() => {
+ const latestCall = MockMCPToolConfiguration.mock.calls[MockMCPToolConfiguration.mock.calls.length - 1];
+ expect(latestCall[0].allowedTools).toEqual([]);
+ });
+ });
+});
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 4209d8bf111..c4f1bc85832 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
@@ -41,6 +41,8 @@ const MCPServerEdit: React.FC = ({
const [pendingRestoredValues, setPendingRestoredValues] = useState | null>(null);
const authType = Form.useWatch("auth_type", form) as string | undefined;
const transportType = Form.useWatch("transport", form) as string | undefined;
+ const urlValue = Form.useWatch("url", form) as string | undefined;
+ const specPathValue = Form.useWatch("spec_path", form) as string | undefined;
const isStdioTransport = transportType === "stdio";
const isOpenAPITransport = transportType === TRANSPORT.OPENAPI;
const isMCPTransport = !isStdioTransport && !isOpenAPITransport;
@@ -191,6 +193,16 @@ const MCPServerEdit: React.FC = ({
}
}, [mcpServer]);
+ // Clear allowed tools when URL or spec_path changes to a different server
+ // so that the auto-select logic in MCPToolConfiguration picks up the new tools
+ useEffect(() => {
+ const currentEndpoint = urlValue ?? specPathValue;
+ const originalEndpoint = mcpServer.url ?? mcpServer.spec_path;
+ if (currentEndpoint !== undefined && currentEndpoint !== originalEndpoint) {
+ setAllowedTools([]);
+ }
+ }, [urlValue, specPathValue, mcpServer.url, mcpServer.spec_path]);
+
useEffect(() => {
if (typeof window === "undefined") {
return;
@@ -880,9 +892,10 @@ const MCPServerEdit: React.FC = ({
formValues={{
server_id: mcpServer.server_id,
server_name: mcpServer.server_name,
- url: mcpServer.url,
- transport: mcpServer.transport,
- auth_type: mcpServer.auth_type,
+ url: urlValue ?? mcpServer.url,
+ spec_path: specPathValue ?? mcpServer.spec_path,
+ transport: transportType ?? mcpServer.transport,
+ auth_type: authType ?? mcpServer.auth_type,
mcp_info: mcpServer.mcp_info,
oauth_flow_type: mcpServer.token_url ? OAUTH_FLOW.M2M : OAUTH_FLOW.INTERACTIVE,
}}
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 404172623b9..46b3f3ce8aa 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
@@ -21,7 +21,7 @@ const MCPToolConfiguration: React.FC = ({
existingAllowedTools,
onAllowedToolsChange,
}) => {
- const previousToolsLengthRef = useRef(0);
+ const previousToolNamesRef = useRef("");
const [toolSearchTerm, setToolSearchTerm] = useState("");
const { tools, isLoadingTools, toolsError, canFetchTools } = useTestMCPConnection({
@@ -40,27 +40,32 @@ const MCPToolConfiguration: React.FC = ({
);
});
- // Auto-select tools when tools are first loaded
+ // Auto-select tools when tools are first loaded or when tool list changes (e.g. URL changed)
useEffect(() => {
+ const currentToolNames = tools.map((t) => t.name).sort().join(",");
// Only auto-select if:
// 1. We have tools
- // 2. Tools length changed (new tools loaded)
- // 3. No tools are currently selected (initial state)
- if (tools.length > 0 && tools.length !== previousToolsLengthRef.current && allowedTools.length === 0) {
+ // 2. Tool names changed from last fetch (detects both count and content changes)
+ // 3. No tools are currently selected (initial state or cleared after URL change)
+ if (tools.length > 0 && currentToolNames !== previousToolNamesRef.current && allowedTools.length === 0) {
if (existingAllowedTools && existingAllowedTools.length > 0) {
// If we have existing allowed tools, use those as the initial selection
// Filter to only include tools that are actually available from the server
const availableToolNames = tools.map((tool) => tool.name);
const validExistingTools = existingAllowedTools.filter((toolName) => availableToolNames.includes(toolName));
- onAllowedToolsChange(validExistingTools);
+ if (validExistingTools.length > 0) {
+ onAllowedToolsChange(validExistingTools);
+ } else {
+ // No existing tools match the new server — select all new tools
+ onAllowedToolsChange(availableToolNames);
+ }
} else {
// If no existing allowed tools, auto-select all tools (create mode)
const allToolNames = tools.map((tool) => tool.name);
onAllowedToolsChange(allToolNames);
}
}
- // Update ref to track tools length (will be 0 when tools clear)
- previousToolsLengthRef.current = tools.length;
+ previousToolNamesRef.current = currentToolNames;
}, [tools, allowedTools.length, existingAllowedTools, onAllowedToolsChange]);
const handleToolToggle = (toolName: string) => {