mcpx/protocol/
tools.rs

1//! Protocol types and definitions for MCP tools
2//!
3//! This module contains types for working with tools in the MCP protocol,
4//! including tool definitions, arguments, and call results.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9use crate::protocol::{
10    Cursor,
11    Request,
12    Notification,
13    sampling::{TextContent, ImageContent, AudioContent},
14    prompts::EmbeddedResource,
15};
16use crate::protocol::messages::MessageResult;
17
18/// Definition for a tool the client can call.
19#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
20pub struct Tool {
21    /// The name of the tool.
22    pub name: String,
23    /// A human-readable description of the tool.
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub description: Option<String>,
26    /// A JSON Schema object defining the expected parameters for the tool.
27    pub input_schema: ToolInputSchema,
28    /// Optional additional tool information.
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub annotations: Option<ToolAnnotations>,
31}
32
33/// Input schema for a tool, defining the expected parameters.
34#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
35pub struct ToolInputSchema {
36    /// The schema type (always "object")
37    pub r#type: String,
38    /// Properties the tool accepts
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub properties: Option<HashMap<String, serde_json::Value>>,
41    /// Required properties
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub required: Option<Vec<String>>,
44}
45
46/// Additional properties describing a Tool to clients.
47///
48/// NOTE: all properties in ToolAnnotations are **hints**.
49/// They are not guaranteed to provide a faithful description of
50/// tool behavior (including descriptive properties like `title`).
51///
52/// Clients should never make tool use decisions based on ToolAnnotations
53/// received from untrusted servers.
54#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
55pub struct ToolAnnotations {
56    /// A human-readable title for the tool.
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub title: Option<String>,
59    /// If true, the tool does not modify its environment.
60    ///
61    /// Default: false
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub read_only_hint: Option<bool>,
64    /// If true, the tool may perform destructive updates to its environment.
65    /// If false, the tool performs only additive updates.
66    ///
67    /// (This property is meaningful only when `read_only_hint == false`)
68    ///
69    /// Default: true
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub destructive_hint: Option<bool>,
72    /// If true, calling the tool repeatedly with the same arguments
73    /// will have no additional effect on the its environment.
74    ///
75    /// (This property is meaningful only when `read_only_hint == false`)
76    ///
77    /// Default: false
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub idempotent_hint: Option<bool>,
80    /// If true, this tool may interact with an "open world" of external
81    /// entities. If false, the tool's domain of interaction is closed.
82    /// For example, the world of a web search tool is open, whereas that
83    /// of a memory tool is not.
84    ///
85    /// Default: true
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub open_world_hint: Option<bool>,
88}
89
90/// Sent from the client to request a list of tools the server has.
91#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
92pub struct ListToolsRequest {
93    /// The method name
94    pub method: String,
95    /// The request parameters
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub params: Option<ListToolsParams>,
98}
99
100/// Parameters for the list tools request
101#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
102pub struct ListToolsParams {
103    /// An opaque token representing the current pagination position.
104    /// If provided, the server should return results starting after this cursor.
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub cursor: Option<Cursor>,
107}
108
109/// The server's response to a tools/list request from the client.
110#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
111pub struct ListToolsResult {
112    /// Available tools
113    pub tools: Vec<Tool>,
114    /// An opaque token representing the pagination position after the last returned result.
115    /// If present, there may be more results available.
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub next_cursor: Option<Cursor>,
118    /// Optional metadata
119    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
120    pub meta: Option<serde_json::Value>,
121}
122
123/// Used by the client to invoke a tool provided by the server.
124#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
125pub struct CallToolRequest {
126    /// The method name
127    pub method: String,
128    /// The request parameters
129    pub params: CallToolParams,
130}
131
132/// Parameters for the call tool request
133#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
134pub struct CallToolParams {
135    /// The name of the tool to call
136    pub name: String,
137    /// Arguments to pass to the tool
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub arguments: Option<serde_json::Value>,
140}
141
142/// The server's response to a tool call.
143///
144/// Any errors that originate from the tool SHOULD be reported inside the result
145/// object, with `isError` set to true, _not_ as an MCP protocol-level error
146/// response. Otherwise, the LLM would not be able to see that an error occurred
147/// and self-correct.
148///
149/// However, any errors in _finding_ the tool, an error indicating that the
150/// server does not support tool calls, or any other exceptional conditions,
151/// should be reported as an MCP error response.
152#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
153pub struct CallToolResult {
154    /// The content returned from the tool call
155    pub content: Vec<ToolCallContent>,
156    /// Whether the tool call ended in an error.
157    ///
158    /// If not set, this is assumed to be false (the call was successful).
159    #[serde(skip_serializing_if = "Option::is_none")]
160    pub is_error: Option<bool>,
161    /// Optional metadata
162    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
163    pub meta: Option<serde_json::Value>,
164}
165
166impl MessageResult for CallToolResult {}
167
168/// Content that can be returned from a tool call
169#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
170#[serde(untagged)]
171pub enum ToolCallContent {
172    /// Text content
173    Text(TextContent),
174    /// Image content
175    Image(ImageContent),
176    /// Audio content
177    Audio(AudioContent),
178    /// Embedded resource
179    Resource(EmbeddedResource),
180}
181
182/// An optional notification from the server to the client, informing it that the list of tools
183/// it offers has changed. This may be issued by servers without any previous subscription from the client.
184#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
185pub struct ToolListChangedNotification {
186    /// The method name
187    pub method: String,
188    /// The notification parameters
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub params: Option<serde_json::Value>,
191}
192
193// Implementation for Request trait
194impl Request for ListToolsRequest {
195    const METHOD: &'static str = "tools/list";
196
197    fn method(&self) -> &str {
198        &self.method
199    }
200
201    fn params(&self) -> Option<&serde_json::Value> {
202        None
203    }
204}
205
206impl Request for CallToolRequest {
207    const METHOD: &'static str = "tools/call";
208
209    fn method(&self) -> &str {
210        &self.method
211    }
212
213    fn params(&self) -> Option<&serde_json::Value> {
214        None
215    }
216}
217
218// Implementation for Notification trait
219impl Notification for ToolListChangedNotification {
220    const METHOD: &'static str = "notifications/tools/list_changed";
221
222    fn method(&self) -> &str {
223        &self.method
224    }
225
226    fn params(&self) -> Option<&serde_json::Value> {
227        None
228    }
229}
230
231// Implementation for MessageResult trait
232impl MessageResult for ListToolsResult {}
233
234// Helper constructors
235impl ListToolsRequest {
236    /// Create a new list tools request
237    pub fn new() -> Self {
238        Self {
239            method: Self::METHOD.to_string(),
240            params: None,
241        }
242    }
243
244    /// Create a new list tools request with pagination
245    pub fn with_cursor(cursor: impl Into<String>) -> Self {
246        Self {
247            method: Self::METHOD.to_string(),
248            params: Some(ListToolsParams {
249                cursor: Some(cursor.into()),
250            }),
251        }
252    }
253}
254
255impl CallToolRequest {
256    /// Create a new call tool request
257    pub fn new(name: impl Into<String>) -> Self {
258        Self {
259            method: Self::METHOD.to_string(),
260            params: CallToolParams {
261                name: name.into(),
262                arguments: None,
263            },
264        }
265    }
266
267    /// Create a new call tool request with arguments
268    pub fn with_arguments(
269        name: impl Into<String>,
270        arguments: serde_json::Value,
271    ) -> Self {
272        Self {
273            method: Self::METHOD.to_string(),
274            params: CallToolParams {
275                name: name.into(),
276                arguments: Some(arguments),
277            },
278        }
279    }
280}
281
282impl ToolListChangedNotification {
283    /// Create a new tool list changed notification
284    pub fn new() -> Self {
285        Self {
286            method: Self::METHOD.to_string(),
287            params: None,
288        }
289    }
290}
291
292impl Tool {
293    /// Create a new tool with basic properties
294    pub fn new(
295        name: impl Into<String>,
296        description: impl Into<String>,
297    ) -> Self {
298        Self {
299            name: name.into(),
300            description: Some(description.into()),
301            input_schema: ToolInputSchema {
302                r#type: "object".to_string(),
303                properties: None,
304                required: None,
305            },
306            annotations: None,
307        }
308    }
309
310    /// Set the input schema for the tool
311    pub fn with_schema(mut self, properties: HashMap<String, serde_json::Value>, required: Vec<String>) -> Self {
312        self.input_schema = ToolInputSchema {
313            r#type: "object".to_string(),
314            properties: Some(properties),
315            required: Some(required),
316        };
317        self
318    }
319
320    /// Add annotations to the tool
321    pub fn with_annotations(mut self, annotations: ToolAnnotations) -> Self {
322        self.annotations = Some(annotations);
323        self
324    }
325}
326
327impl ToolAnnotations {
328    /// Create new tool annotations
329    pub fn new() -> Self {
330        Self {
331            title: None,
332            read_only_hint: None,
333            destructive_hint: None,
334            idempotent_hint: None,
335            open_world_hint: None,
336        }
337    }
338
339    /// Set the title
340    pub fn with_title(mut self, title: impl Into<String>) -> Self {
341        self.title = Some(title.into());
342        self
343    }
344
345    /// Mark the tool as read-only
346    pub fn read_only(mut self) -> Self {
347        self.read_only_hint = Some(true);
348        self
349    }
350
351    /// Set whether the tool is destructive
352    pub fn destructive(mut self, is_destructive: bool) -> Self {
353        self.destructive_hint = Some(is_destructive);
354        self
355    }
356
357    /// Set whether the tool is idempotent
358    pub fn idempotent(mut self, is_idempotent: bool) -> Self {
359        self.idempotent_hint = Some(is_idempotent);
360        self
361    }
362
363    /// Set whether the tool interacts with an open world
364    pub fn open_world(mut self, is_open_world: bool) -> Self {
365        self.open_world_hint = Some(is_open_world);
366        self
367    }
368}