Skip to main content

atomcode_core/mcp/
tool_adapter.rs

1//! Tool adapter - exposes MCP tools through the Tool trait.
2
3use anyhow::Result;
4use async_trait::async_trait;
5use serde_json::json;
6
7use crate::tool::{ApprovalRequirement, Tool, ToolContext, ToolDef, ToolResult};
8
9use super::client::McpToolInfo;
10use super::registry::McpRegistry;
11
12/// Adapter that wraps an MCP tool and implements the Tool trait.
13pub struct McpToolAdapter {
14    registry: std::sync::Arc<McpRegistry>,
15    info: McpToolInfo,
16    tool_name: String, // mcp__{server}__{tool}
17}
18
19impl McpToolAdapter {
20    /// Create a new adapter for an MCP tool.
21    pub fn new(registry: std::sync::Arc<McpRegistry>, info: McpToolInfo) -> Self {
22        let tool_name = format!("mcp__{}__{}", info.server_name, info.tool_name);
23        Self {
24            registry,
25            info,
26            tool_name,
27        }
28    }
29
30    /// Get the full tool name (mcp__{server}__{tool}).
31    pub fn full_name(&self) -> &str {
32        &self.tool_name
33    }
34}
35
36#[async_trait]
37impl Tool for McpToolAdapter {
38    fn definition(&self) -> ToolDef {
39        ToolDef {
40            name: Box::leak(self.tool_name.clone().into_boxed_str()),
41            description: if self.info.description.is_empty() {
42                format!(
43                    "MCP tool from server '{}'. See input schema for details.",
44                    self.info.server_name
45                )
46            } else {
47                format!(
48                    "[MCP:{}] {}",
49                    self.info.server_name, self.info.description
50                )
51            },
52            parameters: self.info.input_schema.clone(),
53        }
54    }
55
56    fn approval(&self, args: &str) -> ApprovalRequirement {
57        // MCP tools require approval by default - they are external code.
58        ApprovalRequirement::RequireApproval(format!(
59            "MCP tool '{}' from server '{}' wants to execute with arguments: {}",
60            self.info.tool_name, self.info.server_name, args
61        ))
62    }
63
64    async fn execute(&self, args: &str, _ctx: &ToolContext) -> Result<ToolResult> {
65        let arguments: serde_json::Value = if args.is_empty() || args == "{}" {
66            json!({})
67        } else {
68            serde_json::from_str(args)?
69        };
70
71        let output = self
72            .registry
73            .call_tool(&self.info.server_name, &self.info.tool_name, arguments)
74            .await?;
75
76        Ok(ToolResult {
77            call_id: String::new(),
78            output,
79            success: true,
80        })
81    }
82}
83
84/// Register all MCP tools from a registry into a ToolRegistry (sync version).
85/// Use this at startup when you have mutable access to the registry.
86pub fn register_mcp_tools(
87    registry: &mut crate::tool::ToolRegistry,
88    mcp_registry: std::sync::Arc<McpRegistry>,
89    tools: Vec<McpToolInfo>,
90) {
91    for info in tools {
92        let adapter = McpToolAdapter::new(mcp_registry.clone(), info);
93        registry.register_sync(Box::new(adapter));
94    }
95}
96
97/// Register all MCP tools from a registry into a ToolRegistry (async version).
98/// Use this when registering tools dynamically after startup.
99pub async fn register_mcp_tools_async(
100    registry: &crate::tool::ToolRegistry,
101    mcp_registry: std::sync::Arc<McpRegistry>,
102    tools: Vec<McpToolInfo>,
103) {
104    for info in tools {
105        let adapter = McpToolAdapter::new(mcp_registry.clone(), info);
106        registry.register(Box::new(adapter)).await;
107    }
108}