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::{
10 CallToolRequest, CallToolResult, Cursor, ListToolsRequest, ListToolsResult, Tool,
11};
12use turbomcp_protocol::{Error, Result};
13
14/// Maximum number of pagination pages to prevent infinite loops from misbehaving servers.
15const MAX_PAGINATION_PAGES: usize = 1000;
16
17impl<T: turbomcp_transport::Transport + 'static> super::super::core::Client<T> {
18 /// List all available tools from the MCP server
19 ///
20 /// Returns complete tool definitions with schemas that can be used
21 /// for form generation, validation, and documentation. Tools represent
22 /// executable functions provided by the server.
23 ///
24 /// # Returns
25 ///
26 /// Returns a vector of Tool objects with complete metadata including names,
27 /// descriptions, and input schemas. These schemas can be used to generate
28 /// user interfaces for tool invocation.
29 ///
30 /// # Examples
31 ///
32 /// ```rust,no_run
33 /// # use turbomcp_client::Client;
34 /// # use turbomcp_transport::stdio::StdioTransport;
35 /// # async fn example() -> turbomcp_protocol::Result<()> {
36 /// let mut client = Client::new(StdioTransport::new());
37 /// client.initialize().await?;
38 ///
39 /// let tools = client.list_tools().await?;
40 /// for tool in tools {
41 /// println!("Tool: {} - {}", tool.name, tool.description.as_deref().unwrap_or("No description"));
42 /// }
43 /// # Ok(())
44 /// # }
45 /// ```
46 pub async fn list_tools(&self) -> Result<Vec<Tool>> {
47 if !self.inner.initialized.load(Ordering::Relaxed) {
48 return Err(Error::invalid_request("Client not initialized"));
49 }
50
51 let mut all_tools = Vec::new();
52 let mut cursor = None;
53 for _ in 0..MAX_PAGINATION_PAGES {
54 let result = self.list_tools_paginated(cursor).await?;
55 let page_empty = result.tools.is_empty();
56 all_tools.extend(result.tools);
57 match result.next_cursor {
58 Some(c) if !page_empty => cursor = Some(c),
59 _ => break,
60 }
61 }
62 Ok(all_tools)
63 }
64
65 /// List tools with pagination support
66 ///
67 /// Returns the full `ListToolsResult` including `next_cursor` for manual
68 /// pagination control. Use `list_tools()` for automatic pagination.
69 ///
70 /// # Arguments
71 ///
72 /// * `cursor` - Optional cursor from a previous `ListToolsResult::next_cursor`
73 pub async fn list_tools_paginated(&self, cursor: Option<Cursor>) -> Result<ListToolsResult> {
74 if !self.inner.initialized.load(Ordering::Relaxed) {
75 return Err(Error::invalid_request("Client not initialized"));
76 }
77
78 let request = ListToolsRequest {
79 cursor,
80 _meta: None,
81 };
82 let params = if request.cursor.is_some() {
83 Some(serde_json::to_value(&request)?)
84 } else {
85 None
86 };
87 self.inner.protocol.request("tools/list", params).await
88 }
89
90 /// List available tool names from the MCP server
91 ///
92 /// Returns only the tool names for cases where full schemas are not needed.
93 /// For most use cases, prefer `list_tools()` which provides complete tool definitions.
94 ///
95 /// # Returns
96 ///
97 /// Returns a vector of tool names available on the server.
98 ///
99 /// # Examples
100 ///
101 /// ```rust,no_run
102 /// # use turbomcp_client::Client;
103 /// # use turbomcp_transport::stdio::StdioTransport;
104 /// # async fn example() -> turbomcp_protocol::Result<()> {
105 /// let mut client = Client::new(StdioTransport::new());
106 /// client.initialize().await?;
107 ///
108 /// let tool_names = client.list_tool_names().await?;
109 /// for name in tool_names {
110 /// println!("Available tool: {}", name);
111 /// }
112 /// # Ok(())
113 /// # }
114 /// ```
115 pub async fn list_tool_names(&self) -> Result<Vec<String>> {
116 let tools = self.list_tools().await?;
117 Ok(tools.into_iter().map(|tool| tool.name).collect())
118 }
119
120 /// Call a tool on the server
121 ///
122 /// Executes a tool on the server with the provided arguments and returns
123 /// the complete MCP `CallToolResult`.
124 ///
125 /// # Arguments
126 ///
127 /// * `name` - The name of the tool to call
128 /// * `arguments` - Optional arguments to pass to the tool
129 /// * `task` - Optional task metadata for task-augmented requests (MCP 2025-11-25 draft)
130 ///
131 /// # Returns
132 ///
133 /// Returns the complete `CallToolResult` with:
134 /// - `content: Vec<ContentBlock>` - All content blocks (text, image, resource, audio, etc.)
135 /// - `is_error: Option<bool>` - Whether the tool execution resulted in an error
136 /// - `structured_content: Option<serde_json::Value>` - Schema-validated structured output
137 /// - `_meta: Option<serde_json::Value>` - Metadata for client applications (not exposed to LLMs)
138 /// - `task_id: Option<String>` - Task identifier if task-augmented
139 ///
140 /// # Examples
141 ///
142 /// ## Basic Usage
143 ///
144 /// ```rust,no_run
145 /// # use turbomcp_client::Client;
146 /// # use turbomcp_transport::stdio::StdioTransport;
147 /// # use turbomcp_protocol::types::ContentBlock;
148 /// # use std::collections::HashMap;
149 /// # async fn example() -> turbomcp_protocol::Result<()> {
150 /// let mut client = Client::new(StdioTransport::new());
151 /// client.initialize().await?;
152 ///
153 /// let mut args = HashMap::new();
154 /// args.insert("input".to_string(), serde_json::json!("test"));
155 ///
156 /// let result = client.call_tool("my_tool", Some(args), None).await?;
157 /// # Ok(())
158 /// # }
159 /// ```
160 pub async fn call_tool(
161 &self,
162 name: &str,
163 arguments: Option<HashMap<String, serde_json::Value>>,
164 task: Option<turbomcp_protocol::types::tasks::TaskMetadata>,
165 ) -> Result<CallToolResult> {
166 if !self.inner.initialized.load(Ordering::Relaxed) {
167 return Err(Error::invalid_request("Client not initialized"));
168 }
169
170 let request_data = CallToolRequest {
171 name: name.to_string(),
172 arguments: Some(arguments.unwrap_or_default()),
173 task,
174 _meta: None,
175 };
176
177 // Core protocol call
178 let result: CallToolResult = self
179 .inner
180 .protocol
181 .request("tools/call", Some(serde_json::to_value(&request_data)?))
182 .await?;
183
184 Ok(result) // Return full CallToolResult - MCP spec compliant!
185 }
186}