mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-13 18:23:20 +00:00
[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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user