use crate::hook::mcp::{McpBridge, config::McpServerConfig};
use std::sync::{Arc, RwLock as StdRwLock};
use tokio::sync::RwLock;
pub struct McpHandler {
bridge: RwLock<Arc<McpBridge>>,
server_cache: StdRwLock<Vec<(String, Vec<String>)>>,
}
impl McpHandler {
async fn build_bridge(configs: &[McpServerConfig]) -> McpBridge {
let bridge = McpBridge::new();
for server_config in configs {
let mut cmd = tokio::process::Command::new(&server_config.command);
cmd.args(&server_config.args);
for (k, v) in &server_config.env {
cmd.env(k, v);
}
tracing::info!(
server = %server_config.name,
command = %server_config.command,
"connecting MCP server"
);
match bridge
.connect_stdio_named(server_config.name.clone(), cmd)
.await
{
Ok(tools) => {
tracing::info!(
"connected MCP server '{}' — {} tool(s)",
server_config.name,
tools.len()
);
}
Err(e) => {
tracing::warn!(
"failed to connect MCP server '{}' (command: {}): {e}",
server_config.name,
server_config.command
);
}
}
}
bridge
}
pub async fn load(configs: &[McpServerConfig]) -> Self {
let bridge = Self::build_bridge(configs).await;
let servers = bridge.list_servers().await;
Self {
bridge: RwLock::new(Arc::new(bridge)),
server_cache: StdRwLock::new(servers),
}
}
pub async fn list(&self) -> Vec<(String, Vec<String>)> {
self.bridge.read().await.list_servers().await
}
pub fn cached_list(&self) -> Vec<(String, Vec<String>)> {
self.server_cache.read().unwrap().clone()
}
pub async fn bridge(&self) -> Arc<McpBridge> {
Arc::clone(&*self.bridge.read().await)
}
pub fn try_bridge(&self) -> Option<Arc<McpBridge>> {
self.bridge.try_read().ok().map(|g| Arc::clone(&*g))
}
}