Skip to main content

telos_agent/integrations/mcp/
bridge.rs

1use async_trait::async_trait;
2use serde_json::Value;
3use std::sync::Arc;
4
5use crate::error::AgentError;
6use crate::integrations::mcp::client::McpTool;
7use crate::integrations::mcp::manager::McpManager;
8use crate::tools::api::{PermissionDecision, Tool, ToolContext, ToolDefinition, ToolOutput};
9
10/// Wraps an MCP server tool as a native [`Tool`].
11///
12/// Each bridge holds a reference to the [`McpManager`] so that `invoke` can
13/// dispatch the call to the correct server.
14pub struct McpToolBridge {
15    server_id: String,
16    mcp_tool: McpTool,
17    manager: Arc<McpManager>,
18}
19
20impl McpToolBridge {
21    /// Create a new bridge wrapping an MCP tool on a named server.
22    pub fn new(server_id: String, mcp_tool: McpTool, manager: Arc<McpManager>) -> Self {
23        Self { server_id, mcp_tool, manager }
24    }
25
26    /// Normalized tool name: `"mcp__<server>__<tool>"`.
27    ///
28    /// This namespace avoids collisions when multiple MCP servers export tools
29    /// with the same short name.
30    pub fn normalized_name(server_id: &str, tool_name: &str) -> String {
31        format!("mcp__{server_id}__{tool_name}")
32    }
33}
34
35#[async_trait]
36impl Tool for McpToolBridge {
37    fn definition(&self) -> ToolDefinition {
38        ToolDefinition {
39            name: Self::normalized_name(&self.server_id, &self.mcp_tool.name),
40            description: format!("[MCP:{}] {}", self.server_id, self.mcp_tool.description),
41            input_schema: self.mcp_tool.input_schema.clone(),
42        }
43    }
44
45    fn is_concurrency_safe(&self, _arguments: &Value) -> bool {
46        false
47    }
48
49    async fn check_permission(
50        &self,
51        _arguments: &Value,
52        _context: &ToolContext,
53    ) -> Result<PermissionDecision, AgentError> {
54        Ok(PermissionDecision::Ask {
55            reason: format!("MCP tool '{}' from server '{}'", self.mcp_tool.name, self.server_id),
56        })
57    }
58
59    async fn invoke(
60        &self,
61        arguments: Value,
62        _context: ToolContext,
63    ) -> Result<ToolOutput, AgentError> {
64        let result =
65            self.manager.call_tool(&self.server_id, &self.mcp_tool.name, arguments).await?;
66        Ok(ToolOutput::json(result))
67    }
68}