Skip to main content

cc_switch_lib/services/
mcp.rs

1use std::collections::{BTreeSet, HashMap};
2
3use crate::app_config::{AppType, McpApps, McpServer, MultiAppConfig};
4use crate::error::AppError;
5use crate::mcp;
6use crate::store::AppState;
7use serde_json::Value;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum McpLiveDriftKind {
11    InSync,
12    LiveOnly,
13    DbOnly,
14    Changed,
15    LiveInvalid,
16    Unknown,
17}
18
19#[derive(Debug, Clone)]
20pub struct McpLiveDriftEntry {
21    pub app: AppType,
22    pub id: String,
23    pub kind: McpLiveDriftKind,
24    pub db_spec: Option<Value>,
25    pub live_spec: Option<Value>,
26    pub message: Option<String>,
27}
28
29#[derive(Debug, Clone)]
30pub struct McpLiveDriftReport {
31    pub app: AppType,
32    pub entries: Vec<McpLiveDriftEntry>,
33}
34
35/// MCP 相关业务逻辑(v3.7.0 统一结构)
36pub struct McpService;
37
38impl McpService {
39    /// 获取所有 MCP 服务器(统一结构)
40    pub fn get_all_servers(state: &AppState) -> Result<HashMap<String, McpServer>, AppError> {
41        let cfg = state.config.read()?;
42
43        // 如果是新结构,直接返回
44        if let Some(servers) = &cfg.mcp.servers {
45            return Ok(servers.clone());
46        }
47
48        // 理论上不应该走到这里,因为 load 时会自动迁移
49        Err(AppError::localized(
50            "mcp.old_structure",
51            "检测到旧版 MCP 结构,请重启应用完成迁移",
52            "Old MCP structure detected, please restart app to complete migration",
53        ))
54    }
55
56    pub fn get_live_drift(state: &AppState, app: AppType) -> Result<McpLiveDriftReport, AppError> {
57        let live_servers = match Self::read_live_mcp_servers(&app) {
58            Ok(servers) => servers,
59            Err(err) => {
60                return Ok(McpLiveDriftReport {
61                    app: app.clone(),
62                    entries: vec![McpLiveDriftEntry {
63                        app,
64                        id: String::new(),
65                        kind: McpLiveDriftKind::LiveInvalid,
66                        db_spec: None,
67                        live_spec: None,
68                        message: Some(err.to_string()),
69                    }],
70                });
71            }
72        };
73
74        let db_servers = Self::get_all_servers(state)?;
75        let mut ids = BTreeSet::new();
76
77        for id in live_servers.keys() {
78            ids.insert(id.clone());
79        }
80
81        for (id, server) in &db_servers {
82            if server.apps.is_enabled_for(&app) || live_servers.contains_key(id) {
83                ids.insert(id.clone());
84            }
85        }
86
87        let mut entries = Vec::new();
88        for id in ids {
89            let db_server = db_servers
90                .get(&id)
91                .filter(|server| server.apps.is_enabled_for(&app));
92            let db_spec = db_server.map(|server| server.server.clone());
93            let live_spec = live_servers.get(&id).cloned();
94
95            let kind = match (&db_spec, &live_spec) {
96                (Some(db), Some(live)) => {
97                    if normalize_json_value(db) == normalize_json_value(live) {
98                        McpLiveDriftKind::InSync
99                    } else {
100                        McpLiveDriftKind::Changed
101                    }
102                }
103                (Some(_), None) => McpLiveDriftKind::DbOnly,
104                (None, Some(_)) => McpLiveDriftKind::LiveOnly,
105                (None, None) => continue,
106            };
107
108            entries.push(McpLiveDriftEntry {
109                app: app.clone(),
110                id,
111                kind,
112                db_spec,
113                live_spec,
114                message: None,
115            });
116        }
117
118        Ok(McpLiveDriftReport { app, entries })
119    }
120
121    fn read_live_mcp_servers(app: &AppType) -> Result<HashMap<String, Value>, AppError> {
122        match app {
123            AppType::Codex => mcp::read_codex_live_mcp_servers_map(),
124            _ => Ok(HashMap::new()),
125        }
126    }
127
128    pub fn import_live_server(state: &AppState, app: AppType, id: &str) -> Result<(), AppError> {
129        let live_servers = Self::read_live_mcp_servers(&app)?;
130        let live_spec = live_servers.get(id).cloned().ok_or_else(|| {
131            AppError::McpValidation(format!(
132                "{} live MCP server '{}' not found",
133                app.as_str(),
134                id
135            ))
136        })?;
137
138        {
139            let mut cfg = state.config.write()?;
140            let servers = cfg.mcp.servers.get_or_insert_with(HashMap::new);
141
142            if let Some(existing) = servers.get_mut(id) {
143                existing.server = live_spec;
144                existing.apps.set_enabled_for(&app, true);
145            } else {
146                let mut apps = McpApps::default();
147                apps.set_enabled_for(&app, true);
148                servers.insert(
149                    id.to_string(),
150                    McpServer {
151                        id: id.to_string(),
152                        name: id.to_string(),
153                        server: live_spec,
154                        apps,
155                        description: None,
156                        homepage: None,
157                        docs: None,
158                        tags: Vec::new(),
159                    },
160                );
161            }
162        }
163
164        state.save()?;
165        Ok(())
166    }
167
168    pub fn push_db_server_to_live(
169        state: &AppState,
170        app: AppType,
171        id: &str,
172    ) -> Result<(), AppError> {
173        let server = {
174            let cfg = state.config.read()?;
175            cfg.mcp
176                .servers
177                .as_ref()
178                .and_then(|servers| servers.get(id))
179                .cloned()
180        }
181        .ok_or_else(|| AppError::McpValidation(format!("MCP server '{id}' not found")))?;
182
183        if !server.apps.is_enabled_for(&app) {
184            return Err(AppError::McpValidation(format!(
185                "MCP server '{}' is not enabled for {}",
186                id,
187                app.as_str()
188            )));
189        }
190
191        Self::sync_server_to_app(state, &server, &app)
192    }
193
194    /// 添加或更新 MCP 服务器
195    pub fn upsert_server(state: &AppState, server: McpServer) -> Result<(), AppError> {
196        let (server_id, apps_to_remove) = {
197            let mut cfg = state.config.write()?;
198
199            let servers = cfg.mcp.servers.get_or_insert_with(HashMap::new);
200            let server_id = server.id.clone();
201
202            let apps_to_remove = servers
203                .get(&server_id)
204                .map(|existing| {
205                    existing
206                        .apps
207                        .enabled_apps()
208                        .into_iter()
209                        .filter(|app| !server.apps.is_enabled_for(app))
210                        .collect::<Vec<_>>()
211                })
212                .unwrap_or_default();
213
214            // 插入或更新
215            servers.insert(server_id.clone(), server.clone());
216
217            (server_id, apps_to_remove)
218        };
219
220        state.save()?;
221
222        // 如果是更新:对“由启用变为禁用”的应用,清理对应 live 配置
223        for app in apps_to_remove {
224            Self::remove_server_from_app(state, &server_id, &app)?;
225        }
226
227        // 同步到各个启用的应用
228        Self::sync_server_to_apps(state, &server)?;
229
230        Ok(())
231    }
232
233    /// 删除 MCP 服务器
234    pub fn delete_server(state: &AppState, id: &str) -> Result<bool, AppError> {
235        let server = {
236            let mut cfg = state.config.write()?;
237
238            if let Some(servers) = &mut cfg.mcp.servers {
239                servers.remove(id)
240            } else {
241                None
242            }
243        };
244
245        if let Some(server) = server {
246            state.save()?;
247
248            // 从所有应用的 live 配置中移除
249            Self::remove_server_from_all_apps(state, id, &server)?;
250            Ok(true)
251        } else {
252            Ok(false)
253        }
254    }
255
256    /// 切换指定应用的启用状态
257    pub fn toggle_app(
258        state: &AppState,
259        server_id: &str,
260        app: AppType,
261        enabled: bool,
262    ) -> Result<(), AppError> {
263        let server = {
264            let mut cfg = state.config.write()?;
265
266            if let Some(servers) = &mut cfg.mcp.servers {
267                if let Some(server) = servers.get_mut(server_id) {
268                    server.apps.set_enabled_for(&app, enabled);
269                    Some(server.clone())
270                } else {
271                    None
272                }
273            } else {
274                None
275            }
276        };
277
278        if let Some(server) = server {
279            state.save()?;
280
281            // 同步到对应应用
282            if enabled {
283                Self::sync_server_to_app(state, &server, &app)?;
284            } else {
285                Self::remove_server_from_app(state, server_id, &app)?;
286            }
287        }
288
289        Ok(())
290    }
291
292    /// 将 MCP 服务器同步到所有启用的应用
293    fn sync_server_to_apps(state: &AppState, server: &McpServer) -> Result<(), AppError> {
294        let cfg = state.config.read()?;
295
296        for app in server.apps.enabled_apps() {
297            Self::sync_server_to_app_internal(&cfg, server, &app)?;
298        }
299
300        Ok(())
301    }
302
303    /// 将 MCP 服务器同步到指定应用
304    fn sync_server_to_app(
305        state: &AppState,
306        server: &McpServer,
307        app: &AppType,
308    ) -> Result<(), AppError> {
309        let cfg = state.config.read()?;
310        Self::sync_server_to_app_internal(&cfg, server, app)
311    }
312
313    fn sync_server_to_app_internal(
314        cfg: &MultiAppConfig,
315        server: &McpServer,
316        app: &AppType,
317    ) -> Result<(), AppError> {
318        match app {
319            AppType::Claude => {
320                mcp::sync_single_server_to_claude(cfg, &server.id, &server.server)?;
321            }
322            AppType::Codex => {
323                mcp::sync_single_server_to_codex(cfg, &server.id, &server.server)?;
324            }
325            AppType::Gemini => {
326                mcp::sync_single_server_to_gemini(cfg, &server.id, &server.server)?;
327            }
328            AppType::OpenCode => {
329                mcp::sync_single_server_to_opencode(cfg, &server.id, &server.server)?;
330            }
331            AppType::OpenClaw => {
332                mcp::sync_single_server_to_openclaw(cfg, &server.id, &server.server)?;
333            }
334            AppType::Hermes => {
335                mcp::sync_single_server_to_hermes(cfg, &server.id, &server.server)?;
336            }
337        }
338        Ok(())
339    }
340
341    /// 从所有曾启用过该服务器的应用中移除
342    fn remove_server_from_all_apps(
343        state: &AppState,
344        id: &str,
345        server: &McpServer,
346    ) -> Result<(), AppError> {
347        // 从所有曾启用的应用中移除
348        for app in server.apps.enabled_apps() {
349            Self::remove_server_from_app(state, id, &app)?;
350        }
351        Ok(())
352    }
353
354    fn remove_server_from_app(_state: &AppState, id: &str, app: &AppType) -> Result<(), AppError> {
355        match app {
356            AppType::Claude => mcp::remove_server_from_claude(id)?,
357            AppType::Codex => mcp::remove_server_from_codex(id)?,
358            AppType::Gemini => mcp::remove_server_from_gemini(id)?,
359            AppType::OpenCode => mcp::remove_server_from_opencode(id)?,
360            AppType::OpenClaw => mcp::remove_server_from_openclaw(id)?,
361            AppType::Hermes => mcp::remove_server_from_hermes(id)?,
362        }
363        Ok(())
364    }
365
366    /// 手动同步所有启用的 MCP 服务器到对应的应用
367    pub fn sync_all_enabled(state: &AppState) -> Result<(), AppError> {
368        Self::sync_all_enabled_except(state, &[])
369    }
370
371    /// 同步所有启用的 MCP 服务器到对应应用,但跳过指定应用。
372    pub fn sync_all_enabled_except(
373        state: &AppState,
374        excluded_apps: &[AppType],
375    ) -> Result<(), AppError> {
376        let servers = Self::get_all_servers(state)?;
377
378        for app in AppType::all() {
379            if excluded_apps.contains(&app) {
380                continue;
381            }
382
383            for server in servers.values() {
384                if server.apps.is_enabled_for(&app) {
385                    Self::sync_server_to_app(state, server, &app)?;
386                } else {
387                    Self::remove_server_from_app(state, &server.id, &app)?;
388                }
389            }
390        }
391
392        Ok(())
393    }
394
395    // ========================================================================
396    // 兼容层:支持旧的 v3.6.x 命令(已废弃,将在 v4.0 移除)
397    // ========================================================================
398
399    /// [已废弃] 获取指定应用的 MCP 服务器(兼容旧 API)
400    #[deprecated(since = "3.7.0", note = "Use get_all_servers instead")]
401    pub fn get_servers(
402        state: &AppState,
403        app: AppType,
404    ) -> Result<HashMap<String, serde_json::Value>, AppError> {
405        let all_servers = Self::get_all_servers(state)?;
406        let mut result = HashMap::new();
407
408        for (id, server) in all_servers {
409            if server.apps.is_enabled_for(&app) {
410                result.insert(id, server.server);
411            }
412        }
413
414        Ok(result)
415    }
416
417    /// [已废弃] 设置 MCP 服务器在指定应用的启用状态(兼容旧 API)
418    #[deprecated(since = "3.7.0", note = "Use toggle_app instead")]
419    pub fn set_enabled(
420        state: &AppState,
421        app: AppType,
422        id: &str,
423        enabled: bool,
424    ) -> Result<bool, AppError> {
425        Self::toggle_app(state, id, app, enabled)?;
426        Ok(true)
427    }
428
429    /// [已废弃] 同步启用的 MCP 到指定应用(兼容旧 API)
430    #[deprecated(since = "3.7.0", note = "Use sync_all_enabled instead")]
431    pub fn sync_enabled(state: &AppState, app: AppType) -> Result<(), AppError> {
432        let servers = Self::get_all_servers(state)?;
433
434        for server in servers.values() {
435            if server.apps.is_enabled_for(&app) {
436                Self::sync_server_to_app(state, server, &app)?;
437            }
438        }
439
440        Ok(())
441    }
442
443    /// 从 Claude 导入 MCP(v3.7.0 已更新为统一结构)
444    pub fn import_from_claude(state: &AppState) -> Result<usize, AppError> {
445        let mut cfg = state.config.write()?;
446        let count = mcp::import_from_claude(&mut cfg)?;
447        drop(cfg);
448        state.save()?;
449        Ok(count)
450    }
451
452    /// 从 Codex 导入 MCP(v3.7.0 已更新为统一结构)
453    pub fn import_from_codex(state: &AppState) -> Result<usize, AppError> {
454        let mut cfg = state.config.write()?;
455        let count = mcp::import_from_codex(&mut cfg)?;
456        drop(cfg);
457        state.save()?;
458        Ok(count)
459    }
460
461    /// 从 Gemini 导入 MCP(v3.7.0 已更新为统一结构)
462    pub fn import_from_gemini(state: &AppState) -> Result<usize, AppError> {
463        let mut cfg = state.config.write()?;
464        let count = mcp::import_from_gemini(&mut cfg)?;
465        drop(cfg);
466        state.save()?;
467        Ok(count)
468    }
469
470    /// 从 OpenCode 导入 MCP
471    pub fn import_from_opencode(state: &AppState) -> Result<usize, AppError> {
472        let mut cfg = state.config.write()?;
473        let count = mcp::import_from_opencode(&mut cfg)?;
474        drop(cfg);
475        state.save()?;
476        Ok(count)
477    }
478
479    /// 从 OpenClaw 导入 MCP
480    pub fn import_from_openclaw(state: &AppState) -> Result<usize, AppError> {
481        let mut cfg = state.config.write()?;
482        let count = mcp::import_from_openclaw(&mut cfg)?;
483        drop(cfg);
484        state.save()?;
485        Ok(count)
486    }
487
488    /// 从 Hermes 导入 MCP
489    pub fn import_from_hermes(state: &AppState) -> Result<usize, AppError> {
490        let mut cfg = state.config.write()?;
491        let count = mcp::import_from_hermes(&mut cfg)?;
492        drop(cfg);
493        state.save()?;
494        Ok(count)
495    }
496}
497
498fn normalize_json_value(value: &Value) -> Value {
499    match value {
500        Value::Array(items) => Value::Array(items.iter().map(normalize_json_value).collect()),
501        Value::Object(map) => {
502            let mut normalized = serde_json::Map::new();
503            let mut keys = map.keys().collect::<Vec<_>>();
504            keys.sort();
505            for key in keys {
506                if let Some(value) = map.get(key) {
507                    normalized.insert(key.clone(), normalize_json_value(value));
508                }
509            }
510            Value::Object(normalized)
511        }
512        _ => value.clone(),
513    }
514}