From a38c8584d78e4cfabd7a408860a7e7cea4630a11 Mon Sep 17 00:00:00 2001 From: Qiyuan Jiao Date: Thu, 6 Nov 2025 10:38:45 +0800 Subject: [PATCH] feat: add edit and refresh functionality for MCP servers in settings tab (#680) * feat: add edit and refresh functionality for MCP servers in settings tab * feat: fix lint error and enhance MCP server dialog with validation and error handling * fix: add missing newline at the end of en.json file * feat: only refreshing specific servers * feat: add validation messages for MCP server configuration and improve server update logic --- web/messages/en.json | 17 +- web/messages/zh.json | 9 +- .../dialogs/edit-mcp-server-dialog.tsx | 160 ++++++++++++++++ web/src/app/settings/tabs/mcp-tab.tsx | 177 +++++++++++++++++- 4 files changed, 351 insertions(+), 12 deletions(-) create mode 100644 web/src/app/settings/dialogs/edit-mcp-server-dialog.tsx diff --git a/web/messages/en.json b/web/messages/en.json index 4aa4917..14c52b6 100644 --- a/web/messages/en.json +++ b/web/messages/en.json @@ -52,8 +52,15 @@ "learnMore": "learn more about MCP.", "enableDisable": "Enable/disable server", "deleteServer": "Delete server", + "editServer": "Edit server", + "refreshServer": "Refresh server", + "editServerDescription": "Edit the MCP server configuration", + "editServerNote": "Update the server configuration in JSON format", "disabled": "Disabled", - "new": "New" + "new": "New", + "invalidJson": "Invalid JSON format", + "validationFailed": "Validation failed", + "missingServerName": "Missing server name" }, "about": { "title": "About" @@ -81,9 +88,9 @@ }, "chat": { "page": { - "loading": "Loading DeerFlow...", - "welcomeUser": "Welcome, {username}", - "starOnGitHub": "Star DeerFlow on GitHub" + "loading": "Loading DeerFlow...", + "welcomeUser": "Welcome, {username}", + "starOnGitHub": "Star DeerFlow on GitHub" }, "welcome": { "greeting": "👋 Hello, there!", @@ -231,4 +238,4 @@ "contributeNow": "Contribute Now" } } -} +} \ No newline at end of file diff --git a/web/messages/zh.json b/web/messages/zh.json index ee8f13e..7407894 100644 --- a/web/messages/zh.json +++ b/web/messages/zh.json @@ -52,8 +52,15 @@ "learnMore": "了解更多关于 MCP 的信息。", "enableDisable": "启用/禁用服务器", "deleteServer": "删除服务器", + "editServer": "编辑服务器", + "refreshServer": "刷新服务器", + "editServerDescription": "编辑 MCP 服务器配置", + "editServerNote": "以 JSON 格式更新服务器配置", "disabled": "已禁用", - "new": "新增" + "new": "新增", + "invalidJson": "无效的 JSON 格式", + "validationFailed": "验证失败", + "missingServerName": "缺少服务器名称" }, "about": { "title": "关于" diff --git a/web/src/app/settings/dialogs/edit-mcp-server-dialog.tsx b/web/src/app/settings/dialogs/edit-mcp-server-dialog.tsx new file mode 100644 index 0000000..e4e7651 --- /dev/null +++ b/web/src/app/settings/dialogs/edit-mcp-server-dialog.tsx @@ -0,0 +1,160 @@ +// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates +// SPDX-License-Identifier: MIT + +import { Loader2 } from "lucide-react"; +import { useTranslations } from "next-intl"; +import { useCallback, useState } from "react"; + +import { Button } from "~/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "~/components/ui/dialog"; +import { Textarea } from "~/components/ui/textarea"; +import type { MCPServerMetadata } from "~/core/mcp"; +import { MCPConfigSchema } from "~/core/mcp"; + +export function EditMCPServerDialog({ + server, + onSave, + open, + onOpenChange, +}: { + server: MCPServerMetadata; + onSave: (config: string) => Promise; + open: boolean; + onOpenChange: (open: boolean) => void; +}) { + const t = useTranslations("settings.mcp"); + const commonT = useTranslations("common"); + const [config, setConfig] = useState( + JSON.stringify( + { + mcpServers: { + [server.name]: server.transport === 'stdio' + ? { + command: server.command, + args: server.args, + env: server.env, + } + : { + transport: server.transport, + url: server.url, + headers: server.headers, + }, + }, + }, + null, + 2 + ) + ); + const [processing, setProcessing] = useState(false); + const [validationError, setValidationError] = useState(null); + + const handleChange = useCallback((value: string) => { + setConfig(value); + setValidationError(null); + + if (!value.trim()) { + return; + } + + try { + const parsed = JSON.parse(value); + if (!("mcpServers" in parsed)) { + setValidationError("Missing `mcpServers` in JSON"); + return; + } + + const result = MCPConfigSchema.safeParse(parsed); + if (!result.success) { + if (result.error.errors[0]) { + const error = result.error.errors[0]; + if (error.code === "invalid_union") { + if (error.unionErrors[0]?.errors[0]) { + setValidationError(error.unionErrors[0].errors[0].message); + return; + } + } + setValidationError(error.message || t("validationFailed")); + return; + } + } + + const keys = Object.keys(parsed.mcpServers); + if (keys.length === 0) { + setValidationError(t("missingServerName")); + } + } catch { + setValidationError(t("invalidJson")); + } + }, [t]); + + const handleSave = useCallback(async () => { + setProcessing(true); + try { + const success = await onSave(config); + if (success) { + onOpenChange(false); + } + } catch (error) { + console.error('Failed to save server configuration:', error); + } finally { + setProcessing(false); + } + }, [config, onSave, onOpenChange]); + + return ( + + + + {t("editServer")} + + {t("editServerDescription")} + + + +
+