use std::collections::HashMap;
use crate::app_config::{AppType, McpServer, MultiAppConfig};
use crate::error::AppError;
use crate::mcp;
use crate::store::AppState;
pub struct McpService;
impl McpService {
pub fn get_all_servers(state: &AppState) -> Result<HashMap<String, McpServer>, AppError> {
let cfg = state.config.read()?;
if let Some(servers) = &cfg.mcp.servers {
return Ok(servers.clone());
}
Err(AppError::localized(
"mcp.old_structure",
"检测到旧版 MCP 结构,请重启应用完成迁移",
"Old MCP structure detected, please restart app to complete migration",
))
}
pub fn upsert_server(state: &AppState, server: McpServer) -> Result<(), AppError> {
let (server_id, apps_to_remove) = {
let mut cfg = state.config.write()?;
let servers = cfg.mcp.servers.get_or_insert_with(HashMap::new);
let server_id = server.id.clone();
let apps_to_remove = servers
.get(&server_id)
.map(|existing| {
existing
.apps
.enabled_apps()
.into_iter()
.filter(|app| !server.apps.is_enabled_for(app))
.collect::<Vec<_>>()
})
.unwrap_or_default();
servers.insert(server_id.clone(), server.clone());
(server_id, apps_to_remove)
};
state.save()?;
for app in apps_to_remove {
Self::remove_server_from_app(state, &server_id, &app)?;
}
Self::sync_server_to_apps(state, &server)?;
Ok(())
}
pub fn delete_server(state: &AppState, id: &str) -> Result<bool, AppError> {
let server = {
let mut cfg = state.config.write()?;
if let Some(servers) = &mut cfg.mcp.servers {
servers.remove(id)
} else {
None
}
};
if let Some(server) = server {
state.save()?;
Self::remove_server_from_all_apps(state, id, &server)?;
Ok(true)
} else {
Ok(false)
}
}
pub fn toggle_app(
state: &AppState,
server_id: &str,
app: AppType,
enabled: bool,
) -> Result<(), AppError> {
let server = {
let mut cfg = state.config.write()?;
if let Some(servers) = &mut cfg.mcp.servers {
if let Some(server) = servers.get_mut(server_id) {
server.apps.set_enabled_for(&app, enabled);
Some(server.clone())
} else {
None
}
} else {
None
}
};
if let Some(server) = server {
state.save()?;
if enabled {
Self::sync_server_to_app(state, &server, &app)?;
} else {
Self::remove_server_from_app(state, server_id, &app)?;
}
}
Ok(())
}
fn sync_server_to_apps(state: &AppState, server: &McpServer) -> Result<(), AppError> {
let cfg = state.config.read()?;
for app in server.apps.enabled_apps() {
Self::sync_server_to_app_internal(&cfg, server, &app)?;
}
Ok(())
}
fn sync_server_to_app(
state: &AppState,
server: &McpServer,
app: &AppType,
) -> Result<(), AppError> {
let cfg = state.config.read()?;
Self::sync_server_to_app_internal(&cfg, server, app)
}
fn sync_server_to_app_internal(
cfg: &MultiAppConfig,
server: &McpServer,
app: &AppType,
) -> Result<(), AppError> {
match app {
AppType::Claude => {
mcp::sync_single_server_to_claude(cfg, &server.id, &server.server)?;
}
AppType::Codex => {
mcp::sync_single_server_to_codex(cfg, &server.id, &server.server)?;
}
AppType::Gemini => {
mcp::sync_single_server_to_gemini(cfg, &server.id, &server.server)?;
}
AppType::OpenCode => {
mcp::sync_single_server_to_opencode(cfg, &server.id, &server.server)?;
}
AppType::OpenClaw => {
mcp::sync_single_server_to_openclaw(cfg, &server.id, &server.server)?;
}
AppType::Hermes => {
mcp::sync_single_server_to_hermes(cfg, &server.id, &server.server)?;
}
}
Ok(())
}
fn remove_server_from_all_apps(
state: &AppState,
id: &str,
server: &McpServer,
) -> Result<(), AppError> {
for app in server.apps.enabled_apps() {
Self::remove_server_from_app(state, id, &app)?;
}
Ok(())
}
fn remove_server_from_app(_state: &AppState, id: &str, app: &AppType) -> Result<(), AppError> {
match app {
AppType::Claude => mcp::remove_server_from_claude(id)?,
AppType::Codex => mcp::remove_server_from_codex(id)?,
AppType::Gemini => mcp::remove_server_from_gemini(id)?,
AppType::OpenCode => mcp::remove_server_from_opencode(id)?,
AppType::OpenClaw => mcp::remove_server_from_openclaw(id)?,
AppType::Hermes => mcp::remove_server_from_hermes(id)?,
}
Ok(())
}
pub fn sync_all_enabled(state: &AppState) -> Result<(), AppError> {
let servers = Self::get_all_servers(state)?;
for app in AppType::all() {
for server in servers.values() {
if server.apps.is_enabled_for(&app) {
Self::sync_server_to_app(state, server, &app)?;
} else {
Self::remove_server_from_app(state, &server.id, &app)?;
}
}
}
Ok(())
}
#[deprecated(since = "3.7.0", note = "Use get_all_servers instead")]
pub fn get_servers(
state: &AppState,
app: AppType,
) -> Result<HashMap<String, serde_json::Value>, AppError> {
let all_servers = Self::get_all_servers(state)?;
let mut result = HashMap::new();
for (id, server) in all_servers {
if server.apps.is_enabled_for(&app) {
result.insert(id, server.server);
}
}
Ok(result)
}
#[deprecated(since = "3.7.0", note = "Use toggle_app instead")]
pub fn set_enabled(
state: &AppState,
app: AppType,
id: &str,
enabled: bool,
) -> Result<bool, AppError> {
Self::toggle_app(state, id, app, enabled)?;
Ok(true)
}
#[deprecated(since = "3.7.0", note = "Use sync_all_enabled instead")]
pub fn sync_enabled(state: &AppState, app: AppType) -> Result<(), AppError> {
let servers = Self::get_all_servers(state)?;
for server in servers.values() {
if server.apps.is_enabled_for(&app) {
Self::sync_server_to_app(state, server, &app)?;
}
}
Ok(())
}
pub fn import_from_claude(state: &AppState) -> Result<usize, AppError> {
let mut cfg = state.config.write()?;
let count = mcp::import_from_claude(&mut cfg)?;
drop(cfg);
state.save()?;
Ok(count)
}
pub fn import_from_codex(state: &AppState) -> Result<usize, AppError> {
let mut cfg = state.config.write()?;
let count = mcp::import_from_codex(&mut cfg)?;
drop(cfg);
state.save()?;
Ok(count)
}
pub fn import_from_gemini(state: &AppState) -> Result<usize, AppError> {
let mut cfg = state.config.write()?;
let count = mcp::import_from_gemini(&mut cfg)?;
drop(cfg);
state.save()?;
Ok(count)
}
pub fn import_from_opencode(state: &AppState) -> Result<usize, AppError> {
let mut cfg = state.config.write()?;
let count = mcp::import_from_opencode(&mut cfg)?;
drop(cfg);
state.save()?;
Ok(count)
}
pub fn import_from_openclaw(state: &AppState) -> Result<usize, AppError> {
let mut cfg = state.config.write()?;
let count = mcp::import_from_openclaw(&mut cfg)?;
drop(cfg);
state.save()?;
Ok(count)
}
pub fn import_from_hermes(state: &AppState) -> Result<usize, AppError> {
let mut cfg = state.config.write()?;
let count = mcp::import_from_hermes(&mut cfg)?;
drop(cfg);
state.save()?;
Ok(count)
}
}