diff --git a/ui/litellm-dashboard/src/components/mcp_server_management/MCPServerSelector.test.tsx b/ui/litellm-dashboard/src/components/mcp_server_management/MCPServerSelector.test.tsx index 1517b2dfa27..16847f1be81 100644 --- a/ui/litellm-dashboard/src/components/mcp_server_management/MCPServerSelector.test.tsx +++ b/ui/litellm-dashboard/src/components/mcp_server_management/MCPServerSelector.test.tsx @@ -3,7 +3,7 @@ import userEvent from "@testing-library/user-event"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { renderWithProviders } from "../../../tests/test-utils"; import MCPServerSelector from "./MCPServerSelector"; -import { NO_MCP_SERVERS_SENTINEL } from "../mcp_tools/constants"; +import { ALL_MCP_SERVERS_SENTINEL, NO_MCP_SERVERS_SENTINEL } from "../mcp_tools/constants"; vi.mock("@/app/(dashboard)/hooks/mcpServers/useMCPServers", () => ({ useMCPServers: vi.fn(), @@ -98,3 +98,57 @@ describe("MCPServerSelector no-mcp-servers option", () => { expect(optionByValue(NO_MCP_SERVERS_SENTINEL)?.disabled).toBe(false); }); }); + +describe("MCPServerSelector all-mcp-servers option", () => { + beforeEach(() => { + vi.clearAllMocks(); + mockUseMCPServers.mockReturnValue({ + data: [{ server_id: "srv-1", server_name: "Server One" }], + isLoading: false, + } as any); + mockUseMCPAccessGroups.mockReturnValue({ data: [], isLoading: false } as any); + mockUseMCPToolsets.mockReturnValue({ data: [], isLoading: false } as any); + }); + + const optionByValue = (value: string) => + Array.from(screen.getByTestId("mcp-select").querySelectorAll("option")).find( + (o) => (o as HTMLOptionElement).value === value, + ) as HTMLOptionElement | undefined; + + it("hides the All MCP Servers option by default", () => { + renderWithProviders( + , + ); + expect(optionByValue(ALL_MCP_SERVERS_SENTINEL)).toBeUndefined(); + }); + + it("emits an exclusive sentinel when All MCP Servers is selected", async () => { + const onChange = vi.fn(); + renderWithProviders( + , + ); + expect(optionByValue(ALL_MCP_SERVERS_SENTINEL)).toBeDefined(); + + await userEvent.selectOptions(screen.getByTestId("mcp-select"), [ALL_MCP_SERVERS_SENTINEL]); + + expect(onChange).toHaveBeenCalledWith({ servers: [ALL_MCP_SERVERS_SENTINEL], accessGroups: [], toolsets: [] }); + }); + + it("disables real server options while all-mcp-servers is selected", () => { + renderWithProviders( + , + ); + expect(optionByValue("srv-1")?.disabled).toBe(true); + expect(optionByValue(ALL_MCP_SERVERS_SENTINEL)?.disabled).toBe(false); + }); +}); diff --git a/ui/litellm-dashboard/src/components/mcp_server_management/MCPServerSelector.tsx b/ui/litellm-dashboard/src/components/mcp_server_management/MCPServerSelector.tsx index bbda761938e..01ca04c457e 100644 --- a/ui/litellm-dashboard/src/components/mcp_server_management/MCPServerSelector.tsx +++ b/ui/litellm-dashboard/src/components/mcp_server_management/MCPServerSelector.tsx @@ -3,7 +3,7 @@ import { useMCPServers } from "@/app/(dashboard)/hooks/mcpServers/useMCPServers" import { useMCPToolsets } from "@/app/(dashboard)/hooks/mcpServers/useMCPToolsets"; import { Select } from "antd"; import React from "react"; -import { NO_MCP_SERVERS_SENTINEL } from "@/components/mcp_tools/constants"; +import { ALL_MCP_SERVERS_SENTINEL, NO_MCP_SERVERS_SENTINEL } from "@/components/mcp_tools/constants"; interface MCPServerSelectorProps { onChange: (selected: { servers: string[]; accessGroups: string[]; toolsets: string[] }) => void; @@ -18,6 +18,7 @@ interface MCPServerSelectorProps { disabled?: boolean; teamId?: string | null; allowNoMcpServers?: boolean; + allowAllMcpServers?: boolean; } const TOOLSET_PREFIX = "toolset:"; @@ -31,6 +32,7 @@ const MCPServerSelector: React.FC = ({ disabled = false, teamId, allowNoMcpServers = false, + allowAllMcpServers = false, }) => { const { data: mcpServers = [], isLoading: serversLoading } = useMCPServers(teamId); const { data: accessGroups = [], isLoading: groupsLoading } = useMCPAccessGroups(); @@ -81,10 +83,15 @@ const MCPServerSelector: React.FC = ({ ]; const hasNoMcpServersSelected = allowNoMcpServers && selectedValues.includes(NO_MCP_SERVERS_SENTINEL); + const hasAllMcpServersSelected = allowAllMcpServers && selectedValues.includes(ALL_MCP_SERVERS_SENTINEL); + const hasSentinelSelected = hasNoMcpServersSelected || hasAllMcpServersSelected; // Handle selection const handleChange = (selected: string[]) => { - // "No MCP Servers" is exclusive: picking it clears everything else. + if (allowAllMcpServers && selected.includes(ALL_MCP_SERVERS_SENTINEL)) { + onChange({ servers: [ALL_MCP_SERVERS_SENTINEL], accessGroups: [], toolsets: [] }); + return; + } if (allowNoMcpServers && selected.includes(NO_MCP_SERVERS_SENTINEL)) { onChange({ servers: [NO_MCP_SERVERS_SENTINEL], accessGroups: [], toolsets: [] }); return; @@ -112,13 +119,31 @@ const MCPServerSelector: React.FC = ({ style={{ width: "100%" }} disabled={disabled} filterOption={(input, option) => { - if (option?.value === NO_MCP_SERVERS_SENTINEL) return true; + if (option?.value === NO_MCP_SERVERS_SENTINEL || option?.value === ALL_MCP_SERVERS_SENTINEL) return true; const searchText = options.find((opt) => opt.value === option?.value)?.searchText || ""; return searchText.toLowerCase().includes(input.toLowerCase()); }} > + {allowAllMcpServers && ( + +
+ All MCP Servers + Grant all +
+
+ )} {allowNoMcpServers && ( - +
No MCP Servers Block all @@ -126,7 +151,7 @@ const MCPServerSelector: React.FC = ({ )} {options.map((opt) => ( - +
= ({ team, teams, data, addKey, autoOp teamId={selectedCreateKeyTeam?.team_id ?? null} placeholder="Select MCP servers or access groups (optional)" allowNoMcpServers + allowAllMcpServers /> diff --git a/ui/litellm-dashboard/src/components/organization/organization_view.tsx b/ui/litellm-dashboard/src/components/organization/organization_view.tsx index a3ee0004fa6..c936c27feee 100644 --- a/ui/litellm-dashboard/src/components/organization/organization_view.tsx +++ b/ui/litellm-dashboard/src/components/organization/organization_view.tsx @@ -434,6 +434,7 @@ const OrganizationInfoView: React.FC = ({ value={form.getFieldValue("mcp_servers_and_groups")} accessToken={accessToken || ""} placeholder="Select MCP servers and access groups" + allowAllMcpServers /> diff --git a/ui/litellm-dashboard/src/components/organizations.tsx b/ui/litellm-dashboard/src/components/organizations.tsx index d0629bb9cfd..38f4f9bddda 100644 --- a/ui/litellm-dashboard/src/components/organizations.tsx +++ b/ui/litellm-dashboard/src/components/organizations.tsx @@ -510,6 +510,7 @@ const OrganizationsTable: React.FC = ({ value={form.getFieldValue("allowed_mcp_servers_and_groups")} accessToken={accessToken || ""} placeholder="Select MCP servers and access groups (optional)" + allowAllMcpServers /> diff --git a/ui/litellm-dashboard/src/components/team/TeamInfo.tsx b/ui/litellm-dashboard/src/components/team/TeamInfo.tsx index dc56e6c63e1..6129b94e7c7 100644 --- a/ui/litellm-dashboard/src/components/team/TeamInfo.tsx +++ b/ui/litellm-dashboard/src/components/team/TeamInfo.tsx @@ -1356,6 +1356,7 @@ const TeamInfoView: React.FC = ({ value={form.getFieldValue("mcp_servers_and_groups")} accessToken={accessToken || ""} placeholder="Select MCP servers or access groups (optional)" + allowAllMcpServers /> diff --git a/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx b/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx index e991db8069e..c1499115a9b 100644 --- a/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx +++ b/ui/litellm-dashboard/src/components/templates/key_edit_view.tsx @@ -628,6 +628,7 @@ export function KeyEditView({ accessToken={accessToken || ""} placeholder="Select MCP servers or access groups (optional)" allowNoMcpServers + allowAllMcpServers />