feat(mcp): add import button to import MCP servers from apps

- Add import_mcp_from_apps command that reuses existing import logic
- Add Import button in MCP panel header (consistent with Skills)
- Fix import count to only return truly new servers (not already in DB)
- Update translations for import success/no-import messages (zh/en/ja)
This commit is contained in:
Jason
2026-01-03 10:04:46 +08:00
parent 6460c1d5dd
commit 86e802bd4b
10 changed files with 93 additions and 13 deletions
+10
View File
@@ -192,3 +192,13 @@ pub async fn toggle_mcp_app(
let app_ty = AppType::from_str(&app).map_err(|e| e.to_string())?; 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()) 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<usize, String> {
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)
}
+1
View File
@@ -637,6 +637,7 @@ pub fn run() {
commands::upsert_mcp_server, commands::upsert_mcp_server,
commands::delete_mcp_server, commands::delete_mcp_server,
commands::toggle_mcp_app, commands::toggle_mcp_app,
commands::import_mcp_from_apps,
// Prompt management // Prompt management
commands::get_prompts, commands::get_prompts,
commands::upsert_prompt, commands::upsert_prompt,
+15 -3
View File
@@ -206,6 +206,8 @@ impl McpService {
// 调用原有的导入逻辑(从 mcp.rs) // 调用原有的导入逻辑(从 mcp.rs)
let count = crate::mcp::import_from_claude(&mut temp_config)?; let count = crate::mcp::import_from_claude(&mut temp_config)?;
let mut new_count = 0;
// 如果有导入的服务器,保存到数据库 // 如果有导入的服务器,保存到数据库
if count > 0 { if count > 0 {
if let Some(servers) = &temp_config.mcp.servers { if let Some(servers) = &temp_config.mcp.servers {
@@ -217,6 +219,8 @@ impl McpService {
merged.apps.claude = true; merged.apps.claude = true;
merged merged
} else { } else {
// 真正的新服务器
new_count += 1;
server.clone() server.clone()
}; };
@@ -229,7 +233,7 @@ impl McpService {
} }
} }
Ok(count) Ok(new_count)
} }
/// 从 Codex 导入 MCPv3.7.0 已更新为统一结构) /// 从 Codex 导入 MCPv3.7.0 已更新为统一结构)
@@ -240,6 +244,8 @@ impl McpService {
// 调用原有的导入逻辑(从 mcp.rs) // 调用原有的导入逻辑(从 mcp.rs)
let count = crate::mcp::import_from_codex(&mut temp_config)?; let count = crate::mcp::import_from_codex(&mut temp_config)?;
let mut new_count = 0;
// 如果有导入的服务器,保存到数据库 // 如果有导入的服务器,保存到数据库
if count > 0 { if count > 0 {
if let Some(servers) = &temp_config.mcp.servers { if let Some(servers) = &temp_config.mcp.servers {
@@ -251,6 +257,8 @@ impl McpService {
merged.apps.codex = true; merged.apps.codex = true;
merged merged
} else { } else {
// 真正的新服务器
new_count += 1;
server.clone() server.clone()
}; };
@@ -263,7 +271,7 @@ impl McpService {
} }
} }
Ok(count) Ok(new_count)
} }
/// 从 Gemini 导入 MCPv3.7.0 已更新为统一结构) /// 从 Gemini 导入 MCPv3.7.0 已更新为统一结构)
@@ -274,6 +282,8 @@ impl McpService {
// 调用原有的导入逻辑(从 mcp.rs) // 调用原有的导入逻辑(从 mcp.rs)
let count = crate::mcp::import_from_gemini(&mut temp_config)?; let count = crate::mcp::import_from_gemini(&mut temp_config)?;
let mut new_count = 0;
// 如果有导入的服务器,保存到数据库 // 如果有导入的服务器,保存到数据库
if count > 0 { if count > 0 {
if let Some(servers) = &temp_config.mcp.servers { if let Some(servers) = &temp_config.mcp.servers {
@@ -285,6 +295,8 @@ impl McpService {
merged.apps.gemini = true; merged.apps.gemini = true;
merged merged
} else { } else {
// 真正的新服务器
new_count += 1;
server.clone() server.clone()
}; };
@@ -297,6 +309,6 @@ impl McpService {
} }
} }
Ok(count) Ok(new_count)
} }
} }
+19 -8
View File
@@ -640,14 +640,25 @@ function App() {
</Button> </Button>
)} )}
{currentView === "mcp" && ( {currentView === "mcp" && (
<Button <>
size="icon" <Button
onClick={() => mcpPanelRef.current?.openAdd()} variant="ghost"
className={`ml-auto ${addActionButtonClass}`} size="sm"
title={t("mcp.unifiedPanel.addServer")} onClick={() => mcpPanelRef.current?.openImport()}
> className="hover:bg-black/5 dark:hover:bg-white/5"
<Plus className="w-5 h-5" /> >
</Button> <Download className="w-4 h-4 mr-2" />
{t("mcp.import")}
</Button>
<Button
size="icon"
onClick={() => mcpPanelRef.current?.openAdd()}
className={`ml-auto ${addActionButtonClass}`}
title={t("mcp.unifiedPanel.addServer")}
>
<Plus className="w-5 h-5" />
</Button>
</>
)} )}
{currentView === "skills" && ( {currentView === "skills" && (
<> <>
+19 -2
View File
@@ -3,12 +3,11 @@ import { useTranslation } from "react-i18next";
import { Server } from "lucide-react"; import { Server } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch"; 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 { McpServer } from "@/types";
import type { AppId } from "@/lib/api/types"; import type { AppId } from "@/lib/api/types";
import McpFormModal from "./McpFormModal"; import McpFormModal from "./McpFormModal";
import { ConfirmDialog } from "../ConfirmDialog"; import { ConfirmDialog } from "../ConfirmDialog";
import { useDeleteMcpServer } from "@/hooks/useMcp";
import { Edit3, Trash2 } from "lucide-react"; import { Edit3, Trash2 } from "lucide-react";
import { settingsApi } from "@/lib/api"; import { settingsApi } from "@/lib/api";
import { mcpPresets } from "@/config/mcpPresets"; import { mcpPresets } from "@/config/mcpPresets";
@@ -24,6 +23,7 @@ interface UnifiedMcpPanelProps {
*/ */
export interface UnifiedMcpPanelHandle { export interface UnifiedMcpPanelHandle {
openAdd: () => void; openAdd: () => void;
openImport: () => void;
} }
const UnifiedMcpPanel = React.forwardRef< const UnifiedMcpPanel = React.forwardRef<
@@ -44,6 +44,7 @@ const UnifiedMcpPanel = React.forwardRef<
const { data: serversMap, isLoading } = useAllMcpServers(); const { data: serversMap, isLoading } = useAllMcpServers();
const toggleAppMutation = useToggleMcpApp(); const toggleAppMutation = useToggleMcpApp();
const deleteServerMutation = useDeleteMcpServer(); const deleteServerMutation = useDeleteMcpServer();
const importMutation = useImportMcpFromApps();
// Convert serversMap to array for easier rendering // Convert serversMap to array for easier rendering
const serverEntries = useMemo((): Array<[string, McpServer]> => { const serverEntries = useMemo((): Array<[string, McpServer]> => {
@@ -86,8 +87,24 @@ const UnifiedMcpPanel = React.forwardRef<
setIsFormOpen(true); 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, () => ({ React.useImperativeHandle(ref, () => ({
openAdd: handleAdd, openAdd: handleAdd,
openImport: handleImport,
})); }));
const handleDelete = (id: string) => { const handleDelete = (id: string) => {
+13
View File
@@ -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"] });
},
});
}
+3
View File
@@ -565,6 +565,7 @@
}, },
"mcp": { "mcp": {
"title": "MCP Management", "title": "MCP Management",
"import": "Import",
"claudeTitle": "Claude Code MCP Management", "claudeTitle": "Claude Code MCP Management",
"codexTitle": "Codex MCP Management", "codexTitle": "Codex MCP Management",
"geminiTitle": "Gemini 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.", "deleteConfirm": "Are you sure you want to delete server \"{{id}}\"? This action cannot be undone.",
"noServers": "No servers yet", "noServers": "No servers yet",
"enabledApps": "Enabled Apps", "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": { "apps": {
"claude": "Claude", "claude": "Claude",
"codex": "Codex", "codex": "Codex",
+3
View File
@@ -565,6 +565,7 @@
}, },
"mcp": { "mcp": {
"title": "MCP 管理", "title": "MCP 管理",
"import": "インポート",
"claudeTitle": "Claude Code MCP 管理", "claudeTitle": "Claude Code MCP 管理",
"codexTitle": "Codex MCP 管理", "codexTitle": "Codex MCP 管理",
"geminiTitle": "Gemini MCP 管理", "geminiTitle": "Gemini MCP 管理",
@@ -576,6 +577,8 @@
"deleteConfirm": "サーバー「{{id}}」を削除しますか?この操作は元に戻せません。", "deleteConfirm": "サーバー「{{id}}」を削除しますか?この操作は元に戻せません。",
"noServers": "まだサーバーがありません", "noServers": "まだサーバーがありません",
"enabledApps": "有効なアプリ", "enabledApps": "有効なアプリ",
"noImportFound": "インポートする MCP サーバーが見つかりませんでした。すべてのサーバーは CC Switch で管理されています。",
"importSuccess": "{{count}} 個の MCP サーバーをインポートしました",
"apps": { "apps": {
"claude": "Claude", "claude": "Claude",
"codex": "Codex", "codex": "Codex",
+3
View File
@@ -565,6 +565,7 @@
}, },
"mcp": { "mcp": {
"title": "MCP 管理", "title": "MCP 管理",
"import": "导入",
"claudeTitle": "Claude Code MCP 管理", "claudeTitle": "Claude Code MCP 管理",
"codexTitle": "Codex MCP 管理", "codexTitle": "Codex MCP 管理",
"geminiTitle": "Gemini MCP 管理", "geminiTitle": "Gemini MCP 管理",
@@ -576,6 +577,8 @@
"deleteConfirm": "确定要删除服务器 \"{{id}}\" 吗?此操作无法撤销。", "deleteConfirm": "确定要删除服务器 \"{{id}}\" 吗?此操作无法撤销。",
"noServers": "暂无服务器", "noServers": "暂无服务器",
"enabledApps": "启用的应用", "enabledApps": "启用的应用",
"noImportFound": "未发现需要导入的 MCP 服务器。所有服务器已在 CC Switch 统一管理中。",
"importSuccess": "成功导入 {{count}} 个 MCP 服务器",
"apps": { "apps": {
"claude": "Claude", "claude": "Claude",
"codex": "Codex", "codex": "Codex",
+7
View File
@@ -119,4 +119,11 @@ export const mcpApi = {
): Promise<void> { ): Promise<void> {
return await invoke("toggle_mcp_app", { serverId, app, enabled }); return await invoke("toggle_mcp_app", { serverId, app, enabled });
}, },
/**
* 从所有应用导入 MCP 服务器
*/
async importFromApps(): Promise<number> {
return await invoke("import_mcp_from_apps");
},
}; };