Skip to main content

codetether_agent/tool/
mcp_tools.rs

1//! MCP tool wrappers for exposing external MCP tools through the local Tool trait.
2
3use super::{Tool, ToolResult};
4use anyhow::Result;
5use async_trait::async_trait;
6use serde_json::Value;
7use std::sync::Arc;
8
9/// Manages a connection to an MCP server and produces local tool wrappers.
10pub struct McpToolManager {
11    client: Arc<crate::mcp::McpClient>,
12}
13
14impl McpToolManager {
15    /// Connect to an MCP server subprocess and create a manager.
16    pub async fn connect_subprocess(command: &str, args: &[&str]) -> Result<Self> {
17        let client = crate::mcp::McpClient::connect_subprocess(command, args).await?;
18        Ok(Self { client })
19    }
20
21    /// Build local wrappers for every tool currently advertised by the MCP server.
22    pub async fn wrappers(&self) -> Vec<McpToolWrapper> {
23        self.client
24            .tools()
25            .await
26            .into_iter()
27            .map(|tool| McpToolWrapper::new(Arc::clone(&self.client), tool))
28            .collect()
29    }
30
31    /// Return the underlying MCP client.
32    pub fn client(&self) -> Arc<crate::mcp::McpClient> {
33        Arc::clone(&self.client)
34    }
35}
36
37/// Wraps a single remote MCP tool so it can be executed via the local Tool trait.
38#[derive(Clone)]
39pub struct McpToolWrapper {
40    client: Arc<crate::mcp::McpClient>,
41    tool: crate::mcp::McpTool,
42    id: String,
43}
44
45impl McpToolWrapper {
46    pub fn new(client: Arc<crate::mcp::McpClient>, tool: crate::mcp::McpTool) -> Self {
47        let id = format!("mcp:{}", tool.name);
48        Self { client, tool, id }
49    }
50}
51
52#[async_trait]
53impl Tool for McpToolWrapper {
54    fn id(&self) -> &str {
55        &self.id
56    }
57
58    fn name(&self) -> &str {
59        &self.tool.name
60    }
61
62    fn description(&self) -> &str {
63        self.tool
64            .description
65            .as_deref()
66            .unwrap_or("Remote MCP tool")
67    }
68
69    fn parameters(&self) -> Value {
70        self.tool.input_schema.clone()
71    }
72
73    async fn execute(&self, args: Value) -> Result<ToolResult> {
74        let result = self.client.call_tool(&self.tool.name, args).await?;
75
76        let output = result
77            .content
78            .iter()
79            .map(|item| match item {
80                crate::mcp::ToolContent::Text { text } => text.clone(),
81                crate::mcp::ToolContent::Image { data, mime_type } => {
82                    format!("[image: {} ({} bytes)]", mime_type, data.len())
83                }
84                crate::mcp::ToolContent::Resource { resource } => {
85                    serde_json::to_string(resource).unwrap_or_default()
86                }
87            })
88            .collect::<Vec<_>>()
89            .join("\n");
90
91        if result.is_error {
92            Ok(ToolResult::error(output))
93        } else {
94            Ok(ToolResult::success(output))
95        }
96    }
97}