crabtalk_runtime/mcp/
tool.rs1use crate::{RuntimeHook, bridge::RuntimeBridge};
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<B: RuntimeBridge> RuntimeHook<B> {
24 pub async fn dispatch_mcp(&self, args: &str, agent: &str) -> String {
25 let input: Mcp = match serde_json::from_str(args) {
26 Ok(v) => v,
27 Err(e) => return format!("invalid arguments: {e}"),
28 };
29
30 let bridge = self.mcp.bridge().await;
31
32 let allowed_tools: Option<Vec<String>> = if let Some(scope) = self.scopes.get(agent)
34 && !scope.mcps.is_empty()
35 {
36 let servers = bridge.list_servers().await;
37 Some(
38 servers
39 .into_iter()
40 .filter(|(name, _)| scope.mcps.iter().any(|m| m == name.as_str()))
41 .flat_map(|(_, tools)| tools)
42 .collect(),
43 )
44 } else {
45 None
46 };
47
48 if !input.name.is_empty() {
50 if let Some(ref allowed) = allowed_tools
52 && !allowed.iter().any(|t| t.as_str() == input.name)
53 {
54 return format!("tool not available: {}", input.name);
55 }
56
57 let tools = bridge.tools().await;
58 if tools.iter().any(|t| t.name == input.name) {
59 let tool_args = input.args.unwrap_or_default();
60 return bridge.call(&input.name, &tool_args).await;
61 }
62 }
63
64 let query = input.name.to_lowercase();
66 let tools = bridge.tools().await;
67 let matches: Vec<String> = tools
68 .iter()
69 .filter(|t| {
70 if let Some(ref allowed) = allowed_tools
71 && !allowed.iter().any(|a| a.as_str() == t.name.as_str())
72 {
73 return false;
74 }
75 query.is_empty()
76 || t.name.to_lowercase().contains(&query)
77 || t.description.to_lowercase().contains(&query)
78 })
79 .map(|t| format!("{}: {}", t.name, t.description))
80 .collect();
81
82 if matches.is_empty() {
83 "no tools found".to_owned()
84 } else {
85 matches.join("\n")
86 }
87 }
88}