use crate::{Env, host::Host};
use schemars::JsonSchema;
use serde::Deserialize;
use wcore::agent::ToolDescription;
#[derive(Deserialize, JsonSchema)]
pub struct Mcp {
pub name: String,
#[serde(default)]
pub args: Option<String>,
}
impl ToolDescription for Mcp {
const DESCRIPTION: &'static str =
"Call an MCP tool by name, or list available tools if no exact match.";
}
impl<H: Host> Env<H> {
pub async fn dispatch_mcp(&self, args: &str, agent: &str) -> Result<String, String> {
let input: Mcp =
serde_json::from_str(args).map_err(|e| format!("invalid arguments: {e}"))?;
let bridge = self.mcp.bridge().await;
let allowed_tools: Option<Vec<String>> = if let Some(scope) = self.scopes.get(agent)
&& !scope.mcps.is_empty()
{
let servers = bridge.list_servers().await;
Some(
servers
.into_iter()
.filter(|(name, _)| scope.mcps.iter().any(|m| m == name.as_str()))
.flat_map(|(_, tools)| tools)
.collect(),
)
} else {
None
};
if !input.name.is_empty() {
if let Some(ref allowed) = allowed_tools
&& !allowed.iter().any(|t| t.as_str() == input.name)
{
return Err(format!("tool not available: {}", input.name));
}
let tools = bridge.tools().await;
if tools.iter().any(|t| t.function.name == input.name) {
let tool_args = input.args.unwrap_or_default();
return bridge.call(&input.name, &tool_args).await;
}
}
let query = input.name.to_lowercase();
let tools = bridge.tools().await;
let matches: Vec<String> = tools
.iter()
.filter(|t| {
if let Some(ref allowed) = allowed_tools
&& !allowed
.iter()
.any(|a| a.as_str() == t.function.name.as_str())
{
return false;
}
query.is_empty()
|| t.function.name.to_lowercase().contains(&query)
|| t.function
.description
.as_deref()
.is_some_and(|d| d.to_lowercase().contains(&query))
})
.map(|t| {
format!(
"{}: {}",
t.function.name,
t.function.description.as_deref().unwrap_or(""),
)
})
.collect();
if matches.is_empty() {
Ok("no tools found".to_owned())
} else {
Ok(matches.join("\n"))
}
}
}