use std::sync::Arc;
use agent_base::{AgentResult, ToolRegistry};
use tracing::{info, warn};
use super::client::{McpClient, McpToolAdapter};
use super::types::{McpServerConfig, McpToolInfo};
#[derive(Clone, Debug)]
pub enum ConnectionState {
Disconnected,
Connected,
Failed(String),
}
struct ServerEntry {
config: McpServerConfig,
client: Option<Arc<McpClient>>,
tools: Vec<McpToolInfo>,
state: ConnectionState,
}
pub struct McpHub {
servers: Vec<ServerEntry>,
}
impl McpHub {
pub fn new() -> Self {
Self {
servers: Vec::new(),
}
}
pub fn add_server(&mut self, config: McpServerConfig) {
self.servers.push(ServerEntry {
config,
client: None,
tools: Vec::new(),
state: ConnectionState::Disconnected,
});
}
pub async fn connect_all(&mut self) -> AgentResult<()> {
for entry in &mut self.servers {
let client = Arc::new(McpClient::new(entry.config.transport.clone()).await?);
entry.client = Some(client);
entry.state = ConnectionState::Connected;
info!("connected to MCP server: {}", entry.config.name);
}
Ok(())
}
pub async fn discover_all(&mut self) -> AgentResult<Vec<(String, Vec<McpToolInfo>)>> {
let mut results = Vec::new();
for entry in &mut self.servers {
let Some(client) = &entry.client else {
warn!("server {} not connected", entry.config.name);
continue;
};
match client.list_tools().await {
Ok(tools) => {
info!(
"discovered {} tools from {}",
tools.len(),
entry.config.name
);
let cloned = tools.clone();
entry.tools = tools;
results.push((entry.config.name.clone(), cloned));
}
Err(e) => {
warn!(
"failed to discover tools from {}: {e}",
entry.config.name
);
entry.state = ConnectionState::Failed(format!("{e}"));
}
}
}
Ok(results)
}
pub fn register_all(&self, registry: &mut ToolRegistry) {
for entry in &self.servers {
let Some(client) = &entry.client else {
continue;
};
for tool_info in &entry.tools {
let adapter = McpToolAdapter::new(tool_info.clone(), client.clone());
registry.register(adapter);
}
}
}
pub async fn disconnect_all(&mut self) {
for entry in &mut self.servers {
entry.client = None;
entry.state = ConnectionState::Disconnected;
info!("disconnected from MCP server: {}", entry.config.name);
}
}
pub async fn health_check(&self) -> AgentResult<()> {
for entry in &self.servers {
let Some(client) = &entry.client else {
warn!("server {} not connected", entry.config.name);
continue;
};
match client.list_tools().await {
Ok(_) => {
info!("health check passed: {}", entry.config.name);
}
Err(e) => {
warn!("health check failed for {}: {e}", entry.config.name);
}
}
}
Ok(())
}
}