Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src-tauri/src/app_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ use crate::prompt_files::prompt_file_path;
use crate::provider::ProviderManager;

/// 应用类型
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AppType {
Claude,
Expand Down
11 changes: 4 additions & 7 deletions src-tauri/src/commands/mcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,11 +196,8 @@ pub async fn toggle_mcp_app(

/// 从所有应用导入 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);
total += McpService::import_from_opencode(&state).unwrap_or(0);
Ok(total)
pub async fn import_mcp_from_apps(
state: State<'_, AppState>,
) -> Result<crate::mcp::McpImportResult, String> {
McpService::import_from_apps_detailed(&state).map_err(|e| e.to_string())
}
4 changes: 3 additions & 1 deletion src-tauri/src/database/dao/mcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use crate::app_config::{McpApps, McpServer};
use crate::database::{lock_conn, Database};
use crate::error::AppError;
use crate::mcp::normalize_server_spec;
use indexmap::IndexMap;
use rusqlite::params;

Expand Down Expand Up @@ -66,6 +67,7 @@ impl Database {

/// 保存 MCP 服务器
pub fn save_mcp_server(&self, server: &McpServer) -> Result<(), AppError> {
let canonical_server = normalize_server_spec(&server.server)?;
let conn = lock_conn!(self.conn);
conn.execute(
"INSERT OR REPLACE INTO mcp_servers (
Expand All @@ -75,7 +77,7 @@ impl Database {
params![
server.id,
server.name,
serde_json::to_string(&server.server).map_err(|e| AppError::Database(format!(
serde_json::to_string(&canonical_server).map_err(|e| AppError::Database(format!(
"Failed to serialize server config: {e}"
)))?,
server.description,
Expand Down
9 changes: 5 additions & 4 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@ pub use database::Database;
pub use deeplink::{import_provider_from_deeplink, parse_deeplink_url, DeepLinkImportRequest};
pub use error::AppError;
pub use mcp::{
import_from_claude, import_from_codex, import_from_gemini, remove_server_from_claude,
remove_server_from_codex, remove_server_from_gemini, sync_enabled_to_claude,
sync_enabled_to_codex, sync_enabled_to_gemini, sync_single_server_to_claude,
sync_single_server_to_codex, sync_single_server_to_gemini,
import_from_claude, import_from_codex, import_from_gemini, McpImportIssue,
McpImportIssueKind, McpImportResult, remove_server_from_claude, remove_server_from_codex,
remove_server_from_gemini, sync_enabled_to_claude, sync_enabled_to_codex,
sync_enabled_to_gemini, sync_single_server_to_claude, sync_single_server_to_codex,
sync_single_server_to_gemini,
};
pub use provider::{Provider, ProviderMeta};
pub use services::{
Expand Down
72 changes: 23 additions & 49 deletions src-tauri/src/mcp/claude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
use serde_json::Value;
use std::collections::HashMap;

use crate::app_config::{McpApps, McpConfig, McpServer, MultiAppConfig};
use crate::app_config::{AppType, McpConfig, MultiAppConfig};
use crate::error::AppError;

use super::validation::{extract_server_spec, validate_server_spec};
use super::validation::{extract_server_spec, normalize_server_spec};
use super::{apply_parsed_import, build_imported_server, invalid_issue, ParsedImport};

fn should_sync_claude_mcp() -> bool {
// Claude 未安装/未初始化时:通常 ~/.claude 目录与 ~/.claude.json 都不存在。
Expand Down Expand Up @@ -49,66 +50,39 @@ pub fn sync_enabled_to_claude(config: &MultiAppConfig) -> Result<(), AppError> {
/// 从 ~/.claude.json 导入 mcpServers 到统一结构(v3.7.0+)
/// 已存在的服务器将启用 Claude 应用,不覆盖其他字段和应用状态
pub fn import_from_claude(config: &mut MultiAppConfig) -> Result<usize, AppError> {
let parsed = parse_import_from_claude()?;
apply_parsed_import(config, parsed, AppType::Claude)
}

pub(crate) fn parse_import_from_claude() -> Result<ParsedImport, AppError> {
let text_opt = crate::claude_mcp::read_mcp_json()?;
let Some(text) = text_opt else { return Ok(0) };
let Some(text) = text_opt else {
return Ok(ParsedImport::default());
};

let v: Value = serde_json::from_str(&text)
.map_err(|e| AppError::McpValidation(format!("解析 ~/.claude.json 失败: {e}")))?;
let Some(map) = v.get("mcpServers").and_then(|x| x.as_object()) else {
return Ok(0);
return Ok(ParsedImport::default());
};

// 确保新结构存在
let servers = config.mcp.servers.get_or_insert_with(HashMap::new);

let mut changed = 0;
let mut errors = Vec::new();
let mut parsed = ParsedImport::default();

for (id, spec) in map.iter() {
// 校验:单项失败不中止,收集错误继续处理
if let Err(e) = validate_server_spec(spec) {
log::warn!("跳过无效 MCP 服务器 '{id}': {e}");
errors.push(format!("{id}: {e}"));
continue;
}

if let Some(existing) = servers.get_mut(id) {
// 已存在:仅启用 Claude 应用
if !existing.apps.claude {
existing.apps.claude = true;
changed += 1;
log::info!("MCP 服务器 '{id}' 已启用 Claude 应用");
match normalize_server_spec(spec) {
Ok(spec) => parsed
.servers
.push(build_imported_server(id.clone(), AppType::Claude, spec)),
Err(e) => {
log::warn!("跳过无效 MCP 服务器 '{id}': {e}");
parsed
.issues
.push(invalid_issue(id.clone(), AppType::Claude, e.to_string()));
}
} else {
// 新建服务器:默认仅启用 Claude
servers.insert(
id.clone(),
McpServer {
id: id.clone(),
name: id.clone(),
server: spec.clone(),
apps: McpApps {
claude: true,
codex: false,
gemini: false,
opencode: false,
},
description: None,
homepage: None,
docs: None,
tags: Vec::new(),
},
);
changed += 1;
log::info!("导入新 MCP 服务器 '{id}'");
}
}

if !errors.is_empty() {
log::warn!("导入完成,但有 {} 项失败: {:?}", errors.len(), errors);
}

Ok(changed)
Ok(parsed)
}

/// 将单个 MCP 服务器同步到 Claude live 配置
Expand Down
Loading