use async_trait::async_trait;
use mofa_kernel::agent::components::tool::{
Tool, ToolDescriptor, ToolRegistry as ToolRegistryTrait,
};
use mofa_kernel::agent::error::AgentResult;
use std::collections::HashMap;
use std::sync::Arc;
pub struct ToolRegistry {
tools: HashMap<String, Arc<dyn Tool>>,
sources: HashMap<String, ToolSource>,
mcp_endpoints: Vec<String>,
#[cfg(feature = "mcp")]
mcp_client: Option<std::sync::Arc<tokio::sync::RwLock<super::mcp::McpClientManager>>>,
}
#[derive(Debug, Clone)]
pub enum ToolSource {
Builtin,
Mcp { endpoint: String },
Plugin { path: String },
Dynamic,
}
impl ToolRegistry {
pub fn new() -> Self {
Self {
tools: HashMap::new(),
sources: HashMap::new(),
mcp_endpoints: Vec::new(),
#[cfg(feature = "mcp")]
mcp_client: None,
}
}
pub fn register_with_source(
&mut self,
tool: Arc<dyn Tool>,
source: ToolSource,
) -> AgentResult<()> {
let name = tool.name().to_string();
self.sources.insert(name.clone(), source);
self.tools.insert(name, tool);
Ok(())
}
#[cfg(feature = "mcp")]
pub async fn load_mcp_server(
&mut self,
config: mofa_kernel::agent::components::mcp::McpServerConfig,
) -> AgentResult<Vec<String>> {
use mofa_kernel::agent::components::mcp::McpClient;
let endpoint = config.name.clone();
self.mcp_endpoints.push(endpoint.clone());
let client = self
.mcp_client
.get_or_insert_with(|| {
std::sync::Arc::new(tokio::sync::RwLock::new(
super::mcp::McpClientManager::new(),
))
})
.clone();
{
let mut client_guard = client.write().await;
client_guard.connect(config).await?;
}
let tools = {
let client_guard = client.read().await;
client_guard.list_tools(&endpoint).await?
};
let mut registered_names = Vec::new();
for tool_info in tools {
let name = tool_info.name.clone();
let adapter = super::mcp::McpToolAdapter::new(
endpoint.clone(),
tool_info,
client.clone(),
);
self.register_with_source(
std::sync::Arc::new(adapter),
ToolSource::Mcp {
endpoint: endpoint.clone(),
},
)?;
registered_names.push(name);
}
tracing::info!(
"Loaded {} tools from MCP server '{}'",
registered_names.len(),
endpoint,
);
Ok(registered_names)
}
#[cfg(not(feature = "mcp"))]
pub async fn load_mcp_server(&mut self, endpoint: &str) -> AgentResult<Vec<String>> {
self.mcp_endpoints.push(endpoint.to_string());
Ok(vec![])
}
pub async fn unload_mcp_server(&mut self, endpoint: &str) -> AgentResult<Vec<String>> {
self.mcp_endpoints.retain(|e| e != endpoint);
let to_remove: Vec<String> = self
.sources
.iter()
.filter_map(|(name, source)| {
if let ToolSource::Mcp { endpoint: ep } = source
&& ep == endpoint
{
return Some(name.clone());
}
None
})
.collect();
for name in &to_remove {
self.tools.remove(name);
self.sources.remove(name);
}
Ok(to_remove)
}
pub async fn hot_reload_plugin(&mut self, _path: &str) -> AgentResult<Vec<String>> {
Ok(vec![])
}
pub fn get_source(&self, name: &str) -> Option<&ToolSource> {
self.sources.get(name)
}
pub fn filter_by_source(&self, source_type: &str) -> Vec<ToolDescriptor> {
self.tools
.iter()
.filter(|(name, _)| {
if let Some(source) = self.sources.get(*name) {
match source {
ToolSource::Builtin => source_type == "builtin",
ToolSource::Mcp { .. } => source_type == "mcp",
ToolSource::Plugin { .. } => source_type == "plugin",
ToolSource::Dynamic => source_type == "dynamic",
}
} else {
false
}
})
.map(|(_, tool)| ToolDescriptor::from_tool(tool.as_ref()))
.collect()
}
pub fn mcp_endpoints(&self) -> &[String] {
&self.mcp_endpoints
}
}
impl Default for ToolRegistry {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl ToolRegistryTrait for ToolRegistry {
fn register(&mut self, tool: Arc<dyn Tool>) -> AgentResult<()> {
self.register_with_source(tool, ToolSource::Dynamic)
}
fn get(&self, name: &str) -> Option<Arc<dyn Tool>> {
self.tools.get(name).cloned()
}
fn unregister(&mut self, name: &str) -> AgentResult<bool> {
self.sources.remove(name);
Ok(self.tools.remove(name).is_some())
}
fn list(&self) -> Vec<ToolDescriptor> {
self.tools
.values()
.map(|t| ToolDescriptor::from_tool(t.as_ref()))
.collect()
}
fn list_names(&self) -> Vec<String> {
self.tools.keys().cloned().collect()
}
fn contains(&self, name: &str) -> bool {
self.tools.contains_key(name)
}
fn count(&self) -> usize {
self.tools.len()
}
}
pub struct ToolSearcher<'a> {
registry: &'a ToolRegistry,
}
impl<'a> ToolSearcher<'a> {
pub fn new(registry: &'a ToolRegistry) -> Self {
Self { registry }
}
pub fn search_by_name(&self, pattern: &str) -> Vec<ToolDescriptor> {
let pattern_lower = pattern.to_lowercase();
self.registry
.tools
.iter()
.filter(|(name, _)| name.to_lowercase().contains(&pattern_lower))
.map(|(_, tool)| ToolDescriptor::from_tool(tool.as_ref()))
.collect()
}
pub fn search_by_description(&self, query: &str) -> Vec<ToolDescriptor> {
let query_lower = query.to_lowercase();
self.registry
.tools
.values()
.filter(|tool| tool.description().to_lowercase().contains(&query_lower))
.map(|tool| ToolDescriptor::from_tool(tool.as_ref()))
.collect()
}
pub fn search_by_tag(&self, tag: &str) -> Vec<ToolDescriptor> {
self.registry
.tools
.values()
.filter(|tool| {
let metadata = tool.metadata();
metadata.tags.iter().any(|t| t == tag)
})
.map(|tool| ToolDescriptor::from_tool(tool.as_ref()))
.collect()
}
pub fn search_dangerous(&self) -> Vec<ToolDescriptor> {
self.registry
.tools
.values()
.filter(|tool| tool.metadata().is_dangerous || tool.requires_confirmation())
.map(|tool| ToolDescriptor::from_tool(tool.as_ref()))
.collect()
}
}