Skip to main content

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}