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}