turul_mcp_protocol_2025_06_18/
tools.rs

1//! MCP Tools Protocol Types
2//!
3//! This module defines the types used for the MCP tools functionality.
4
5use crate::meta::Cursor;
6use crate::schema::JsonSchema;
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9use std::collections::HashMap;
10
11// ===========================================
12// === Tool Definition Trait Hierarchy ===
13// ===========================================
14
15/// Base metadata trait - matches TypeScript BaseMetadata interface
16pub trait HasBaseMetadata {
17    /// Programmatic identifier (fallback display name)
18    fn name(&self) -> &str;
19    
20    /// Human-readable display name (UI contexts)
21    fn title(&self) -> Option<&str> { None }
22}
23
24/// Tool description trait
25pub trait HasDescription {
26    fn description(&self) -> Option<&str> { None }
27}
28
29/// Input schema trait
30pub trait HasInputSchema {
31    fn input_schema(&self) -> &ToolSchema;
32}
33
34/// Output schema trait  
35pub trait HasOutputSchema {
36    fn output_schema(&self) -> Option<&ToolSchema> { None }
37}
38
39/// Annotations trait
40pub trait HasAnnotations {
41    fn annotations(&self) -> Option<&ToolAnnotations> { None }
42}
43
44/// Tool-specific meta trait (separate from RPC _meta)
45pub trait HasToolMeta {
46    fn tool_meta(&self) -> Option<&HashMap<String, Value>> { None }
47}
48
49/// Complete tool definition - composed from fine-grained traits
50pub trait ToolDefinition: 
51    HasBaseMetadata +           // name, title
52    HasDescription +            // description  
53    HasInputSchema +            // inputSchema
54    HasOutputSchema +           // outputSchema
55    HasAnnotations +            // annotations
56    HasToolMeta +               // _meta (tool-specific)
57    Send + 
58    Sync 
59{
60    /// Display name precedence: title > annotations.title > name (matches TypeScript spec)
61    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    /// Convert to concrete Tool struct for protocol serialization
76    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/// Tool annotations structure (matches TypeScript ToolAnnotations)
90/// NOTE: all properties in ToolAnnotations are **hints**.
91/// They are not guaranteed to provide a faithful description of tool behavior.
92/// Clients should never make tool use decisions based on ToolAnnotations from untrusted servers.
93#[derive(Debug, Clone, Serialize, Deserialize)]
94#[serde(rename_all = "camelCase")]
95pub struct ToolAnnotations {
96    /// A human-readable title for the tool
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub title: Option<String>,
99    /// If true, the tool does not modify its environment. Default: false
100    #[serde(rename = "readOnlyHint", skip_serializing_if = "Option::is_none")]
101    pub read_only_hint: Option<bool>,
102    /// If true, the tool may perform destructive updates to its environment.
103    /// If false, the tool performs only additive updates.
104    /// (This property is meaningful only when `readOnlyHint == false`) Default: true
105    #[serde(rename = "destructiveHint", skip_serializing_if = "Option::is_none")]
106    pub destructive_hint: Option<bool>,
107    /// If true, calling the tool repeatedly with the same arguments
108    /// will have no additional effect on its environment.
109    /// (This property is meaningful only when `readOnlyHint == false`) Default: false
110    #[serde(rename = "idempotentHint", skip_serializing_if = "Option::is_none")]
111    pub idempotent_hint: Option<bool>,
112    /// If true, this tool may interact with an "open world" of external entities.
113    /// If false, the tool's domain of interaction is closed.
114    /// For example, the world of a web search tool is open, whereas that of a memory tool is not.
115    /// Default: true
116    #[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// ===========================================
158// === Protocol Types ===
159// ===========================================
160
161/// JSON Schema definition for tool input/output (matches TypeScript spec exactly)
162/// Must be an object with type: "object", properties, and required fields
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct ToolSchema {
165    /// The schema type (must be "object" for tools)
166    #[serde(rename = "type")]
167    pub schema_type: String,
168    /// Property definitions
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub properties: Option<HashMap<String, JsonSchema>>,
171    /// Required property names
172    #[serde(skip_serializing_if = "Option::is_none")]
173    pub required: Option<Vec<String>>,
174    /// Additional schema properties
175    #[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/// Tool definition
201#[derive(Debug, Clone, Serialize, Deserialize)]
202#[serde(rename_all = "camelCase")]
203pub struct Tool {
204    /// The tool's name - used as identifier when calling
205    pub name: String,
206    /// Intended for UI and end-user contexts — optimized to be human-readable
207    /// and easily understood, even by those unfamiliar with domain-specific terminology.
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub title: Option<String>,
210    /// Optional human-readable description
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub description: Option<String>,
213    /// JSON Schema for input parameters
214    pub input_schema: ToolSchema,
215    /// Optional JSON Schema for output results
216    #[serde(skip_serializing_if = "Option::is_none")]
217    pub output_schema: Option<ToolSchema>,
218    /// Optional annotations for client hints
219    #[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
270// ===========================================
271// === Tool Implements ToolDefinition ===
272// ===========================================
273
274impl 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
299// Blanket implementation: any type that implements all component traits automatically implements ToolDefinition
300impl<T> ToolDefinition for T 
301where 
302    T: HasBaseMetadata + HasDescription + HasInputSchema + HasOutputSchema + HasAnnotations + HasToolMeta + Send + Sync,
303{
304    // Default implementations are provided by the trait definition
305}
306
307/// Parameters for tools/list request
308#[derive(Debug, Clone, Serialize, Deserialize)]
309#[serde(rename_all = "camelCase")]
310pub struct ListToolsParams {
311    /// Optional cursor for pagination
312    #[serde(skip_serializing_if = "Option::is_none")]
313    pub cursor: Option<Cursor>,
314    /// Meta information (optional _meta field inside params)
315    #[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/// Complete tools/list request (matches TypeScript ListToolsRequest interface)
345#[derive(Debug, Clone, Serialize, Deserialize)]
346#[serde(rename_all = "camelCase")]
347pub struct ListToolsRequest {
348    /// Method name (always "tools/list")
349    pub method: String,
350    /// Request parameters
351    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/// Result for tools/list (per MCP spec) - extends PaginatedResult
374#[derive(Debug, Clone, Serialize, Deserialize)]
375#[serde(rename_all = "camelCase")]
376pub struct ListToolsResult {
377    /// Available tools
378    pub tools: Vec<Tool>,
379    /// Optional cursor for next page
380    #[serde(skip_serializing_if = "Option::is_none")]
381    pub next_cursor: Option<Cursor>,
382    /// Meta information (from PaginatedResult)
383    #[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/// Parameters for tools/call request (matches TypeScript CallToolRequest.params)
408#[derive(Debug, Clone, Serialize, Deserialize)]
409#[serde(rename_all = "camelCase")]
410pub struct CallToolParams {
411    /// Name of the tool to call
412    pub name: String,
413    /// Arguments to pass to the tool - matches TypeScript { [key: string]: unknown }
414    #[serde(skip_serializing_if = "Option::is_none")]
415    pub arguments: Option<HashMap<String, Value>>,
416    /// Meta information (optional _meta field inside params)
417    #[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        // Helper for backward compatibility - convert Value to HashMap if it's an object
437        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/// Complete tools/call request (matches TypeScript CallToolRequest interface)
450#[derive(Debug, Clone, Serialize, Deserialize)]
451#[serde(rename_all = "camelCase")]
452pub struct CallToolRequest {
453    /// Method name (always "tools/call")
454    pub method: String,
455    /// Request parameters
456    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/// Content item types that tools can return
484#[derive(Debug, Clone, Serialize, Deserialize)]
485#[serde(tag = "type", rename_all = "lowercase")]
486pub enum ToolResult {
487    /// Text content
488    Text { text: String },
489    /// Image content
490    Image {
491        data: String,
492        #[serde(rename = "mimeType")]
493        mime_type: String,
494    },
495    /// Audio content
496    Audio {
497        data: String,
498        #[serde(rename = "mimeType")]
499        mime_type: String,
500    },
501    /// Resource reference
502    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/// Result for tools/call (per MCP spec)
546#[derive(Debug, Clone, Serialize, Deserialize)]
547#[serde(rename_all = "camelCase")]
548pub struct CallToolResult {
549    /// Content returned by the tool
550    pub content: Vec<ToolResult>,
551    /// Whether the tool call resulted in an error
552    #[serde(skip_serializing_if = "Option::is_none")]
553    pub is_error: Option<bool>,
554    /// Structured content that matches the tool's output schema (MCP 2025-06-18)
555    #[serde(skip_serializing_if = "Option::is_none")]
556    pub structured_content: Option<Value>,
557    /// Meta information (follows MCP Result interface)
558    #[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    // ===========================================
611    // === Smart Response Builders ===
612    // ===========================================
613    
614    /// Create response from serializable result with automatic structured content based on ToolDefinition
615    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        // Auto-add structured content if tool has output schema
625        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    /// Create response from serializable result with automatic structured content based on schema
635    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        // Auto-add structured content if schema exists
645        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    /// Create response with automatic structured content for primitives (zero-config)
655    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        // Auto-detect structured content for common types
665        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            // Auto-add structured content for primitive types (zero-config)
670            Value::Number(_) | Value::Bool(_) => true,
671            // Auto-add for arrays and objects (structured data)
672            Value::Array(_) | Value::Object(_) => true,
673            // Skip for plain strings (text is sufficient)
674            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    /// Create response from JSON value with automatic structured content
686    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
701// Trait implementations for CallToolResult
702
703use 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
744// Trait implementations for ListToolsParams
745impl 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
759// Trait implementations for ListToolsRequest
760impl 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
772// Trait implementations for ListToolsResult
773impl 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
808// Trait implementations for CallToolParams
809impl 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        // This is a temporary workaround for trait compatibility
818        // The trait expects &Value but we store HashMap<String, Value>
819        // TODO: Fix trait definition to use proper HashMap type
820        self.arguments.as_ref().and_then(|_| None) // Return None for now
821    }
822
823    fn meta(&self) -> Option<&HashMap<String, Value>> {
824        self.meta.as_ref()
825    }
826}
827
828// Trait implementations for CallToolRequest
829impl 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}