mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-04-03 14:36:44 +08:00
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:
@@ -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<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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
27
src/App.tsx
27
src/App.tsx
@@ -640,14 +640,25 @@ function App() {
|
||||
</Button>
|
||||
)}
|
||||
{currentView === "mcp" && (
|
||||
<Button
|
||||
size="icon"
|
||||
onClick={() => mcpPanelRef.current?.openAdd()}
|
||||
className={`ml-auto ${addActionButtonClass}`}
|
||||
title={t("mcp.unifiedPanel.addServer")}
|
||||
>
|
||||
<Plus className="w-5 h-5" />
|
||||
</Button>
|
||||
<>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => mcpPanelRef.current?.openImport()}
|
||||
className="hover:bg-black/5 dark:hover:bg-white/5"
|
||||
>
|
||||
<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" && (
|
||||
<>
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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"] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -119,4 +119,11 @@ export const mcpApi = {
|
||||
): Promise<void> {
|
||||
return await invoke("toggle_mcp_app", { serverId, app, enabled });
|
||||
},
|
||||
|
||||
/**
|
||||
* 从所有应用导入 MCP 服务器
|
||||
*/
|
||||
async importFromApps(): Promise<number> {
|
||||
return await invoke("import_mcp_from_apps");
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user