agent_code_lib/tools/
mcp_proxy.rs1use async_trait::async_trait;
7use std::sync::Arc;
8use tokio::sync::Mutex;
9
10use super::{Tool, ToolContext, ToolResult};
11use crate::error::ToolError;
12use crate::services::mcp::{McpClient, McpTool};
13
14pub struct McpProxyTool {
17 definition: McpTool,
19 qualified_name: String,
21 client: Arc<Mutex<McpClient>>,
23 server_name: String,
25}
26
27impl McpProxyTool {
28 pub fn new(definition: McpTool, server_name: &str, client: Arc<Mutex<McpClient>>) -> Self {
29 let qualified_name = format!(
30 "mcp__{}__{}",
31 normalize_name(server_name),
32 normalize_name(&definition.name),
33 );
34 Self {
35 definition,
36 qualified_name,
37 client,
38 server_name: server_name.to_string(),
39 }
40 }
41}
42
43#[async_trait]
44impl Tool for McpProxyTool {
45 fn name(&self) -> &'static str {
46 Box::leak(self.qualified_name.clone().into_boxed_str())
49 }
50
51 fn description(&self) -> &'static str {
52 let desc = self
53 .definition
54 .description
55 .clone()
56 .unwrap_or_else(|| format!("MCP tool from {}", self.server_name));
57 Box::leak(desc.into_boxed_str())
58 }
59
60 fn input_schema(&self) -> serde_json::Value {
61 self.definition.input_schema.clone()
62 }
63
64 fn is_read_only(&self) -> bool {
65 false }
67
68 fn is_concurrency_safe(&self) -> bool {
69 false }
71
72 async fn call(
73 &self,
74 input: serde_json::Value,
75 _ctx: &ToolContext,
76 ) -> Result<ToolResult, ToolError> {
77 let client = self.client.lock().await;
78
79 let result = client
80 .call_tool(&self.definition.name, input)
81 .await
82 .map_err(|e| ToolError::ExecutionFailed(format!("MCP call failed: {e}")))?;
83
84 let content = result
86 .content
87 .iter()
88 .filter_map(|c| match c {
89 crate::services::mcp::McpContent::Text { text } => Some(text.as_str()),
90 _ => None,
91 })
92 .collect::<Vec<_>>()
93 .join("\n");
94
95 Ok(ToolResult {
96 content: if content.is_empty() {
97 "(no output)".to_string()
98 } else {
99 content
100 },
101 is_error: result.is_error,
102 })
103 }
104}
105
106fn normalize_name(s: &str) -> String {
108 s.chars()
109 .map(|c| {
110 if c.is_alphanumeric() {
111 c.to_ascii_lowercase()
112 } else {
113 '_'
114 }
115 })
116 .collect()
117}
118
119pub fn create_proxy_tools(
121 server_name: &str,
122 mcp_tools: &[McpTool],
123 client: Arc<Mutex<McpClient>>,
124) -> Vec<Arc<dyn Tool>> {
125 mcp_tools
126 .iter()
127 .map(|t| {
128 Arc::new(McpProxyTool::new(t.clone(), server_name, client.clone())) as Arc<dyn Tool>
129 })
130 .collect()
131}