1use std::collections::HashMap;
2
3use crate::app_config::{AppType, McpServer, MultiAppConfig};
4use crate::error::AppError;
5use crate::mcp;
6use crate::store::AppState;
7
8pub struct McpService;
10
11impl McpService {
12 pub fn get_all_servers(state: &AppState) -> Result<HashMap<String, McpServer>, AppError> {
14 let cfg = state.config.read()?;
15
16 if let Some(servers) = &cfg.mcp.servers {
18 return Ok(servers.clone());
19 }
20
21 Err(AppError::localized(
23 "mcp.old_structure",
24 "检测到旧版 MCP 结构,请重启应用完成迁移",
25 "Old MCP structure detected, please restart app to complete migration",
26 ))
27 }
28
29 pub fn upsert_server(state: &AppState, server: McpServer) -> Result<(), AppError> {
31 let (server_id, apps_to_remove) = {
32 let mut cfg = state.config.write()?;
33
34 let servers = cfg.mcp.servers.get_or_insert_with(HashMap::new);
35 let server_id = server.id.clone();
36
37 let apps_to_remove = servers
38 .get(&server_id)
39 .map(|existing| {
40 existing
41 .apps
42 .enabled_apps()
43 .into_iter()
44 .filter(|app| !server.apps.is_enabled_for(app))
45 .collect::<Vec<_>>()
46 })
47 .unwrap_or_default();
48
49 servers.insert(server_id.clone(), server.clone());
51
52 (server_id, apps_to_remove)
53 };
54
55 state.save()?;
56
57 for app in apps_to_remove {
59 Self::remove_server_from_app(state, &server_id, &app)?;
60 }
61
62 Self::sync_server_to_apps(state, &server)?;
64
65 Ok(())
66 }
67
68 pub fn delete_server(state: &AppState, id: &str) -> Result<bool, AppError> {
70 let server = {
71 let mut cfg = state.config.write()?;
72
73 if let Some(servers) = &mut cfg.mcp.servers {
74 servers.remove(id)
75 } else {
76 None
77 }
78 };
79
80 if let Some(server) = server {
81 state.save()?;
82
83 Self::remove_server_from_all_apps(state, id, &server)?;
85 Ok(true)
86 } else {
87 Ok(false)
88 }
89 }
90
91 pub fn toggle_app(
93 state: &AppState,
94 server_id: &str,
95 app: AppType,
96 enabled: bool,
97 ) -> Result<(), AppError> {
98 let server = {
99 let mut cfg = state.config.write()?;
100
101 if let Some(servers) = &mut cfg.mcp.servers {
102 if let Some(server) = servers.get_mut(server_id) {
103 server.apps.set_enabled_for(&app, enabled);
104 Some(server.clone())
105 } else {
106 None
107 }
108 } else {
109 None
110 }
111 };
112
113 if let Some(server) = server {
114 state.save()?;
115
116 if enabled {
118 Self::sync_server_to_app(state, &server, &app)?;
119 } else {
120 Self::remove_server_from_app(state, server_id, &app)?;
121 }
122 }
123
124 Ok(())
125 }
126
127 fn sync_server_to_apps(state: &AppState, server: &McpServer) -> Result<(), AppError> {
129 let cfg = state.config.read()?;
130
131 for app in server.apps.enabled_apps() {
132 Self::sync_server_to_app_internal(&cfg, server, &app)?;
133 }
134
135 Ok(())
136 }
137
138 fn sync_server_to_app(
140 state: &AppState,
141 server: &McpServer,
142 app: &AppType,
143 ) -> Result<(), AppError> {
144 let cfg = state.config.read()?;
145 Self::sync_server_to_app_internal(&cfg, server, app)
146 }
147
148 fn sync_server_to_app_internal(
149 cfg: &MultiAppConfig,
150 server: &McpServer,
151 app: &AppType,
152 ) -> Result<(), AppError> {
153 match app {
154 AppType::Claude => {
155 mcp::sync_single_server_to_claude(cfg, &server.id, &server.server)?;
156 }
157 AppType::Codex => {
158 mcp::sync_single_server_to_codex(cfg, &server.id, &server.server)?;
159 }
160 AppType::Gemini => {
161 mcp::sync_single_server_to_gemini(cfg, &server.id, &server.server)?;
162 }
163 AppType::OpenCode => {
164 mcp::sync_single_server_to_opencode(cfg, &server.id, &server.server)?;
165 }
166 AppType::OpenClaw => {}
167 AppType::Hermes => {
168 mcp::sync_single_server_to_hermes(cfg, &server.id, &server.server)?;
169 }
170 }
171 Ok(())
172 }
173
174 fn remove_server_from_all_apps(
176 state: &AppState,
177 id: &str,
178 server: &McpServer,
179 ) -> Result<(), AppError> {
180 for app in server.apps.enabled_apps() {
182 Self::remove_server_from_app(state, id, &app)?;
183 }
184 Ok(())
185 }
186
187 fn remove_server_from_app(_state: &AppState, id: &str, app: &AppType) -> Result<(), AppError> {
188 match app {
189 AppType::Claude => mcp::remove_server_from_claude(id)?,
190 AppType::Codex => mcp::remove_server_from_codex(id)?,
191 AppType::Gemini => mcp::remove_server_from_gemini(id)?,
192 AppType::OpenCode => mcp::remove_server_from_opencode(id)?,
193 AppType::OpenClaw => {}
194 AppType::Hermes => mcp::remove_server_from_hermes(id)?,
195 }
196 Ok(())
197 }
198
199 pub fn sync_all_enabled(state: &AppState) -> Result<(), AppError> {
201 let servers = Self::get_all_servers(state)?;
202
203 for app in AppType::all() {
204 if matches!(app, AppType::OpenClaw) {
205 continue;
206 }
207
208 for server in servers.values() {
209 if server.apps.is_enabled_for(&app) {
210 Self::sync_server_to_app(state, server, &app)?;
211 } else {
212 Self::remove_server_from_app(state, &server.id, &app)?;
213 }
214 }
215 }
216
217 Ok(())
218 }
219
220 #[deprecated(since = "3.7.0", note = "Use get_all_servers instead")]
226 pub fn get_servers(
227 state: &AppState,
228 app: AppType,
229 ) -> Result<HashMap<String, serde_json::Value>, AppError> {
230 let all_servers = Self::get_all_servers(state)?;
231 let mut result = HashMap::new();
232
233 for (id, server) in all_servers {
234 if server.apps.is_enabled_for(&app) {
235 result.insert(id, server.server);
236 }
237 }
238
239 Ok(result)
240 }
241
242 #[deprecated(since = "3.7.0", note = "Use toggle_app instead")]
244 pub fn set_enabled(
245 state: &AppState,
246 app: AppType,
247 id: &str,
248 enabled: bool,
249 ) -> Result<bool, AppError> {
250 Self::toggle_app(state, id, app, enabled)?;
251 Ok(true)
252 }
253
254 #[deprecated(since = "3.7.0", note = "Use sync_all_enabled instead")]
256 pub fn sync_enabled(state: &AppState, app: AppType) -> Result<(), AppError> {
257 let servers = Self::get_all_servers(state)?;
258
259 for server in servers.values() {
260 if server.apps.is_enabled_for(&app) {
261 Self::sync_server_to_app(state, server, &app)?;
262 }
263 }
264
265 Ok(())
266 }
267
268 pub fn import_from_claude(state: &AppState) -> Result<usize, AppError> {
270 let mut cfg = state.config.write()?;
271 let count = mcp::import_from_claude(&mut cfg)?;
272 drop(cfg);
273 state.save()?;
274 Ok(count)
275 }
276
277 pub fn import_from_codex(state: &AppState) -> Result<usize, AppError> {
279 let mut cfg = state.config.write()?;
280 let count = mcp::import_from_codex(&mut cfg)?;
281 drop(cfg);
282 state.save()?;
283 Ok(count)
284 }
285
286 pub fn import_from_gemini(state: &AppState) -> Result<usize, AppError> {
288 let mut cfg = state.config.write()?;
289 let count = mcp::import_from_gemini(&mut cfg)?;
290 drop(cfg);
291 state.save()?;
292 Ok(count)
293 }
294
295 pub fn import_from_opencode(state: &AppState) -> Result<usize, AppError> {
297 let mut cfg = state.config.write()?;
298 let count = mcp::import_from_opencode(&mut cfg)?;
299 drop(cfg);
300 state.save()?;
301 Ok(count)
302 }
303
304 pub fn import_from_hermes(state: &AppState) -> Result<usize, AppError> {
306 let mut cfg = state.config.write()?;
307 let count = mcp::import_from_hermes(&mut cfg)?;
308 drop(cfg);
309 state.save()?;
310 Ok(count)
311 }
312}