use crate::config::{Config, McpServerConfig};
use crate::mcp::McpConnectionType;
use anyhow::Result;
use std::collections::HashMap;
use std::sync::{Arc, OnceLock, RwLock};
static TOOL_MAP: OnceLock<Arc<RwLock<ToolMapState>>> = OnceLock::new();
#[derive(Debug, Clone, Default)]
struct ToolMapState {
tool_to_server: HashMap<String, McpServerConfig>,
initialized: bool,
config_hash: u64,
}
pub async fn initialize_tool_map(config: &Config) -> Result<()> {
let config_hash = calculate_config_hash(config);
let tool_map_state = TOOL_MAP.get_or_init(|| Arc::new(RwLock::new(ToolMapState::default())));
{
let state = tool_map_state.read().unwrap();
if state.initialized && state.config_hash == config_hash {
crate::log_debug!("Tool map already initialized with current config");
return Ok(());
}
}
crate::log_debug!("Building tool-to-server map...");
let tool_to_server = build_tool_server_map_impl(config).await?;
{
let mut state = tool_map_state.write().unwrap();
state.tool_to_server = tool_to_server;
state.initialized = true;
state.config_hash = config_hash;
crate::log_debug!(
"Tool map initialized with {} tools",
state.tool_to_server.len()
);
}
Ok(())
}
pub fn get_server_for_tool(tool_name: &str) -> Option<McpServerConfig> {
let tool_map_state = TOOL_MAP.get()?;
let state = tool_map_state.read().unwrap();
if !state.initialized {
crate::log_debug!("Tool map not initialized, falling back to original logic");
return None;
}
state.tool_to_server.get(tool_name).cloned()
}
pub fn get_tool_server_name(tool_name: &str) -> Option<String> {
get_server_for_tool(tool_name).map(|server| server.name().to_string())
}
pub fn is_initialized() -> bool {
TOOL_MAP
.get()
.map(|state| state.read().unwrap().initialized)
.unwrap_or(false)
}
pub fn get_all_tool_names() -> Vec<String> {
let tool_map_state = match TOOL_MAP.get() {
Some(state) => state,
None => return Vec::new(),
};
let state = tool_map_state.read().unwrap();
if !state.initialized {
return Vec::new();
}
state.tool_to_server.keys().cloned().collect()
}
pub fn get_all_server_names() -> std::collections::HashSet<String> {
let tool_map_state = match TOOL_MAP.get() {
Some(state) => state,
None => return std::collections::HashSet::new(),
};
let state = tool_map_state.read().unwrap();
if !state.initialized {
return std::collections::HashSet::new();
}
state
.tool_to_server
.values()
.map(|server| server.name().to_string())
.collect()
}
pub fn register_dynamic_agent_tool(agent_name: &str) {
let tool_map_state = match TOOL_MAP.get() {
Some(state) => state,
None => {
crate::log_debug!("Tool map not initialized, cannot register dynamic agent");
return;
}
};
let tool_name = format!("agent_{}", agent_name);
let agent_server = McpServerConfig::Builtin {
name: "agent".to_string(),
timeout_seconds: 300,
tools: vec![tool_name.clone()],
auto_bind: None,
};
let mut state = tool_map_state.write().unwrap();
state.tool_to_server.insert(tool_name.clone(), agent_server);
crate::log_debug!("Registered dynamic agent tool: {}", tool_name);
}
pub fn unregister_dynamic_agent_tool(agent_name: &str) {
let tool_map_state = match TOOL_MAP.get() {
Some(state) => state,
None => {
crate::log_debug!("Tool map not initialized, cannot unregister dynamic agent");
return;
}
};
let tool_name = format!("agent_{}", agent_name);
let mut state = tool_map_state.write().unwrap();
state.tool_to_server.remove(&tool_name);
crate::log_debug!("Unregistered dynamic agent tool: {}", tool_name);
}
pub fn register_dynamic_server_tools(
server_name: &str,
server_config: &McpServerConfig,
tool_names: &[String],
) {
let tool_map_state = match TOOL_MAP.get() {
Some(state) => state,
None => {
crate::log_debug!("Tool map not initialized, cannot register dynamic server");
return;
}
};
let mut state = tool_map_state.write().unwrap();
for tool_name in tool_names {
state
.tool_to_server
.insert(tool_name.clone(), server_config.clone());
crate::log_debug!("Registered dynamic server tool: {}", tool_name);
}
crate::log_debug!(
"Registered {} tools from dynamic server '{}'",
tool_names.len(),
server_name
);
}
pub fn unregister_dynamic_server_tools(server_name: &str, tool_names: &[String]) {
let tool_map_state = match TOOL_MAP.get() {
Some(state) => state,
None => {
crate::log_debug!("Tool map not initialized, cannot unregister dynamic server");
return;
}
};
let mut state = tool_map_state.write().unwrap();
for tool_name in tool_names {
state.tool_to_server.remove(tool_name);
crate::log_debug!("Unregistered dynamic server tool: {}", tool_name);
}
crate::log_debug!(
"Unregistered {} tools from dynamic server '{}'",
tool_names.len(),
server_name
);
}
async fn build_tool_server_map_impl(config: &Config) -> Result<HashMap<String, McpServerConfig>> {
let mut tool_map = HashMap::new();
let enabled_servers: Vec<McpServerConfig> = config.mcp.servers.to_vec();
for server in enabled_servers {
let server_functions = match server.connection_type() {
McpConnectionType::Builtin => {
match server.name() {
"core" => {
crate::mcp::get_filtered_server_functions(
"core",
server.tools(),
crate::mcp::core::get_all_functions,
)
}
"agent" => {
let server_functions = crate::mcp::agent::get_all_functions(config);
crate::mcp::filter_tools_by_patterns(server_functions, server.tools())
}
_ => {
crate::log_debug!("Unknown builtin server: {}", server.name());
Vec::new()
}
}
}
McpConnectionType::Http | McpConnectionType::Stdin => {
match crate::mcp::server::get_server_functions_cached(&server).await {
Ok(functions) => {
crate::mcp::filter_tools_by_patterns(functions, server.tools())
}
Err(e) => {
crate::log_error!(
"Server '{}' is not available: {}. Verify the server is running at the configured URL.",
server.name(),
e
);
Vec::new()
}
}
}
};
for function in server_functions {
tool_map
.entry(function.name)
.or_insert_with(|| server.clone());
}
}
for server in crate::mcp::core::dynamic::get_all_configs() {
if let Some(functions) = crate::mcp::core::dynamic::get_functions(server.name()) {
for function in functions {
tool_map
.entry(function.name)
.or_insert_with(|| server.clone());
}
}
}
for agent_config in crate::mcp::core::dynamic_agents::get_all_configs() {
let tool_name = format!("agent_{}", agent_config.name);
let agent_server = McpServerConfig::Builtin {
name: "agent".to_string(),
timeout_seconds: 300,
tools: vec![tool_name.clone()],
auto_bind: None,
};
tool_map.entry(tool_name).or_insert_with(|| agent_server);
}
Ok(tool_map)
}
fn calculate_config_hash(config: &Config) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
for server in &config.mcp.servers {
server.name().hash(&mut hasher);
server.connection_type().hash(&mut hasher);
server.tools().hash(&mut hasher);
}
hasher.finish()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tool_map_not_initialized() {
assert_eq!(get_server_for_tool("test_tool"), None);
assert_eq!(get_tool_server_name("test_tool"), None);
assert!(!is_initialized());
assert!(get_all_tool_names().is_empty());
}
}