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, 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 and returns
86    /// the complete MCP `CallToolResult`.
87    ///
88    /// # Arguments
89    ///
90    /// * `name` - The name of the tool to call
91    /// * `arguments` - Optional arguments to pass to the tool
92    ///
93    /// # Returns
94    ///
95    /// Returns the complete `CallToolResult` with:
96    /// - `content: Vec<ContentBlock>` - All content blocks (text, image, resource, audio, etc.)
97    /// - `is_error: Option<bool>` - Whether the tool execution resulted in an error
98    /// - `structured_content: Option<serde_json::Value>` - Schema-validated structured output
99    /// - `_meta: Option<serde_json::Value>` - Metadata for client applications (not exposed to LLMs)
100    ///
101    /// # Examples
102    ///
103    /// ## Basic Usage
104    ///
105    /// ```rust,no_run
106    /// # use turbomcp_client::Client;
107    /// # use turbomcp_transport::stdio::StdioTransport;
108    /// # use turbomcp_protocol::types::Content;
109    /// # use std::collections::HashMap;
110    /// # async fn example() -> turbomcp_protocol::Result<()> {
111    /// let mut client = Client::new(StdioTransport::new());
112    /// client.initialize().await?;
113    ///
114    /// let mut args = HashMap::new();
115    /// args.insert("input".to_string(), serde_json::json!("test"));
116    ///
117    /// let result = client.call_tool("my_tool", Some(args)).await?;
118    ///
119    /// // Access all content blocks
120    /// for content in &result.content {
121    ///     match content {
122    ///         Content::Text(text) => println!("Text: {}", text.text),
123    ///         Content::Image(image) => println!("Image: {}", image.mime_type),
124    ///         _ => {}
125    ///     }
126    /// }
127    ///
128    /// // Check for errors
129    /// if result.is_error.unwrap_or(false) {
130    ///     eprintln!("Tool execution failed");
131    /// }
132    /// # Ok(())
133    /// # }
134    /// ```
135    ///
136    /// ## Structured Output (Schema Validation)
137    ///
138    /// ```rust,no_run
139    /// # use turbomcp_client::Client;
140    /// # use turbomcp_transport::stdio::StdioTransport;
141    /// # use serde::Deserialize;
142    /// # use std::collections::HashMap;
143    /// # async fn example() -> turbomcp_protocol::Result<()> {
144    /// # #[derive(Deserialize)]
145    /// # struct WeatherData {
146    /// #     temperature: f64,
147    /// #     conditions: String,
148    /// # }
149    /// let mut client = Client::new(StdioTransport::new());
150    /// client.initialize().await?;
151    ///
152    /// let result = client.call_tool("get_weather", None).await?;
153    ///
154    /// // Access schema-validated structured output
155    /// if let Some(structured) = result.structured_content {
156    ///     let weather: WeatherData = serde_json::from_value(structured)?;
157    ///     println!("Temperature: {}°C", weather.temperature);
158    /// }
159    /// # Ok(())
160    /// # }
161    /// ```
162    ///
163    /// ## Metadata Access
164    ///
165    /// ```rust,no_run
166    /// # use turbomcp_client::Client;
167    /// # use turbomcp_transport::stdio::StdioTransport;
168    /// # use std::collections::HashMap;
169    /// # async fn example() -> turbomcp_protocol::Result<()> {
170    /// let mut client = Client::new(StdioTransport::new());
171    /// client.initialize().await?;
172    ///
173    /// let result = client.call_tool("query_database", None).await?;
174    ///
175    /// // Access metadata (tracking IDs, performance metrics, etc.)
176    /// if let Some(meta) = result._meta {
177    ///     if let Some(query_id) = meta.get("query_id") {
178    ///         println!("Query ID: {}", query_id);
179    ///     }
180    /// }
181    /// # Ok(())
182    /// # }
183    /// ```
184    pub async fn call_tool(
185        &self,
186        name: &str,
187        arguments: Option<HashMap<String, serde_json::Value>>,
188    ) -> Result<CallToolResult> {
189        if !self.inner.initialized.load(Ordering::Relaxed) {
190            return Err(Error::bad_request("Client not initialized"));
191        }
192
193        let request_data = CallToolRequest {
194            name: name.to_string(),
195            arguments: Some(arguments.unwrap_or_default()),
196            _meta: None,
197            ..Default::default()
198        };
199
200        with_plugins!(self, "tools/call", request_data, {
201            // Core protocol call - plugins execute automatically around this
202            let result: CallToolResult = self
203                .inner
204                .protocol
205                .request("tools/call", Some(serde_json::to_value(&request_data)?))
206                .await?;
207
208            Ok(result) // Return full CallToolResult - MCP spec compliant!
209        })
210    }
211}