1use crate::error::Result;
6use async_trait::async_trait;
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9use std::collections::HashMap;
10use uuid::Uuid;
11
12#[async_trait]
14pub trait ToolHandler: Send + Sync {
15 async fn call(&self, arguments: HashMap<String, Value>) -> Result<ToolResult>;
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct Tool {
22 pub name: String,
24
25 #[serde(skip_serializing_if = "Option::is_none")]
27 pub description: Option<String>,
28
29 #[serde(rename = "inputSchema")]
31 pub input_schema: Value,
32
33 #[serde(skip_serializing_if = "Option::is_none")]
35 pub server_id: Option<Uuid>,
36
37 #[serde(skip_serializing_if = "Option::is_none")]
39 pub server_name: Option<String>,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct ToolInput {
45 pub name: String,
47
48 #[serde(default)]
50 pub arguments: HashMap<String, Value>,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct ToolResult {
56 pub content: Vec<ToolResultContent>,
58
59 #[serde(skip_serializing_if = "Option::is_none", rename = "isError")]
61 pub is_error: Option<bool>,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66#[serde(tag = "type", rename_all = "snake_case")]
67pub enum ToolResultContent {
68 Text {
70 text: String,
72 },
73 Image {
75 data: String,
77 #[serde(rename = "mimeType")]
79 mime_type: String,
80 },
81 Resource {
83 uri: String,
85 #[serde(skip_serializing_if = "Option::is_none", rename = "mimeType")]
87 mime_type: Option<String>,
88 },
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct ToolCapability {
94 #[serde(default)]
96 pub streaming: bool,
97
98 #[serde(default)]
100 pub cancellable: bool,
101
102 #[serde(skip_serializing_if = "Option::is_none")]
104 pub estimated_duration_ms: Option<u64>,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct ResourceTemplate {
110 #[serde(rename = "uriTemplate")]
112 pub uri_template: String,
113
114 pub name: String,
116
117 #[serde(skip_serializing_if = "Option::is_none")]
119 pub description: Option<String>,
120
121 #[serde(skip_serializing_if = "Option::is_none", rename = "mimeType")]
123 pub mime_type: Option<String>,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct Prompt {
129 pub name: String,
131
132 #[serde(skip_serializing_if = "Option::is_none")]
134 pub description: Option<String>,
135
136 #[serde(skip_serializing_if = "Option::is_none")]
138 pub arguments: Option<Vec<PromptArgument>>,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct PromptArgument {
144 pub name: String,
146
147 #[serde(skip_serializing_if = "Option::is_none")]
149 pub description: Option<String>,
150
151 #[serde(default)]
153 pub required: bool,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct GetPromptRequest {
159 pub name: String,
161
162 #[serde(default)]
164 pub arguments: HashMap<String, String>,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct GetPromptResult {
170 #[serde(skip_serializing_if = "Option::is_none")]
172 pub description: Option<String>,
173
174 pub messages: Vec<PromptMessage>,
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct PromptMessage {
181 pub role: String,
183
184 pub content: ToolResultContent,
186}
187
188impl Tool {
189 pub fn simple(name: impl Into<String>, description: impl Into<String>) -> Self {
191 Self {
192 name: name.into(),
193 description: Some(description.into()),
194 input_schema: serde_json::json!({
195 "type": "object",
196 "properties": {},
197 "required": []
198 }),
199 server_id: None,
200 server_name: None,
201 }
202 }
203
204 pub fn with_schema(
206 name: impl Into<String>,
207 description: impl Into<String>,
208 schema: Value,
209 ) -> Self {
210 Self {
211 name: name.into(),
212 description: Some(description.into()),
213 input_schema: schema,
214 server_id: None,
215 server_name: None,
216 }
217 }
218}
219
220impl ToolResult {
221 pub fn text(text: impl Into<String>) -> Self {
223 Self {
224 content: vec![ToolResultContent::Text { text: text.into() }],
225 is_error: None,
226 }
227 }
228
229 pub fn error(message: impl Into<String>) -> Self {
231 Self {
232 content: vec![ToolResultContent::Text {
233 text: message.into(),
234 }],
235 is_error: Some(true),
236 }
237 }
238
239 pub fn with_content(content: Vec<ToolResultContent>) -> Self {
241 Self {
242 content,
243 is_error: None,
244 }
245 }
246}
247
248impl ToolResultContent {
249 pub fn text(text: impl Into<String>) -> Self {
251 Self::Text { text: text.into() }
252 }
253
254 pub fn image(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
256 Self::Image {
257 data: data.into(),
258 mime_type: mime_type.into(),
259 }
260 }
261
262 pub fn resource(uri: impl Into<String>) -> Self {
264 Self::Resource {
265 uri: uri.into(),
266 mime_type: None,
267 }
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn test_simple_tool() {
277 let tool = Tool::simple("test_tool", "A test tool");
278 assert_eq!(tool.name, "test_tool");
279 assert!(tool.description.is_some());
280 }
281
282 #[test]
283 fn test_tool_result() {
284 let result = ToolResult::text("Success");
285 assert_eq!(result.content.len(), 1);
286 assert!(result.is_error.is_none());
287 }
288
289 #[test]
290 fn test_error_result() {
291 let result = ToolResult::error("Failed");
292 assert_eq!(result.is_error, Some(true));
293 }
294
295 #[test]
296 fn test_tool_serialization() {
297 let tool = Tool::simple("test", "description");
298 let json = serde_json::to_string(&tool).unwrap();
299 assert!(json.contains("\"name\":\"test\""));
300 assert!(json.contains("inputSchema"));
301 }
302}