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}