crabtalk_runtime/mcp/
tool.rs1use crate::{Env, host::Host};
4use schemars::JsonSchema;
5use serde::Deserialize;
6use wcore::agent::ToolDescription;
7
8#[derive(Deserialize, JsonSchema)]
9pub struct Mcp {
10 pub name: String,
13 #[serde(default)]
15 pub args: Option<String>,
16}
17
18impl ToolDescription for Mcp {
19 const DESCRIPTION: &'static str =
20 "Call an MCP tool by name, or list available tools if no exact match.";
21}
22
23impl<H: Host> Env<H> {
24 pub async fn dispatch_mcp(&self, args: &str, agent: &str) -> Result<String, String> {
25 let input: Mcp =
26 serde_json::from_str(args).map_err(|e| format!("invalid arguments: {e}"))?;
27
28 let bridge = self.mcp.bridge().await;
29
30 let allowed_tools: Option<Vec<String>> = if let Some(scope) = self.scopes.get(agent)
32 && !scope.mcps.is_empty()
33 {
34 let servers = bridge.list_servers().await;
35 Some(
36 servers
37 .into_iter()
38 .filter(|(name, _)| scope.mcps.iter().any(|m| m == name.as_str()))
39 .flat_map(|(_, tools)| tools)
40 .collect(),
41 )
42 } else {
43 None
44 };
45
46 if !input.name.is_empty() {
48 if let Some(ref allowed) = allowed_tools
50 && !allowed.iter().any(|t| t.as_str() == input.name)
51 {
52 return Err(format!("tool not available: {}", input.name));
53 }
54
55 let tools = bridge.tools().await;
56 if tools.iter().any(|t| t.function.name == input.name) {
57 let tool_args = input.args.unwrap_or_default();
58 return bridge.call(&input.name, &tool_args).await;
59 }
60 }
61
62 let query = input.name.to_lowercase();
64 let tools = bridge.tools().await;
65 let matches: Vec<String> = tools
66 .iter()
67 .filter(|t| {
68 if let Some(ref allowed) = allowed_tools
69 && !allowed
70 .iter()
71 .any(|a| a.as_str() == t.function.name.as_str())
72 {
73 return false;
74 }
75 query.is_empty()
76 || t.function.name.to_lowercase().contains(&query)
77 || t.function
78 .description
79 .as_deref()
80 .is_some_and(|d| d.to_lowercase().contains(&query))
81 })
82 .map(|t| {
83 format!(
84 "{}: {}",
85 t.function.name,
86 t.function.description.as_deref().unwrap_or(""),
87 )
88 })
89 .collect();
90
91 if matches.is_empty() {
94 Ok("no tools found".to_owned())
95 } else {
96 Ok(matches.join("\n"))
97 }
98 }
99}