From 891a71b7a0eda4e65814935c2d2f24050a15eed3 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Fri, 13 Jun 2025 09:56:58 -0700 Subject: [PATCH] [Feat] UI Allow editing mcp servers (#11693) * ui - clean up MCP server table * ui - clean up table * fix mb-4 * ui fix * add support for editing MCP servers --- .../components/mcp_tools/mcp_server_edit.tsx | 67 +++++++++++++++++++ .../components/mcp_tools/mcp_server_view.tsx | 54 ++++++++++++++- .../src/components/mcp_tools/mcp_servers.tsx | 6 +- .../src/components/networking.tsx | 27 ++++++++ 4 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.tsx 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 new file mode 100644 index 0000000000..1f1b594498 --- /dev/null +++ b/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_edit.tsx @@ -0,0 +1,67 @@ +import React from "react"; +import { Form, Select, Button as AntdButton, message } from "antd"; +import { Button, TextInput } from "@tremor/react"; +import { MCPServer } from "./types"; +import { updateMCPServer } from "../networking"; + +interface MCPServerEditProps { + mcpServer: MCPServer; + accessToken: string | null; + onCancel: () => void; + onSuccess: (server: MCPServer) => void; +} + +const MCPServerEdit: React.FC = ({ mcpServer, accessToken, onCancel, onSuccess }) => { + const [form] = Form.useForm(); + + const handleSave = async (values: Record) => { + if (!accessToken) return; + try { + const updated = await updateMCPServer(accessToken, { ...values, server_id: mcpServer.server_id }); + message.success("MCP Server updated successfully"); + onSuccess(updated); + } catch (error: any) { + message.error("Failed to update MCP Server" + (error?.message ? `: ${error.message}` : "")); + } + }; + + return ( +
+ + + + + + + + + + + + + + + + + + +
+ Cancel + +
+
+ ); +}; + +export default MCPServerEdit; diff --git a/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_view.tsx b/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_view.tsx index 38eb06f8e5..2d65d1cfb7 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_view.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_view.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useState } from "react"; import { Title, @@ -16,6 +16,7 @@ import { import { MCPServer, handleTransport, handleAuth } from "./types"; // TODO: Move Tools viewer from index file import { MCPToolsViewer } from "."; +import MCPServerEdit from "./mcp_server_edit"; interface MCPServerViewProps { mcpServer: MCPServer; @@ -36,6 +37,13 @@ export const MCPServerView: React.FC = ({ userRole, userID, }) => { + const [editing, setEditing] = useState(isEditing); + + const handleSuccess = (updated: MCPServer) => { + setEditing(false); + onBack(); + }; + return (
@@ -49,7 +57,7 @@ export const MCPServerView: React.FC = ({
{/* TODO: magic number for index */} - + {[ Overview, @@ -97,8 +105,48 @@ export const MCPServerView: React.FC = ({
- Editing MCP Servers coming soon! + MCP Server Settings + {editing ? null : ( + + )}
+ {editing ? ( + setEditing(false)} + onSuccess={handleSuccess} + /> + ) : ( +
+
+ Server Name +
{mcpServer.alias}
+
+
+ Description +
{mcpServer.description}
+
+
+ URL +
{mcpServer.url}
+
+
+ Transport +
{handleTransport(mcpServer.transport)}
+
+
+ Auth Type +
{handleAuth(mcpServer.auth_type)}
+
+
+ Spec Version +
{mcpServer.spec_version}
+
+
+ )}
diff --git a/ui/litellm-dashboard/src/components/mcp_tools/mcp_servers.tsx b/ui/litellm-dashboard/src/components/mcp_tools/mcp_servers.tsx index 86ad1b7434..19e72e1589 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/mcp_servers.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/mcp_servers.tsx @@ -153,7 +153,11 @@ const MCPServers: React.FC = ({ (server: MCPServer) => server.server_id === selectedServerId ) || {} } - onBack={() => setSelectedServerId(null)} + onBack={() => { + setEditServer(false); + setSelectedServerId(null); + refetch(); + }} isProxyAdmin={isAdminRole(userRole)} isEditing={editServer} accessToken={accessToken} diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 3988627b12..6e5a5e25e8 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -4578,6 +4578,33 @@ export const createMCPServer = async ( } }; +export const updateMCPServer = async ( + accessToken: string, + formValues: Record +) => { + try { + const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/mcp/server` : `/v1/mcp/server`; + const response = await fetch(url, { + method: "PUT", + headers: { + [globalLitellmHeaderName]: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(formValues), + }); + + if (!response.ok) { + const errorData = await response.text(); + handleError(errorData); + throw new Error("Network response was not ok"); + } + return await response.json(); + } catch (error) { + console.error("Failed to update MCP server:", error); + throw error; + } +}; + export const deleteMCPServer = async ( accessToken: String, serverId: String