turbomcp_client/client/operations/
tools.rs

1//! Tool operations for MCP client
2//!
3//! This module provides tool-related functionality including listing tools,
4//! calling tools, and processing tool results.
5
6use std::collections::HashMap;
7use std::sync::atomic::Ordering;
8
9use turbomcp_protocol::types::{CallToolRequest, CallToolResult, Content, ListToolsResult, Tool};
10use turbomcp_protocol::{Error, Result};
11
12use crate::with_plugins;
13
14impl<T: turbomcp_transport::Transport + 'static> super::super::core::Client<T> {
15    /// List all available tools from the MCP server
16    ///
17    /// Returns complete tool definitions with schemas that can be used
18    /// for form generation, validation, and documentation. Tools represent
19    /// executable functions provided by the server.
20    ///
21    /// # Returns
22    ///
23    /// Returns a vector of Tool objects with complete metadata including names,
24    /// descriptions, and input schemas. These schemas can be used to generate
25    /// user interfaces for tool invocation.
26    ///
27    /// # Examples
28    ///
29    /// ```rust,no_run
30    /// # use turbomcp_client::Client;
31    /// # use turbomcp_transport::stdio::StdioTransport;
32    /// # async fn example() -> turbomcp_protocol::Result<()> {
33    /// let mut client = Client::new(StdioTransport::new());
34    /// client.initialize().await?;
35    ///
36    /// let tools = client.list_tools().await?;
37    /// for tool in tools {
38    ///     println!("Tool: {} - {}", tool.name, tool.description.as_deref().unwrap_or("No description"));
39    /// }
40    /// # Ok(())
41    /// # }
42    /// ```
43    pub async fn list_tools(&self) -> Result<Vec<Tool>> {
44        if !self.inner.initialized.load(Ordering::Relaxed) {
45            return Err(Error::bad_request("Client not initialized"));
46        }
47
48        // Send tools/list request with plugin middleware
49        let response: ListToolsResult = self.execute_with_plugins("tools/list", None).await?;
50        Ok(response.tools) // Return full Tool objects with schemas
51    }
52
53    /// List available tool names from the MCP server
54    ///
55    /// Returns only the tool names for cases where full schemas are not needed.
56    /// For most use cases, prefer `list_tools()` which provides complete tool definitions.
57    ///
58    /// # Returns
59    ///
60    /// Returns a vector of tool names available on the server.
61    ///
62    /// # Examples
63    ///
64    /// ```rust,no_run
65    /// # use turbomcp_client::Client;
66    /// # use turbomcp_transport::stdio::StdioTransport;
67    /// # async fn example() -> turbomcp_protocol::Result<()> {
68    /// let mut client = Client::new(StdioTransport::new());
69    /// client.initialize().await?;
70    ///
71    /// let tool_names = client.list_tool_names().await?;
72    /// for name in tool_names {
73    ///     println!("Available tool: {}", name);
74    /// }
75    /// # Ok(())
76    /// # }
77    /// ```
78    pub async fn list_tool_names(&self) -> Result<Vec<String>> {
79        let tools = self.list_tools().await?;
80        Ok(tools.into_iter().map(|tool| tool.name).collect())
81    }
82
83    /// Call a tool on the server
84    ///
85    /// Executes a tool on the server with the provided arguments.
86    ///
87    /// # Arguments
88    ///
89    /// * `name` - The name of the tool to call
90    /// * `arguments` - Optional arguments to pass to the tool
91    ///
92    /// # Returns
93    ///
94    /// Returns the result of the tool execution.
95    ///
96    /// # Examples
97    ///
98    /// ```rust,no_run
99    /// # use turbomcp_client::Client;
100    /// # use turbomcp_transport::stdio::StdioTransport;
101    /// # use std::collections::HashMap;
102    /// # async fn example() -> turbomcp_protocol::Result<()> {
103    /// let mut client = Client::new(StdioTransport::new());
104    /// client.initialize().await?;
105    ///
106    /// let mut args = HashMap::new();
107    /// args.insert("input".to_string(), serde_json::json!("test"));
108    ///
109    /// let result = client.call_tool("my_tool", Some(args)).await?;
110    /// println!("Tool result: {:?}", result);
111    /// # Ok(())
112    /// # }
113    /// ```
114    pub async fn call_tool(
115        &self,
116        name: &str,
117        arguments: Option<HashMap<String, serde_json::Value>>,
118    ) -> Result<serde_json::Value> {
119        if !self.inner.initialized.load(Ordering::Relaxed) {
120            return Err(Error::bad_request("Client not initialized"));
121        }
122
123        // 🎉 TurboMCP v1.0.7: Clean plugin execution with macro!
124        let request_data = CallToolRequest {
125            name: name.to_string(),
126            arguments: Some(arguments.unwrap_or_default()),
127            _meta: None,
128        };
129
130        with_plugins!(self, "tools/call", request_data, {
131            // Core protocol call - plugins execute automatically around this
132            let result: CallToolResult = self
133                .inner
134                .protocol
135                .request("tools/call", Some(serde_json::to_value(&request_data)?))
136                .await?;
137
138            Ok(self.extract_tool_content(&result))
139        })
140    }
141
142    /// Helper method to extract content from CallToolResult
143    fn extract_tool_content(&self, response: &CallToolResult) -> serde_json::Value {
144        // Extract content from response - for simplicity, return the first text content
145        if let Some(content) = response.content.first() {
146            match content {
147                Content::Text(text_content) => serde_json::json!({
148                    "text": text_content.text,
149                    "is_error": response.is_error.unwrap_or(false)
150                }),
151                Content::Image(image_content) => serde_json::json!({
152                    "image": image_content.data,
153                    "mime_type": image_content.mime_type,
154                    "is_error": response.is_error.unwrap_or(false)
155                }),
156                Content::Resource(resource_content) => serde_json::json!({
157                    "resource": resource_content.resource,
158                    "annotations": resource_content.annotations,
159                    "is_error": response.is_error.unwrap_or(false)
160                }),
161                Content::Audio(audio_content) => serde_json::json!({
162                    "audio": audio_content.data,
163                    "mime_type": audio_content.mime_type,
164                    "is_error": response.is_error.unwrap_or(false)
165                }),
166                Content::ResourceLink(resource_link) => serde_json::json!({
167                    "resource_uri": resource_link.uri,
168                    "is_error": response.is_error.unwrap_or(false)
169                }),
170            }
171        } else {
172            serde_json::json!({
173                "message": "No content returned",
174                "is_error": response.is_error.unwrap_or(false)
175            })
176        }
177    }
178}