1use crate::meta::Cursor;
6use crate::schema::JsonSchema;
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9use std::collections::HashMap;
10
11pub trait HasBaseMetadata {
17 fn name(&self) -> &str;
19
20 fn title(&self) -> Option<&str> { None }
22}
23
24pub trait HasDescription {
26 fn description(&self) -> Option<&str> { None }
27}
28
29pub trait HasInputSchema {
31 fn input_schema(&self) -> &ToolSchema;
32}
33
34pub trait HasOutputSchema {
36 fn output_schema(&self) -> Option<&ToolSchema> { None }
37}
38
39pub trait HasAnnotations {
41 fn annotations(&self) -> Option<&ToolAnnotations> { None }
42}
43
44pub trait HasToolMeta {
46 fn tool_meta(&self) -> Option<&HashMap<String, Value>> { None }
47}
48
49pub trait ToolDefinition:
51 HasBaseMetadata + HasDescription + HasInputSchema + HasOutputSchema + HasAnnotations + HasToolMeta + Send +
58 Sync
59{
60 fn display_name(&self) -> &str {
62 if let Some(title) = self.title() {
63 title
64 } else if let Some(annotations) = self.annotations() {
65 if let Some(title) = &annotations.title {
66 title
67 } else {
68 self.name()
69 }
70 } else {
71 self.name()
72 }
73 }
74
75 fn to_tool(&self) -> Tool {
77 Tool {
78 name: self.name().to_string(),
79 title: self.title().map(String::from),
80 description: self.description().map(String::from),
81 input_schema: self.input_schema().clone(),
82 output_schema: self.output_schema().cloned(),
83 annotations: self.annotations().cloned(),
84 meta: self.tool_meta().cloned(),
85 }
86 }
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
94#[serde(rename_all = "camelCase")]
95pub struct ToolAnnotations {
96 #[serde(skip_serializing_if = "Option::is_none")]
98 pub title: Option<String>,
99 #[serde(rename = "readOnlyHint", skip_serializing_if = "Option::is_none")]
101 pub read_only_hint: Option<bool>,
102 #[serde(rename = "destructiveHint", skip_serializing_if = "Option::is_none")]
106 pub destructive_hint: Option<bool>,
107 #[serde(rename = "idempotentHint", skip_serializing_if = "Option::is_none")]
111 pub idempotent_hint: Option<bool>,
112 #[serde(rename = "openWorldHint", skip_serializing_if = "Option::is_none")]
117 pub open_world_hint: Option<bool>,
118}
119
120impl ToolAnnotations {
121 pub fn new() -> Self {
122 Self {
123 title: None,
124 read_only_hint: None,
125 destructive_hint: None,
126 idempotent_hint: None,
127 open_world_hint: None,
128 }
129 }
130
131 pub fn with_title(mut self, title: impl Into<String>) -> Self {
132 self.title = Some(title.into());
133 self
134 }
135
136 pub fn with_read_only_hint(mut self, read_only: bool) -> Self {
137 self.read_only_hint = Some(read_only);
138 self
139 }
140
141 pub fn with_destructive_hint(mut self, destructive: bool) -> Self {
142 self.destructive_hint = Some(destructive);
143 self
144 }
145
146 pub fn with_idempotent_hint(mut self, idempotent: bool) -> Self {
147 self.idempotent_hint = Some(idempotent);
148 self
149 }
150
151 pub fn with_open_world_hint(mut self, open_world: bool) -> Self {
152 self.open_world_hint = Some(open_world);
153 self
154 }
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct ToolSchema {
165 #[serde(rename = "type")]
167 pub schema_type: String,
168 #[serde(skip_serializing_if = "Option::is_none")]
170 pub properties: Option<HashMap<String, JsonSchema>>,
171 #[serde(skip_serializing_if = "Option::is_none")]
173 pub required: Option<Vec<String>>,
174 #[serde(flatten)]
176 pub additional: HashMap<String, Value>,
177}
178
179impl ToolSchema {
180 pub fn object() -> Self {
181 Self {
182 schema_type: "object".to_string(),
183 properties: None,
184 required: None,
185 additional: HashMap::new(),
186 }
187 }
188
189 pub fn with_properties(mut self, properties: HashMap<String, JsonSchema>) -> Self {
190 self.properties = Some(properties);
191 self
192 }
193
194 pub fn with_required(mut self, required: Vec<String>) -> Self {
195 self.required = Some(required);
196 self
197 }
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize)]
202#[serde(rename_all = "camelCase")]
203pub struct Tool {
204 pub name: String,
206 #[serde(skip_serializing_if = "Option::is_none")]
209 pub title: Option<String>,
210 #[serde(skip_serializing_if = "Option::is_none")]
212 pub description: Option<String>,
213 pub input_schema: ToolSchema,
215 #[serde(skip_serializing_if = "Option::is_none")]
217 pub output_schema: Option<ToolSchema>,
218 #[serde(skip_serializing_if = "Option::is_none")]
220 pub annotations: Option<ToolAnnotations>,
221
222 #[serde(
223 default,
224 skip_serializing_if = "Option::is_none",
225 alias = "_meta",
226 rename = "_meta"
227 )]
228 pub meta: Option<HashMap<String, Value>>,
229}
230
231impl Tool {
232 pub fn new(name: impl Into<String>, input_schema: ToolSchema) -> Self {
233 Self {
234 name: name.into(),
235 title: None,
236 description: None,
237 input_schema,
238 output_schema: None,
239 annotations: None,
240 meta: None,
241 }
242 }
243
244 pub fn with_title(mut self, title: impl Into<String>) -> Self {
245 self.title = Some(title.into());
246 self
247 }
248
249 pub fn with_description(mut self, description: impl Into<String>) -> Self {
250 self.description = Some(description.into());
251 self
252 }
253
254 pub fn with_output_schema(mut self, output_schema: ToolSchema) -> Self {
255 self.output_schema = Some(output_schema);
256 self
257 }
258
259 pub fn with_annotations(mut self, annotations: ToolAnnotations) -> Self {
260 self.annotations = Some(annotations);
261 self
262 }
263
264 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
265 self.meta = Some(meta);
266 self
267 }
268}
269
270impl HasBaseMetadata for Tool {
275 fn name(&self) -> &str { &self.name }
276 fn title(&self) -> Option<&str> { self.title.as_deref() }
277}
278
279impl HasDescription for Tool {
280 fn description(&self) -> Option<&str> { self.description.as_deref() }
281}
282
283impl HasInputSchema for Tool {
284 fn input_schema(&self) -> &ToolSchema { &self.input_schema }
285}
286
287impl HasOutputSchema for Tool {
288 fn output_schema(&self) -> Option<&ToolSchema> { self.output_schema.as_ref() }
289}
290
291impl HasAnnotations for Tool {
292 fn annotations(&self) -> Option<&ToolAnnotations> { self.annotations.as_ref() }
293}
294
295impl HasToolMeta for Tool {
296 fn tool_meta(&self) -> Option<&HashMap<String, Value>> { self.meta.as_ref() }
297}
298
299impl<T> ToolDefinition for T
301where
302 T: HasBaseMetadata + HasDescription + HasInputSchema + HasOutputSchema + HasAnnotations + HasToolMeta + Send + Sync,
303{
304 }
306
307#[derive(Debug, Clone, Serialize, Deserialize)]
309#[serde(rename_all = "camelCase")]
310pub struct ListToolsParams {
311 #[serde(skip_serializing_if = "Option::is_none")]
313 pub cursor: Option<Cursor>,
314 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
316 pub meta: Option<HashMap<String, Value>>,
317}
318
319impl ListToolsParams {
320 pub fn new() -> Self {
321 Self {
322 cursor: None,
323 meta: None,
324 }
325 }
326
327 pub fn with_cursor(mut self, cursor: Cursor) -> Self {
328 self.cursor = Some(cursor);
329 self
330 }
331
332 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
333 self.meta = Some(meta);
334 self
335 }
336}
337
338impl Default for ListToolsParams {
339 fn default() -> Self {
340 Self::new()
341 }
342}
343
344#[derive(Debug, Clone, Serialize, Deserialize)]
346#[serde(rename_all = "camelCase")]
347pub struct ListToolsRequest {
348 pub method: String,
350 pub params: ListToolsParams,
352}
353
354impl ListToolsRequest {
355 pub fn new() -> Self {
356 Self {
357 method: "tools/list".to_string(),
358 params: ListToolsParams::new(),
359 }
360 }
361
362 pub fn with_cursor(mut self, cursor: Cursor) -> Self {
363 self.params = self.params.with_cursor(cursor);
364 self
365 }
366
367 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
368 self.params = self.params.with_meta(meta);
369 self
370 }
371}
372
373#[derive(Debug, Clone, Serialize, Deserialize)]
375#[serde(rename_all = "camelCase")]
376pub struct ListToolsResult {
377 pub tools: Vec<Tool>,
379 #[serde(skip_serializing_if = "Option::is_none")]
381 pub next_cursor: Option<Cursor>,
382 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
384 pub meta: Option<HashMap<String, Value>>,
385}
386
387impl ListToolsResult {
388 pub fn new(tools: Vec<Tool>) -> Self {
389 Self {
390 tools,
391 next_cursor: None,
392 meta: None,
393 }
394 }
395
396 pub fn with_next_cursor(mut self, cursor: Cursor) -> Self {
397 self.next_cursor = Some(cursor);
398 self
399 }
400
401 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
402 self.meta = Some(meta);
403 self
404 }
405}
406
407#[derive(Debug, Clone, Serialize, Deserialize)]
409#[serde(rename_all = "camelCase")]
410pub struct CallToolParams {
411 pub name: String,
413 #[serde(skip_serializing_if = "Option::is_none")]
415 pub arguments: Option<HashMap<String, Value>>,
416 #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
418 pub meta: Option<HashMap<String, Value>>,
419}
420
421impl CallToolParams {
422 pub fn new(name: impl Into<String>) -> Self {
423 Self {
424 name: name.into(),
425 arguments: None,
426 meta: None,
427 }
428 }
429
430 pub fn with_arguments(mut self, arguments: HashMap<String, Value>) -> Self {
431 self.arguments = Some(arguments);
432 self
433 }
434
435 pub fn with_arguments_value(mut self, arguments: Value) -> Self {
436 if let Value::Object(map) = arguments {
438 self.arguments = Some(map.into_iter().collect());
439 }
440 self
441 }
442
443 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
444 self.meta = Some(meta);
445 self
446 }
447}
448
449#[derive(Debug, Clone, Serialize, Deserialize)]
451#[serde(rename_all = "camelCase")]
452pub struct CallToolRequest {
453 pub method: String,
455 pub params: CallToolParams,
457}
458
459impl CallToolRequest {
460 pub fn new(name: impl Into<String>) -> Self {
461 Self {
462 method: "tools/call".to_string(),
463 params: CallToolParams::new(name),
464 }
465 }
466
467 pub fn with_arguments(mut self, arguments: HashMap<String, Value>) -> Self {
468 self.params = self.params.with_arguments(arguments);
469 self
470 }
471
472 pub fn with_arguments_value(mut self, arguments: Value) -> Self {
473 self.params = self.params.with_arguments_value(arguments);
474 self
475 }
476
477 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
478 self.params = self.params.with_meta(meta);
479 self
480 }
481}
482
483#[derive(Debug, Clone, Serialize, Deserialize)]
485#[serde(tag = "type", rename_all = "lowercase")]
486pub enum ToolResult {
487 Text { text: String },
489 Image {
491 data: String,
492 #[serde(rename = "mimeType")]
493 mime_type: String,
494 },
495 Audio {
497 data: String,
498 #[serde(rename = "mimeType")]
499 mime_type: String,
500 },
501 Resource {
503 resource: Value,
504 #[serde(skip_serializing_if = "Option::is_none")]
505 annotations: Option<Value>,
506 },
507}
508
509impl ToolResult {
510 pub fn text(content: impl Into<String>) -> Self {
511 Self::Text {
512 text: content.into(),
513 }
514 }
515
516 pub fn image(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
517 Self::Image {
518 data: data.into(),
519 mime_type: mime_type.into(),
520 }
521 }
522
523 pub fn audio(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
524 Self::Audio {
525 data: data.into(),
526 mime_type: mime_type.into(),
527 }
528 }
529
530 pub fn resource(resource: Value) -> Self {
531 Self::Resource {
532 resource,
533 annotations: None,
534 }
535 }
536
537 pub fn resource_with_annotations(resource: Value, annotations: Value) -> Self {
538 Self::Resource {
539 resource,
540 annotations: Some(annotations),
541 }
542 }
543}
544
545#[derive(Debug, Clone, Serialize, Deserialize)]
547#[serde(rename_all = "camelCase")]
548pub struct CallToolResult {
549 pub content: Vec<ToolResult>,
551 #[serde(skip_serializing_if = "Option::is_none")]
553 pub is_error: Option<bool>,
554 #[serde(skip_serializing_if = "Option::is_none")]
556 pub structured_content: Option<Value>,
557 #[serde(
559 default,
560 skip_serializing_if = "Option::is_none",
561 alias = "_meta",
562 rename = "_meta"
563 )]
564 pub meta: Option<HashMap<String, Value>>,
565}
566
567impl CallToolResult {
568 pub fn new(content: Vec<ToolResult>) -> Self {
569 Self {
570 content,
571 is_error: None,
572 structured_content: None,
573 meta: None,
574 }
575 }
576
577 pub fn success(content: Vec<ToolResult>) -> Self {
578 Self {
579 content,
580 is_error: Some(false),
581 structured_content: None,
582 meta: None,
583 }
584 }
585
586 pub fn error(content: Vec<ToolResult>) -> Self {
587 Self {
588 content,
589 is_error: Some(true),
590 structured_content: None,
591 meta: None,
592 }
593 }
594
595 pub fn with_error_flag(mut self, is_error: bool) -> Self {
596 self.is_error = Some(is_error);
597 self
598 }
599
600 pub fn with_structured_content(mut self, structured_content: Value) -> Self {
601 self.structured_content = Some(structured_content);
602 self
603 }
604
605 pub fn with_meta(mut self, meta: HashMap<String, Value>) -> Self {
606 self.meta = Some(meta);
607 self
608 }
609
610 pub fn from_result_with_tool<T: serde::Serialize>(
616 result: &T,
617 tool: &dyn ToolDefinition
618 ) -> Result<Self, crate::McpError> {
619 let text_content = serde_json::to_string(result)
620 .map_err(|e| crate::McpError::tool_execution(&format!("Serialization error: {}", e)))?;
621
622 let response = Self::success(vec![ToolResult::text(text_content)]);
623
624 if let Some(_) = tool.output_schema() {
626 let structured = serde_json::to_value(result)
627 .map_err(|e| crate::McpError::tool_execution(&format!("Structured content error: {}", e)))?;
628 Ok(response.with_structured_content(structured))
629 } else {
630 Ok(response)
631 }
632 }
633
634 pub fn from_result_with_schema<T: serde::Serialize>(
636 result: &T,
637 schema: Option<&ToolSchema>
638 ) -> Result<Self, crate::McpError> {
639 let text_content = serde_json::to_string(result)
640 .map_err(|e| crate::McpError::tool_execution(&format!("Serialization error: {}", e)))?;
641
642 let response = Self::success(vec![ToolResult::text(text_content)]);
643
644 if schema.is_some() {
646 let structured = serde_json::to_value(result)
647 .map_err(|e| crate::McpError::tool_execution(&format!("Structured content error: {}", e)))?;
648 Ok(response.with_structured_content(structured))
649 } else {
650 Ok(response)
651 }
652 }
653
654 pub fn from_result_auto<T: serde::Serialize>(
656 result: &T,
657 schema: Option<&ToolSchema>
658 ) -> Result<Self, crate::McpError> {
659 let text_content = serde_json::to_string(result)
660 .map_err(|e| crate::McpError::tool_execution(&format!("Serialization error: {}", e)))?;
661
662 let response = Self::success(vec![ToolResult::text(text_content)]);
663
664 let structured = serde_json::to_value(result)
666 .map_err(|e| crate::McpError::tool_execution(&format!("Structured content error: {}", e)))?;
667
668 let should_add_structured = schema.is_some() || match &structured {
669 Value::Number(_) | Value::Bool(_) => true,
671 Value::Array(_) | Value::Object(_) => true,
673 Value::String(_) => false,
675 Value::Null => false,
676 };
677
678 if should_add_structured {
679 Ok(response.with_structured_content(structured))
680 } else {
681 Ok(response)
682 }
683 }
684
685 pub fn from_json_with_schema(
687 json_result: Value,
688 schema: Option<&ToolSchema>
689 ) -> Self {
690 let text_content = json_result.to_string();
691 let response = Self::success(vec![ToolResult::text(text_content)]);
692
693 if schema.is_some() {
694 response.with_structured_content(json_result)
695 } else {
696 response
697 }
698 }
699}
700
701use crate::traits::*;
704
705impl HasData for CallToolResult {
706 fn data(&self) -> HashMap<String, Value> {
707 let mut data = HashMap::new();
708 data.insert(
709 "content".to_string(),
710 serde_json::to_value(&self.content).unwrap_or(Value::Null),
711 );
712 if let Some(is_error) = self.is_error {
713 data.insert("isError".to_string(), Value::Bool(is_error));
714 }
715 if let Some(ref structured_content) = self.structured_content {
716 data.insert("structuredContent".to_string(), structured_content.clone());
717 }
718 data
719 }
720}
721
722impl HasMeta for CallToolResult {
723 fn meta(&self) -> Option<HashMap<String, Value>> {
724 self.meta.clone()
725 }
726}
727
728impl RpcResult for CallToolResult {}
729
730impl crate::traits::CallToolResult for CallToolResult {
731 fn content(&self) -> &Vec<ToolResult> {
732 &self.content
733 }
734
735 fn is_error(&self) -> Option<bool> {
736 self.is_error
737 }
738
739 fn structured_content(&self) -> Option<&Value> {
740 self.structured_content.as_ref()
741 }
742}
743
744impl Params for ListToolsParams {}
746
747impl HasListToolsParams for ListToolsParams {
748 fn cursor(&self) -> Option<&Cursor> {
749 self.cursor.as_ref()
750 }
751}
752
753impl HasMetaParam for ListToolsParams {
754 fn meta(&self) -> Option<&HashMap<String, Value>> {
755 self.meta.as_ref()
756 }
757}
758
759impl HasMethod for ListToolsRequest {
761 fn method(&self) -> &str {
762 &self.method
763 }
764}
765
766impl HasParams for ListToolsRequest {
767 fn params(&self) -> Option<&dyn Params> {
768 Some(&self.params)
769 }
770}
771
772impl HasData for ListToolsResult {
774 fn data(&self) -> HashMap<String, Value> {
775 let mut data = HashMap::new();
776 data.insert(
777 "tools".to_string(),
778 serde_json::to_value(&self.tools).unwrap_or(Value::Null),
779 );
780 if let Some(ref next_cursor) = self.next_cursor {
781 data.insert(
782 "nextCursor".to_string(),
783 Value::String(next_cursor.as_str().to_string()),
784 );
785 }
786 data
787 }
788}
789
790impl HasMeta for ListToolsResult {
791 fn meta(&self) -> Option<HashMap<String, Value>> {
792 self.meta.clone()
793 }
794}
795
796impl RpcResult for ListToolsResult {}
797
798impl crate::traits::ListToolsResult for ListToolsResult {
799 fn tools(&self) -> &Vec<Tool> {
800 &self.tools
801 }
802
803 fn next_cursor(&self) -> Option<&Cursor> {
804 self.next_cursor.as_ref()
805 }
806}
807
808impl Params for CallToolParams {}
810
811impl HasCallToolParams for CallToolParams {
812 fn name(&self) -> &String {
813 &self.name
814 }
815
816 fn arguments(&self) -> Option<&Value> {
817 self.arguments.as_ref().and_then(|_| None) }
822
823 fn meta(&self) -> Option<&HashMap<String, Value>> {
824 self.meta.as_ref()
825 }
826}
827
828impl HasMethod for CallToolRequest {
830 fn method(&self) -> &str {
831 &self.method
832 }
833}
834
835impl HasParams for CallToolRequest {
836 fn params(&self) -> Option<&dyn Params> {
837 Some(&self.params)
838 }
839}
840
841pub mod builder;
842
843#[cfg(test)]
844mod tests {
845 use super::*;
846 use serde_json::json;
847
848 #[test]
849 fn test_tool_creation() {
850 let schema = ToolSchema::object()
851 .with_properties(HashMap::from([("text".to_string(), JsonSchema::string())]))
852 .with_required(vec!["text".to_string()]);
853
854 let tool = Tool::new("test_tool", schema).with_description("A test tool");
855
856 assert_eq!(tool.name, "test_tool");
857 assert!(tool.description.is_some());
858 assert_eq!(tool.input_schema.schema_type, "object");
859 }
860
861 #[test]
862 fn test_tool_result_creation() {
863 let text_result = ToolResult::text("Hello, world!");
864 let image_result = ToolResult::image("base64data", "image/png");
865 let resource_result = ToolResult::resource(json!({"key": "value"}));
866
867 assert!(matches!(text_result, ToolResult::Text { .. }));
868 assert!(matches!(image_result, ToolResult::Image { .. }));
869 assert!(matches!(resource_result, ToolResult::Resource { .. }));
870 }
871
872 #[test]
873 fn test_call_tool_response() {
874 let response =
875 CallToolResult::success(vec![ToolResult::text("Operation completed successfully")]);
876
877 assert_eq!(response.is_error, Some(false));
878 assert_eq!(response.content.len(), 1);
879 assert!(response.structured_content.is_none());
880 }
881
882 #[test]
883 fn test_call_tool_response_with_structured_content() {
884 let structured_data = serde_json::json!({
885 "result": "success",
886 "value": 42
887 });
888
889 let response =
890 CallToolResult::success(vec![ToolResult::text("Operation completed successfully")])
891 .with_structured_content(structured_data.clone());
892
893 assert_eq!(response.is_error, Some(false));
894 assert_eq!(response.content.len(), 1);
895 assert_eq!(response.structured_content, Some(structured_data));
896 }
897
898 #[test]
899 fn test_serialization() {
900 let tool = Tool::new("echo", ToolSchema::object()).with_description("Echo tool");
901
902 let json = serde_json::to_string(&tool).unwrap();
903 assert!(json.contains("echo"));
904 assert!(json.contains("Echo tool"));
905
906 let parsed: Tool = serde_json::from_str(&json).unwrap();
907 assert_eq!(parsed.name, "echo");
908 }
909}