From 4543664ba2b85efa8465336c12045b3bfaa4c45b Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 10 Oct 2025 22:34:38 +0800 Subject: [PATCH] refactor(mcp): remove installed preset badge and env-related preset logic - Move MCP presets into Add modal, no auto-seeding into list - Replace env-required presets with context7 (no env needed) - Remove requiresEnv checks/prompts from list and form - Keep Docs button; maintain clean list UI --- src/components/mcp/McpFormModal.tsx | 74 +++++++++++++++++++++++++++++ src/components/mcp/McpListItem.tsx | 24 ++++++++++ src/components/mcp/McpPanel.tsx | 74 ++++------------------------- src/config/mcpPresets.ts | 44 ++++++++++++----- 4 files changed, 137 insertions(+), 79 deletions(-) diff --git a/src/components/mcp/McpFormModal.tsx b/src/components/mcp/McpFormModal.tsx index 5a2522b..2bb0ff2 100644 --- a/src/components/mcp/McpFormModal.tsx +++ b/src/components/mcp/McpFormModal.tsx @@ -2,6 +2,7 @@ import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { X, Save, AlertCircle } from "lucide-react"; import { McpServer } from "../../types"; +import { mcpPresets } from "../../config/mcpPresets"; import { buttonStyles, inputStyles } from "../../lib/styles"; import McpWizardModal from "./McpWizardModal"; import { extractErrorMessage } from "../../utils/errorUtils"; @@ -66,6 +67,11 @@ const McpFormModal: React.FC = ({ // 编辑模式下禁止修改 ID const isEditing = !!editingId; + // 预设选择状态(仅新增模式显示;-1 表示自定义) + const [selectedPreset, setSelectedPreset] = useState( + isEditing ? null : -1, + ); + const handleIdChange = (value: string) => { setFormId(value); if (!isEditing) { @@ -74,6 +80,39 @@ const McpFormModal: React.FC = ({ } }; + const ensureUniqueId = (base: string): string => { + let candidate = base.trim(); + if (!candidate) candidate = "mcp-server"; + if (!existingIds.includes(candidate)) return candidate; + let i = 1; + while (existingIds.includes(`${candidate}-${i}`)) i++; + return `${candidate}-${i}`; + }; + + // 应用预设(写入表单但不落库) + const applyPreset = (index: number) => { + if (index < 0 || index >= mcpPresets.length) return; + const p = mcpPresets[index]; + const id = ensureUniqueId(p.id); + setFormId(id); + setFormDescription(p.description || ""); + const json = JSON.stringify(p.server, null, 2); + setFormJson(json); + // 触发一次校验 + setJsonError(validateJson(json)); + setSelectedPreset(index); + }; + + // 切回自定义 + const applyCustom = () => { + setSelectedPreset(-1); + // 恢复到空白模板 + setFormId(""); + setFormDescription(""); + setFormJson(""); + setJsonError(""); + }; + const handleJsonChange = (value: string) => { setFormJson(value); @@ -218,6 +257,41 @@ const McpFormModal: React.FC = ({ {/* Content */}
+ {/* 预设选择(仅新增时展示) */} + {!isEditing && ( +
+
+ {t("mcp.presets.title")} +
+
+ + {mcpPresets.map((p, idx) => ( + + ))} +
+ {/* 无需环境变量提示:已移除 */} +
+ )} {/* ID (标题) */}
diff --git a/src/components/mcp/McpListItem.tsx b/src/components/mcp/McpListItem.tsx index 2d1a18e..189d177 100644 --- a/src/components/mcp/McpListItem.tsx +++ b/src/components/mcp/McpListItem.tsx @@ -2,6 +2,7 @@ import React from "react"; import { useTranslation } from "react-i18next"; import { Edit3, Trash2 } from "lucide-react"; import { McpServer } from "../../types"; +import { mcpPresets } from "../../config/mcpPresets"; import { cardStyles, buttonStyles, cn } from "../../lib/styles"; import McpToggle from "./McpToggle"; @@ -32,6 +33,19 @@ const McpListItem: React.FC = ({ // 只显示 description,没有则留空 const description = (server as any).description || ""; + // 匹配预设元信息(用于展示文档链接等) + const meta = mcpPresets.find((p) => p.id === id); + + const openDocs = async () => { + const url = meta?.docs || meta?.homepage; + if (!url) return; + try { + await window.api.openExternal(url); + } catch { + // ignore + } + }; + return (
@@ -53,10 +67,20 @@ const McpListItem: React.FC = ({ {description}

)} + {/* 预设标记已移除 */}
{/* 右侧:操作按钮 */}
+ {meta?.docs && ( + + )}
) : ( (() => { - const notInstalledPresets = mcpPresets.filter( - (p) => !servers[p.id], - ); - const hasAny = - serverEntries.length > 0 || notInstalledPresets.length > 0; + const hasAny = serverEntries.length > 0; if (!hasAny) { return (
@@ -259,37 +231,7 @@ const McpPanel: React.FC = ({ onClose, onNotify, appType }) => { /> ))} - {/* 预设(未安装) */} - {notInstalledPresets.map((p) => { - return ( -
-
-
- handleToggle(p.id, en)} - /> -
-
-

- {p.id} -

- {p.description && ( -

- {p.description} -

- )} -
-
-
- ); - })} + {/* 预设已移至“新增 MCP”面板中展示与套用 */}
); })() diff --git a/src/config/mcpPresets.ts b/src/config/mcpPresets.ts index 7dcd192..613cd04 100644 --- a/src/config/mcpPresets.ts +++ b/src/config/mcpPresets.ts @@ -8,20 +8,38 @@ export type McpPreset = { server: McpServer; homepage?: string; docs?: string; - requiresEnv?: string[]; }; -// 预设库(数据文件,当前未接入 UI,便于后续“一键启用”) -// 注意:预设数据暂时清空,仅保留结构与引用位置。 -// 原因: -// - 近期决定将 MCP SSOT 拆分为 mcp.claude / mcp.codex,不同客户端的格式与支持能力不同; -// - 需要先完善“隐藏预设/不回种”机制与导入/同步策略,避免用户删除后被自动回填; -// - 在上述机制与 Codex 适配完成前,避免内置示例误导或造成意外写入。 -// 后续计划(占位备注): -// - 重新引入官方/社区 MCP 预设,区分 `source: "preset"`; -// - 支持每客户端(Claude/Codex)独立隐藏名单 `hiddenPresets`,仅影响自动回种; -// - UI 提供“删除并隐藏”与“恢复预设”操作; -// - 导入/同步与启用状态解耦,仅启用项投影至对应客户端的用户配置文件。 -export const mcpPresets: McpPreset[] = []; +// 预设 MCP(逻辑简化版): +// - 仅包含最常用、可快速落地的 stdio 模式示例 +// - 不涉及分类/模板/测速等复杂逻辑,默认以 disabled 形式“回种”到 config.json +// - 用户可在 MCP 面板中一键启用/编辑 +export const mcpPresets: McpPreset[] = [ + { + id: "fetch", + name: "mcp-server-fetch", + description: + "通用 HTTP Fetch(stdio,经 uvx 运行 mcp-server-fetch),适合快速请求接口/抓取数据", + tags: ["stdio", "http"], + server: { + type: "stdio", + command: "uvx", + args: ["mcp-server-fetch"], + } as McpServer, + }, + { + id: "context7", + name: "mcp-context7", + description: "Context7 示例(无需环境变量),可按需在表单中调整参数", + tags: ["stdio", "docs"], + server: { + type: "stdio", + command: "uvx", + // 使用 fetch 服务器作为基础示例,用户可在表单中补充 args + args: ["mcp-server-fetch"], + } as McpServer, + docs: "https://github.com/context7", + }, +]; export default mcpPresets;