[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
This commit is contained in:
Ishaan Jaff
2025-06-13 09:56:58 -07:00
committed by GitHub
parent 196a7269f6
commit 891a71b7a0
4 changed files with 150 additions and 4 deletions
@@ -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<MCPServerEditProps> = ({ mcpServer, accessToken, onCancel, onSuccess }) => {
const [form] = Form.useForm();
const handleSave = async (values: Record<string, any>) => {
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 (
<Form form={form} onFinish={handleSave} initialValues={mcpServer} layout="vertical">
<Form.Item label="MCP Server Name" name="alias">
<TextInput />
</Form.Item>
<Form.Item label="Description" name="description">
<TextInput />
</Form.Item>
<Form.Item label="MCP Server URL" name="url" rules={[{ required: true, message: "Please enter a server URL" }]}>
<TextInput />
</Form.Item>
<Form.Item label="Transport Type" name="transport" rules={[{ required: true }]}>
<Select>
<Select.Option value="sse">Server-Sent Events (SSE)</Select.Option>
<Select.Option value="http">HTTP</Select.Option>
</Select>
</Form.Item>
<Form.Item label="Authentication" name="auth_type" rules={[{ required: true }]}>
<Select>
<Select.Option value="none">None</Select.Option>
<Select.Option value="api_key">API Key</Select.Option>
<Select.Option value="bearer_token">Bearer Token</Select.Option>
<Select.Option value="basic">Basic Auth</Select.Option>
</Select>
</Form.Item>
<Form.Item label="MCP Version" name="spec_version" rules={[{ required: true }]}>
<Select>
<Select.Option value="2025-03-26">2025-03-26 (Latest)</Select.Option>
<Select.Option value="2024-11-05">2024-11-05</Select.Option>
</Select>
</Form.Item>
<div className="flex justify-end gap-2">
<AntdButton onClick={onCancel}>Cancel</AntdButton>
<Button type="submit">Save Changes</Button>
</div>
</Form>
);
};
export default MCPServerEdit;
@@ -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<MCPServerViewProps> = ({
userRole,
userID,
}) => {
const [editing, setEditing] = useState(isEditing);
const handleSuccess = (updated: MCPServer) => {
setEditing(false);
onBack();
};
return (
<div className="p-4">
<div className="flex justify-between items-center mb-6">
@@ -49,7 +57,7 @@ export const MCPServerView: React.FC<MCPServerViewProps> = ({
</div>
{/* TODO: magic number for index */}
<TabGroup defaultIndex={isEditing ? 2 : 0}>
<TabGroup defaultIndex={editing ? 2 : 0}>
<TabList className="mb-4">
{[
<Tab key="overview">Overview</Tab>,
@@ -97,8 +105,48 @@ export const MCPServerView: React.FC<MCPServerViewProps> = ({
<TabPanel>
<Card>
<div className="flex justify-between items-center mb-4">
<Title>Editing MCP Servers coming soon!</Title>
<Title>MCP Server Settings</Title>
{editing ? null : (
<Button variant="light" onClick={() => setEditing(true)}>
Edit Settings
</Button>
)}
</div>
{editing ? (
<MCPServerEdit
mcpServer={mcpServer}
accessToken={accessToken}
onCancel={() => setEditing(false)}
onSuccess={handleSuccess}
/>
) : (
<div className="space-y-4">
<div>
<Text className="font-medium">Server Name</Text>
<div>{mcpServer.alias}</div>
</div>
<div>
<Text className="font-medium">Description</Text>
<div>{mcpServer.description}</div>
</div>
<div>
<Text className="font-medium">URL</Text>
<div className="font-mono">{mcpServer.url}</div>
</div>
<div>
<Text className="font-medium">Transport</Text>
<div>{handleTransport(mcpServer.transport)}</div>
</div>
<div>
<Text className="font-medium">Auth Type</Text>
<div>{handleAuth(mcpServer.auth_type)}</div>
</div>
<div>
<Text className="font-medium">Spec Version</Text>
<div>{mcpServer.spec_version}</div>
</div>
</div>
)}
</Card>
</TabPanel>
</TabPanels>
@@ -153,7 +153,11 @@ const MCPServers: React.FC<MCPServerProps> = ({
(server: MCPServer) => server.server_id === selectedServerId
) || {}
}
onBack={() => setSelectedServerId(null)}
onBack={() => {
setEditServer(false);
setSelectedServerId(null);
refetch();
}}
isProxyAdmin={isAdminRole(userRole)}
isEditing={editServer}
accessToken={accessToken}
@@ -4578,6 +4578,33 @@ export const createMCPServer = async (
}
};
export const updateMCPServer = async (
accessToken: string,
formValues: Record<string, any>
) => {
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