From 9058354ceda7a99ea6fa2a9007a59df27e2b24d8 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Tue, 10 Jun 2025 15:46:43 -0700 Subject: [PATCH] [Feat] UI - Add controls for MCP Permission Management (#11598) * init commit to render MCP servers * allow adding MCP servers to keys * fix allow adding MCP permissions per team * ui mcp permissions * updates based on review --- .../src/components/key_edit_view.tsx | 13 +- .../src/components/key_info_view.tsx | 9 ++ .../MCPServerSelector.tsx | 74 ++++++++++++ .../components/object_permissions_view.tsx | 111 ++---------------- .../permissions/MCPServerPermissions.tsx | 86 ++++++++++++++ .../permissions/VectorStorePermissions.tsx | 83 +++++++++++++ .../src/components/team/team_info.tsx | 18 ++- 7 files changed, 292 insertions(+), 102 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/mcp_server_management/MCPServerSelector.tsx create mode 100644 ui/litellm-dashboard/src/components/permissions/MCPServerPermissions.tsx create mode 100644 ui/litellm-dashboard/src/components/permissions/VectorStorePermissions.tsx diff --git a/ui/litellm-dashboard/src/components/key_edit_view.tsx b/ui/litellm-dashboard/src/components/key_edit_view.tsx index 959ce0362f2..cd4f32567f3 100644 --- a/ui/litellm-dashboard/src/components/key_edit_view.tsx +++ b/ui/litellm-dashboard/src/components/key_edit_view.tsx @@ -6,6 +6,7 @@ import { fetchTeamModels } from "../components/create_key_button"; import { modelAvailableCall } from "./networking"; import NumericalInput from "./shared/numerical_input"; import VectorStoreSelector from "./vector_store_management/VectorStoreSelector"; +import MCPServerSelector from "./mcp_server_management/MCPServerSelector"; interface KeyEditViewProps { keyData: KeyResponse; @@ -98,7 +99,8 @@ export function KeyEditView({ budget_duration: getBudgetDuration(keyData.budget_duration), metadata: keyData.metadata ? JSON.stringify(keyData.metadata, null, 2) : "", guardrails: keyData.metadata?.guardrails || [], - vector_stores: keyData.object_permission?.vector_stores || [] + vector_stores: keyData.object_permission?.vector_stores || [], + mcp_servers: keyData.object_permission?.mcp_servers || [] }; return ( @@ -180,6 +182,15 @@ export function KeyEditView({ /> + + form.setFieldValue('mcp_servers', values)} + value={form.getFieldValue('mcp_servers')} + accessToken={accessToken || ""} + placeholder="Select MCP servers" + /> + + diff --git a/ui/litellm-dashboard/src/components/key_info_view.tsx b/ui/litellm-dashboard/src/components/key_info_view.tsx index 087d60d20ec..1326d37d708 100644 --- a/ui/litellm-dashboard/src/components/key_info_view.tsx +++ b/ui/litellm-dashboard/src/components/key_info_view.tsx @@ -76,6 +76,15 @@ export default function KeyInfoView({ keyId, onClose, keyData, accessToken, user delete formValues.vector_stores; } + if (formValues.mcp_servers !== undefined) { + formValues.object_permission = { + ...keyData.object_permission, + mcp_servers: formValues.mcp_servers || [] + }; + // Remove mcp_servers from the top level as it should be in object_permission + delete formValues.mcp_servers; + } + // Convert metadata back to an object if it exists and is a string if (formValues.metadata && typeof formValues.metadata === "string") { try { diff --git a/ui/litellm-dashboard/src/components/mcp_server_management/MCPServerSelector.tsx b/ui/litellm-dashboard/src/components/mcp_server_management/MCPServerSelector.tsx new file mode 100644 index 00000000000..a8ddda2d257 --- /dev/null +++ b/ui/litellm-dashboard/src/components/mcp_server_management/MCPServerSelector.tsx @@ -0,0 +1,74 @@ +import React, { useEffect, useState } from 'react'; +import { Select } from 'antd'; +import { fetchMCPServers } from '../networking'; +import { MCPServer } from '../mcp_tools/types'; + + +interface MCPServerSelectorProps { + onChange: (selectedMCPServers: string[]) => void; + value?: string[]; + className?: string; + accessToken: string; + placeholder?: string; + disabled?: boolean; +} + +const MCPServerSelector: React.FC = ({ + onChange, + value, + className, + accessToken, + placeholder = "Select MCP servers", + disabled = false +}) => { + const [mcpServers, setMCPServers] = useState([]); + const [loading, setLoading] = useState(false); + + useEffect(() => { + const fetchMCPServerList = async () => { + if (!accessToken) return; + + setLoading(true); + try { + const response = await fetchMCPServers(accessToken); + if (response && Array.isArray(response)) { + // Direct array response + setMCPServers(response); + } else if (response.data && Array.isArray(response.data)) { + // Response with data wrapper + setMCPServers(response.data); + } + } catch (error) { + console.error("Error fetching MCP servers:", error); + } finally { + setLoading(false); + } + }; + + fetchMCPServerList(); + }, [accessToken]); + + return ( +
+