turbomcp_protocol/types/
tools.rs

1//! Types for the MCP tool-calling system.
2//!
3//! This module defines the data structures for defining tools, their input/output schemas,
4//! and the requests and responses used to list and execute them, as specified by the MCP standard.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9use super::{content::ContentBlock, core::Cursor};
10
11/// Provides additional, optional metadata about a tool.
12///
13/// These annotations offer hints to clients and LLMs about the tool's behavior,
14/// helping them make more informed decisions about when and how to use the tool.
15#[derive(Debug, Clone, Serialize, Deserialize, Default)]
16pub struct ToolAnnotations {
17    /// A user-friendly title for the tool, which may be used in UIs instead of the programmatic `name`.
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub title: Option<String>,
20    /// Specifies the intended audience for the tool (e.g., "developer", "admin").
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub audience: Option<Vec<String>>,
23    /// A numeric value indicating the tool's priority, useful for sorting or ranking.
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub priority: Option<f64>,
26    /// If `true`, hints that the tool may perform destructive actions (e.g., deleting data).
27    #[serde(skip_serializing_if = "Option::is_none")]
28    #[serde(rename = "destructiveHint")]
29    pub destructive_hint: Option<bool>,
30    /// If `true`, hints that calling the tool multiple times with the same arguments will not have additional effects.
31    #[serde(skip_serializing_if = "Option::is_none")]
32    #[serde(rename = "idempotentHint")]
33    pub idempotent_hint: Option<bool>,
34    /// If `true`, hints that the tool may interact with external systems or the real world.
35    #[serde(skip_serializing_if = "Option::is_none")]
36    #[serde(rename = "openWorldHint")]
37    pub open_world_hint: Option<bool>,
38    /// If `true`, hints that the tool does not modify any state and only reads data.
39    #[serde(skip_serializing_if = "Option::is_none")]
40    #[serde(rename = "readOnlyHint")]
41    pub read_only_hint: Option<bool>,
42    /// A map for any other custom annotations not defined in the specification.
43    #[serde(flatten)]
44    pub custom: HashMap<String, serde_json::Value>,
45}
46
47/// Represents a tool that can be executed by an MCP server, as per the MCP 2025-06-18 specification.
48///
49/// A `Tool` definition includes its programmatic name, a human-readable description,
50/// and JSON schemas for its inputs and outputs.
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct Tool {
53    /// The programmatic name of the tool, used to identify it in `CallToolRequest`.
54    pub name: String,
55
56    /// An optional, user-friendly title for the tool. Display name precedence is: `title`, `annotations.title`, then `name`.
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub title: Option<String>,
59
60    /// A human-readable description of what the tool does, which can be used by clients or LLMs.
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub description: Option<String>,
63
64    /// The JSON Schema object defining the parameters the tool accepts.
65    #[serde(rename = "inputSchema")]
66    pub input_schema: ToolInputSchema,
67
68    /// An optional JSON Schema object defining the structure of the tool's successful output.
69    #[serde(rename = "outputSchema", skip_serializing_if = "Option::is_none")]
70    pub output_schema: Option<ToolOutputSchema>,
71
72    /// Optional, additional metadata providing hints about the tool's behavior.
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub annotations: Option<ToolAnnotations>,
75
76    /// A general-purpose metadata field for custom data.
77    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
78    pub meta: Option<HashMap<String, serde_json::Value>>,
79}
80
81impl Default for Tool {
82    fn default() -> Self {
83        Self {
84            name: "unnamed_tool".to_string(), // Must have a valid name for MCP compliance
85            title: None,
86            description: None,
87            input_schema: ToolInputSchema::default(),
88            output_schema: None,
89            annotations: None,
90            meta: None,
91        }
92    }
93}
94
95impl Tool {
96    /// Creates a new `Tool` with a given name.
97    ///
98    /// # Panics
99    /// Panics if the name is empty or contains only whitespace.
100    pub fn new(name: impl Into<String>) -> Self {
101        let name = name.into();
102        assert!(!name.trim().is_empty(), "Tool name cannot be empty");
103        Self {
104            name,
105            title: None,
106            description: None,
107            input_schema: ToolInputSchema::default(),
108            output_schema: None,
109            annotations: None,
110            meta: None,
111        }
112    }
113
114    /// Creates a new `Tool` with a name and a description.
115    ///
116    /// # Panics
117    /// Panics if the name is empty or contains only whitespace.
118    pub fn with_description(name: impl Into<String>, description: impl Into<String>) -> Self {
119        let name = name.into();
120        assert!(!name.trim().is_empty(), "Tool name cannot be empty");
121        Self {
122            name,
123            title: None,
124            description: Some(description.into()),
125            input_schema: ToolInputSchema::default(),
126            output_schema: None,
127            annotations: None,
128            meta: None,
129        }
130    }
131
132    /// Sets the input schema for this tool.
133    ///
134    /// # Example
135    /// ```
136    /// # use turbomcp_protocol::types::{Tool, ToolInputSchema};
137    /// let schema = ToolInputSchema::empty();
138    /// let tool = Tool::new("my_tool").with_input_schema(schema);
139    /// ```
140    pub fn with_input_schema(mut self, schema: ToolInputSchema) -> Self {
141        self.input_schema = schema;
142        self
143    }
144
145    /// Sets the output schema for this tool.
146    pub fn with_output_schema(mut self, schema: ToolOutputSchema) -> Self {
147        self.output_schema = Some(schema);
148        self
149    }
150
151    /// Sets the user-friendly title for this tool.
152    pub fn with_title(mut self, title: impl Into<String>) -> Self {
153        self.title = Some(title.into());
154        self
155    }
156
157    /// Sets the annotations for this tool.
158    pub fn with_annotations(mut self, annotations: ToolAnnotations) -> Self {
159        self.annotations = Some(annotations);
160        self
161    }
162}
163
164/// Defines the structure of the arguments a tool accepts, as a JSON Schema object.
165#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct ToolInputSchema {
167    /// The type of the schema, which must be "object" for tool inputs.
168    #[serde(rename = "type")]
169    pub schema_type: String,
170    /// A map defining the properties (parameters) the tool accepts.
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub properties: Option<HashMap<String, serde_json::Value>>,
173    /// A list of property names that are required.
174    #[serde(skip_serializing_if = "Option::is_none")]
175    pub required: Option<Vec<String>>,
176    /// Whether additional, unspecified properties are allowed.
177    #[serde(
178        rename = "additionalProperties",
179        skip_serializing_if = "Option::is_none"
180    )]
181    pub additional_properties: Option<bool>,
182}
183
184impl Default for ToolInputSchema {
185    /// Creates a default `ToolInputSchema` that accepts an empty object.
186    fn default() -> Self {
187        Self {
188            schema_type: "object".to_string(),
189            properties: None,
190            required: None,
191            additional_properties: None,
192        }
193    }
194}
195
196impl ToolInputSchema {
197    /// Creates a new, empty input schema that accepts no parameters.
198    pub fn empty() -> Self {
199        Self::default()
200    }
201
202    /// Creates a new schema with a given set of properties.
203    pub fn with_properties(properties: HashMap<String, serde_json::Value>) -> Self {
204        Self {
205            schema_type: "object".to_string(),
206            properties: Some(properties),
207            required: None,
208            additional_properties: None,
209        }
210    }
211
212    /// Creates a new schema with a given set of properties and a list of required properties.
213    pub fn with_required_properties(
214        properties: HashMap<String, serde_json::Value>,
215        required: Vec<String>,
216    ) -> Self {
217        Self {
218            schema_type: "object".to_string(),
219            properties: Some(properties),
220            required: Some(required),
221            additional_properties: Some(false),
222        }
223    }
224
225    /// Adds a property to the schema using a builder pattern.
226    ///
227    /// # Example
228    /// ```
229    /// # use turbomcp_protocol::types::ToolInputSchema;
230    /// # use serde_json::json;
231    /// let schema = ToolInputSchema::empty()
232    ///     .add_property("name".to_string(), json!({ "type": "string" }));
233    /// ```
234    pub fn add_property(mut self, name: String, property: serde_json::Value) -> Self {
235        self.properties
236            .get_or_insert_with(HashMap::new)
237            .insert(name, property);
238        self
239    }
240
241    /// Marks a property as required using a builder pattern.
242    ///
243    /// # Example
244    /// ```
245    /// # use turbomcp_protocol::types::ToolInputSchema;
246    /// # use serde_json::json;
247    /// let schema = ToolInputSchema::empty()
248    ///     .add_property("name".to_string(), json!({ "type": "string" }))
249    ///     .require_property("name".to_string());
250    /// ```
251    pub fn require_property(mut self, name: String) -> Self {
252        let required = self.required.get_or_insert_with(Vec::new);
253        if !required.contains(&name) {
254            required.push(name);
255        }
256        self
257    }
258}
259
260/// Defines the structure of a tool's successful output, as a JSON Schema object.
261#[derive(Debug, Clone, Serialize, Deserialize)]
262pub struct ToolOutputSchema {
263    /// The type of the schema, which must be "object" for tool outputs.
264    #[serde(rename = "type")]
265    pub schema_type: String,
266    /// A map defining the properties of the output object.
267    #[serde(skip_serializing_if = "Option::is_none")]
268    pub properties: Option<HashMap<String, serde_json::Value>>,
269    /// A list of property names in the output that are required.
270    #[serde(skip_serializing_if = "Option::is_none")]
271    pub required: Option<Vec<String>>,
272    /// Whether additional, unspecified properties are allowed in the output.
273    #[serde(
274        rename = "additionalProperties",
275        skip_serializing_if = "Option::is_none"
276    )]
277    pub additional_properties: Option<bool>,
278}
279
280/// A request to list the available tools on a server.
281#[derive(Debug, Clone, Serialize, Deserialize, Default)]
282pub struct ListToolsRequest {
283    /// An optional cursor for pagination. If provided, the server should return
284    /// the next page of results starting after this cursor.
285    #[serde(skip_serializing_if = "Option::is_none")]
286    pub cursor: Option<Cursor>,
287    /// Optional metadata for the request.
288    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
289    pub _meta: Option<serde_json::Value>,
290}
291
292/// The result of a `ListToolsRequest`.
293#[derive(Debug, Clone, Serialize, Deserialize)]
294pub struct ListToolsResult {
295    /// The list of available tools for the current page.
296    pub tools: Vec<Tool>,
297    /// An optional continuation token for retrieving the next page of results.
298    /// If `None`, there are no more results.
299    #[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
300    pub next_cursor: Option<Cursor>,
301    /// Optional metadata for the result.
302    #[serde(skip_serializing_if = "Option::is_none")]
303    pub _meta: Option<serde_json::Value>,
304}
305
306/// A request to execute a specific tool.
307#[derive(Debug, Clone, Serialize, Deserialize)]
308pub struct CallToolRequest {
309    /// The programmatic name of the tool to call.
310    pub name: String,
311    /// The arguments to pass to the tool, conforming to its `input_schema`.
312    #[serde(skip_serializing_if = "Option::is_none")]
313    pub arguments: Option<HashMap<String, serde_json::Value>>,
314    /// Optional metadata for the request.
315    #[serde(skip_serializing_if = "Option::is_none")]
316    pub _meta: Option<serde_json::Value>,
317}
318
319/// The result of a `CallToolRequest`.
320#[derive(Debug, Clone, Serialize, Deserialize)]
321pub struct CallToolResult {
322    /// The output of the tool, typically as a series of text or other content blocks. This is required.
323    pub content: Vec<ContentBlock>,
324    /// An optional boolean indicating whether the tool execution resulted in an error.
325    #[serde(rename = "isError", skip_serializing_if = "Option::is_none")]
326    pub is_error: Option<bool>,
327    /// Optional structured output from the tool, conforming to its `output_schema`.
328    #[serde(rename = "structuredContent", skip_serializing_if = "Option::is_none")]
329    pub structured_content: Option<serde_json::Value>,
330    /// Optional metadata for the result.
331    #[serde(skip_serializing_if = "Option::is_none")]
332    pub _meta: Option<serde_json::Value>,
333}