Skip to main content

mcp_kit/types/
tool.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4use crate::types::content::Content;
5
6/// A tool that the server exposes to clients
7#[derive(Debug, Clone, Serialize, Deserialize)]
8#[serde(rename_all = "camelCase")]
9pub struct Tool {
10    pub name: String,
11    #[serde(skip_serializing_if = "Option::is_none")]
12    pub description: Option<String>,
13    /// JSON Schema object describing the tool's input parameters
14    pub input_schema: Value,
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub annotations: Option<ToolAnnotations>,
17}
18
19impl Tool {
20    pub fn new(
21        name: impl Into<String>,
22        description: impl Into<String>,
23        input_schema: Value,
24    ) -> Self {
25        Self {
26            name: name.into(),
27            description: Some(description.into()),
28            input_schema,
29            annotations: None,
30        }
31    }
32
33    /// Create a tool with an empty object schema (no parameters)
34    pub fn no_params(name: impl Into<String>, description: impl Into<String>) -> Self {
35        Self::new(
36            name,
37            description,
38            serde_json::json!({ "type": "object", "properties": {} }),
39        )
40    }
41}
42
43/// Behavioural annotations for tools
44#[derive(Debug, Clone, Default, Serialize, Deserialize)]
45#[serde(rename_all = "camelCase")]
46pub struct ToolAnnotations {
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub title: Option<String>,
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub read_only_hint: Option<bool>,
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub destructive_hint: Option<bool>,
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub idempotent_hint: Option<bool>,
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub open_world_hint: Option<bool>,
57}
58
59/// The result returned from a tool call
60#[derive(Debug, Clone, Serialize, Deserialize)]
61#[serde(rename_all = "camelCase")]
62pub struct CallToolResult {
63    pub content: Vec<Content>,
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub is_error: Option<bool>,
66}
67
68impl CallToolResult {
69    pub fn text(text: impl Into<String>) -> Self {
70        Self {
71            content: vec![Content::text(text)],
72            is_error: None,
73        }
74    }
75
76    pub fn success(content: Vec<Content>) -> Self {
77        Self {
78            content,
79            is_error: None,
80        }
81    }
82
83    pub fn error(message: impl Into<String>) -> Self {
84        Self {
85            content: vec![Content::text(message)],
86            is_error: Some(true),
87        }
88    }
89
90    pub fn with_content(mut self, content: impl Into<Content>) -> Self {
91        self.content.push(content.into());
92        self
93    }
94
95    pub fn is_error(&self) -> bool {
96        self.is_error.unwrap_or(false)
97    }
98}
99
100impl From<String> for CallToolResult {
101    fn from(s: String) -> Self {
102        Self::text(s)
103    }
104}
105
106impl From<&str> for CallToolResult {
107    fn from(s: &str) -> Self {
108        Self::text(s)
109    }
110}