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 mcp::sync_single_server_to_openclaw(cfg, &server.id, &server.server)?;
168 }
169 AppType::Hermes => {
170 mcp::sync_single_server_to_hermes(cfg, &server.id, &server.server)?;
171 }
172 }
173 Ok(())
174 }
175
176 fn remove_server_from_all_apps(
178 state: &AppState,
179 id: &str,
180 server: &McpServer,
181 ) -> Result<(), AppError> {
182 for app in server.apps.enabled_apps() {
184 Self::remove_server_from_app(state, id, &app)?;
185 }
186 Ok(())
187 }
188
189 fn remove_server_from_app(_state: &AppState, id: &str, app: &AppType) -> Result<(), AppError> {
190 match app {
191 AppType::Claude => mcp::remove_server_from_claude(id)?,
192 AppType::Codex => mcp::remove_server_from_codex(id)?,
193 AppType::Gemini => mcp::remove_server_from_gemini(id)?,
194 AppType::OpenCode => mcp::remove_server_from_opencode(id)?,
195 AppType::OpenClaw => mcp::remove_server_from_openclaw(id)?,
196 AppType::Hermes => mcp::remove_server_from_hermes(id)?,
197 }
198 Ok(())
199 }
200
201 pub fn sync_all_enabled(state: &AppState) -> Result<(), AppError> {
203 let servers = Self::get_all_servers(state)?;
204
205 for app in AppType::all() {
206 for server in servers.values() {
207 if server.apps.is_enabled_for(&app) {
208 Self::sync_server_to_app(state, server, &app)?;
209 } else {
210 Self::remove_server_from_app(state, &server.id, &app)?;
211 }
212 }
213 }
214
215 Ok(())
216 }
217
218 #[deprecated(since = "3.7.0", note = "Use get_all_servers instead")]
224 pub fn get_servers(
225 state: &AppState,
226 app: AppType,
227 ) -> Result<HashMap<String, serde_json::Value>, AppError> {
228 let all_servers = Self::get_all_servers(state)?;
229 let mut result = HashMap::new();
230
231 for (id, server) in all_servers {
232 if server.apps.is_enabled_for(&app) {
233 result.insert(id, server.server);
234 }
235 }
236
237 Ok(result)
238 }
239
240 #[deprecated(since = "3.7.0", note = "Use toggle_app instead")]
242 pub fn set_enabled(
243 state: &AppState,
244 app: AppType,
245 id: &str,
246 enabled: bool,
247 ) -> Result<bool, AppError> {
248 Self::toggle_app(state, id, app, enabled)?;
249 Ok(true)
250 }
251
252 #[deprecated(since = "3.7.0", note = "Use sync_all_enabled instead")]
254 pub fn sync_enabled(state: &AppState, app: AppType) -> Result<(), AppError> {
255 let servers = Self::get_all_servers(state)?;
256
257 for server in servers.values() {
258 if server.apps.is_enabled_for(&app) {
259 Self::sync_server_to_app(state, server, &app)?;
260 }
261 }
262
263 Ok(())
264 }
265
266 pub fn import_from_claude(state: &AppState) -> Result<usize, AppError> {
268 let mut cfg = state.config.write()?;
269 let count = mcp::import_from_claude(&mut cfg)?;
270 drop(cfg);
271 state.save()?;
272 Ok(count)
273 }
274
275 pub fn import_from_codex(state: &AppState) -> Result<usize, AppError> {
277 let mut cfg = state.config.write()?;
278 let count = mcp::import_from_codex(&mut cfg)?;
279 drop(cfg);
280 state.save()?;
281 Ok(count)
282 }
283
284 pub fn import_from_gemini(state: &AppState) -> Result<usize, AppError> {
286 let mut cfg = state.config.write()?;
287 let count = mcp::import_from_gemini(&mut cfg)?;
288 drop(cfg);
289 state.save()?;
290 Ok(count)
291 }
292
293 pub fn import_from_opencode(state: &AppState) -> Result<usize, AppError> {
295 let mut cfg = state.config.write()?;
296 let count = mcp::import_from_opencode(&mut cfg)?;
297 drop(cfg);
298 state.save()?;
299 Ok(count)
300 }
301
302 pub fn import_from_openclaw(state: &AppState) -> Result<usize, AppError> {
304 let mut cfg = state.config.write()?;
305 let count = mcp::import_from_openclaw(&mut cfg)?;
306 drop(cfg);
307 state.save()?;
308 Ok(count)
309 }
310
311 pub fn import_from_hermes(state: &AppState) -> Result<usize, AppError> {
313 let mut cfg = state.config.write()?;
314 let count = mcp::import_from_hermes(&mut cfg)?;
315 drop(cfg);
316 state.save()?;
317 Ok(count)
318 }
319}