diff --git a/src-tauri/src/commands/mcp.rs b/src-tauri/src/commands/mcp.rs index 7abea8e0f..299fed6bf 100644 --- a/src-tauri/src/commands/mcp.rs +++ b/src-tauri/src/commands/mcp.rs @@ -192,3 +192,13 @@ pub async fn toggle_mcp_app( let app_ty = AppType::from_str(&app).map_err(|e| e.to_string())?; McpService::toggle_app(&state, &server_id, app_ty, enabled).map_err(|e| e.to_string()) } + +/// 从所有应用导入 MCP 服务器(复用已有的导入逻辑) +#[tauri::command] +pub async fn import_mcp_from_apps(state: State<'_, AppState>) -> Result { + let mut total = 0; + total += McpService::import_from_claude(&state).unwrap_or(0); + total += McpService::import_from_codex(&state).unwrap_or(0); + total += McpService::import_from_gemini(&state).unwrap_or(0); + Ok(total) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 154a4dcef..d6475138d 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -637,6 +637,7 @@ pub fn run() { commands::upsert_mcp_server, commands::delete_mcp_server, commands::toggle_mcp_app, + commands::import_mcp_from_apps, // Prompt management commands::get_prompts, commands::upsert_prompt, diff --git a/src-tauri/src/services/mcp.rs b/src-tauri/src/services/mcp.rs index 29f821d57..ea472af31 100644 --- a/src-tauri/src/services/mcp.rs +++ b/src-tauri/src/services/mcp.rs @@ -206,6 +206,8 @@ impl McpService { // 调用原有的导入逻辑(从 mcp.rs) let count = crate::mcp::import_from_claude(&mut temp_config)?; + let mut new_count = 0; + // 如果有导入的服务器,保存到数据库 if count > 0 { if let Some(servers) = &temp_config.mcp.servers { @@ -217,6 +219,8 @@ impl McpService { merged.apps.claude = true; merged } else { + // 真正的新服务器 + new_count += 1; server.clone() }; @@ -229,7 +233,7 @@ impl McpService { } } - Ok(count) + Ok(new_count) } /// 从 Codex 导入 MCP(v3.7.0 已更新为统一结构) @@ -240,6 +244,8 @@ impl McpService { // 调用原有的导入逻辑(从 mcp.rs) let count = crate::mcp::import_from_codex(&mut temp_config)?; + let mut new_count = 0; + // 如果有导入的服务器,保存到数据库 if count > 0 { if let Some(servers) = &temp_config.mcp.servers { @@ -251,6 +257,8 @@ impl McpService { merged.apps.codex = true; merged } else { + // 真正的新服务器 + new_count += 1; server.clone() }; @@ -263,7 +271,7 @@ impl McpService { } } - Ok(count) + Ok(new_count) } /// 从 Gemini 导入 MCP(v3.7.0 已更新为统一结构) @@ -274,6 +282,8 @@ impl McpService { // 调用原有的导入逻辑(从 mcp.rs) let count = crate::mcp::import_from_gemini(&mut temp_config)?; + let mut new_count = 0; + // 如果有导入的服务器,保存到数据库 if count > 0 { if let Some(servers) = &temp_config.mcp.servers { @@ -285,6 +295,8 @@ impl McpService { merged.apps.gemini = true; merged } else { + // 真正的新服务器 + new_count += 1; server.clone() }; @@ -297,6 +309,6 @@ impl McpService { } } - Ok(count) + Ok(new_count) } } diff --git a/src/App.tsx b/src/App.tsx index 8ee1b9150..19d9e6002 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -640,14 +640,25 @@ function App() { )} {currentView === "mcp" && ( - + <> + + + )} {currentView === "skills" && ( <> diff --git a/src/components/mcp/UnifiedMcpPanel.tsx b/src/components/mcp/UnifiedMcpPanel.tsx index f004b1c7d..1379b8c1c 100644 --- a/src/components/mcp/UnifiedMcpPanel.tsx +++ b/src/components/mcp/UnifiedMcpPanel.tsx @@ -3,12 +3,11 @@ import { useTranslation } from "react-i18next"; import { Server } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; -import { useAllMcpServers, useToggleMcpApp } from "@/hooks/useMcp"; +import { useAllMcpServers, useToggleMcpApp, useDeleteMcpServer, useImportMcpFromApps } from "@/hooks/useMcp"; import type { McpServer } from "@/types"; import type { AppId } from "@/lib/api/types"; import McpFormModal from "./McpFormModal"; import { ConfirmDialog } from "../ConfirmDialog"; -import { useDeleteMcpServer } from "@/hooks/useMcp"; import { Edit3, Trash2 } from "lucide-react"; import { settingsApi } from "@/lib/api"; import { mcpPresets } from "@/config/mcpPresets"; @@ -24,6 +23,7 @@ interface UnifiedMcpPanelProps { */ export interface UnifiedMcpPanelHandle { openAdd: () => void; + openImport: () => void; } const UnifiedMcpPanel = React.forwardRef< @@ -44,6 +44,7 @@ const UnifiedMcpPanel = React.forwardRef< const { data: serversMap, isLoading } = useAllMcpServers(); const toggleAppMutation = useToggleMcpApp(); const deleteServerMutation = useDeleteMcpServer(); + const importMutation = useImportMcpFromApps(); // Convert serversMap to array for easier rendering const serverEntries = useMemo((): Array<[string, McpServer]> => { @@ -86,8 +87,24 @@ const UnifiedMcpPanel = React.forwardRef< setIsFormOpen(true); }; + const handleImport = async () => { + try { + const count = await importMutation.mutateAsync(); + if (count === 0) { + toast.success(t("mcp.unifiedPanel.noImportFound"), { closeButton: true }); + } else { + toast.success(t("mcp.unifiedPanel.importSuccess", { count }), { closeButton: true }); + } + } catch (error) { + toast.error(t("common.error"), { + description: String(error), + }); + } + }; + React.useImperativeHandle(ref, () => ({ openAdd: handleAdd, + openImport: handleImport, })); const handleDelete = (id: string) => { diff --git a/src/hooks/useMcp.ts b/src/hooks/useMcp.ts index 4af74f3ee..ff9f505fd 100644 --- a/src/hooks/useMcp.ts +++ b/src/hooks/useMcp.ts @@ -59,3 +59,16 @@ export function useDeleteMcpServer() { }, }); } + +/** + * 从所有应用导入 MCP 服务器 + */ +export function useImportMcpFromApps() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: () => mcpApi.importFromApps(), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["mcp", "all"] }); + }, + }); +} diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index a8631f99f..1661fc9a1 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -565,6 +565,7 @@ }, "mcp": { "title": "MCP Management", + "import": "Import", "claudeTitle": "Claude Code MCP Management", "codexTitle": "Codex MCP Management", "geminiTitle": "Gemini MCP Management", @@ -576,6 +577,8 @@ "deleteConfirm": "Are you sure you want to delete server \"{{id}}\"? This action cannot be undone.", "noServers": "No servers yet", "enabledApps": "Enabled Apps", + "noImportFound": "No MCP servers to import found. All servers are already managed by CC Switch.", + "importSuccess": "Successfully imported {{count}} MCP servers", "apps": { "claude": "Claude", "codex": "Codex", diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index 924e1a2d5..f9bb78558 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -565,6 +565,7 @@ }, "mcp": { "title": "MCP 管理", + "import": "インポート", "claudeTitle": "Claude Code MCP 管理", "codexTitle": "Codex MCP 管理", "geminiTitle": "Gemini MCP 管理", @@ -576,6 +577,8 @@ "deleteConfirm": "サーバー「{{id}}」を削除しますか?この操作は元に戻せません。", "noServers": "まだサーバーがありません", "enabledApps": "有効なアプリ", + "noImportFound": "インポートする MCP サーバーが見つかりませんでした。すべてのサーバーは CC Switch で管理されています。", + "importSuccess": "{{count}} 個の MCP サーバーをインポートしました", "apps": { "claude": "Claude", "codex": "Codex", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 2a950670e..5b9d3c56a 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -565,6 +565,7 @@ }, "mcp": { "title": "MCP 管理", + "import": "导入", "claudeTitle": "Claude Code MCP 管理", "codexTitle": "Codex MCP 管理", "geminiTitle": "Gemini MCP 管理", @@ -576,6 +577,8 @@ "deleteConfirm": "确定要删除服务器 \"{{id}}\" 吗?此操作无法撤销。", "noServers": "暂无服务器", "enabledApps": "启用的应用", + "noImportFound": "未发现需要导入的 MCP 服务器。所有服务器已在 CC Switch 统一管理中。", + "importSuccess": "成功导入 {{count}} 个 MCP 服务器", "apps": { "claude": "Claude", "codex": "Codex", diff --git a/src/lib/api/mcp.ts b/src/lib/api/mcp.ts index 4f97754ae..983daf70e 100644 --- a/src/lib/api/mcp.ts +++ b/src/lib/api/mcp.ts @@ -119,4 +119,11 @@ export const mcpApi = { ): Promise { return await invoke("toggle_mcp_app", { serverId, app, enabled }); }, + + /** + * 从所有应用导入 MCP 服务器 + */ + async importFromApps(): Promise { + return await invoke("import_mcp_from_apps"); + }, };