mcp_protocol_sdk/core/
tool.rs

1//! Tool system for MCP servers
2//!
3//! This module provides the abstraction for implementing and managing tools in MCP servers.
4//! Tools are functions that can be called by clients to perform specific operations.
5//! Enhanced with advanced parameter validation, type checking, and metadata support.
6
7use async_trait::async_trait;
8use serde_json::Value;
9use std::collections::HashMap;
10use std::time::Instant;
11
12use crate::core::error::{McpError, McpResult};
13use crate::core::tool_metadata::{
14    CategoryFilter, EnhancedToolMetadata, ToolBehaviorHints, ToolCategory, ToolDeprecation,
15};
16use crate::core::validation::{ParameterValidator, ValidationConfig};
17use crate::protocol::types::{ContentBlock, ToolInfo, ToolInputSchema, ToolResult};
18
19/// Trait for implementing tool handlers
20#[async_trait]
21pub trait ToolHandler: Send + Sync {
22    /// Execute the tool with the given arguments
23    ///
24    /// # Arguments
25    /// * `arguments` - Tool arguments as key-value pairs
26    ///
27    /// # Returns
28    /// Result containing the tool execution result or an error
29    async fn call(&self, arguments: HashMap<String, Value>) -> McpResult<ToolResult>;
30}
31
32/// A registered tool with its handler, validation, and enhanced metadata
33pub struct Tool {
34    /// Information about the tool
35    pub info: ToolInfo,
36    /// Handler that implements the tool's functionality
37    pub handler: Box<dyn ToolHandler>,
38    /// Whether the tool is currently enabled
39    pub enabled: bool,
40    /// Parameter validator for input validation
41    pub validator: Option<ParameterValidator>,
42    /// Enhanced metadata for tool behavior, categorization, and performance
43    pub enhanced_metadata: EnhancedToolMetadata,
44}
45
46impl Tool {
47    /// Create a new tool with the given information and handler
48    ///
49    /// # Arguments
50    /// * `name` - Name of the tool
51    /// * `description` - Optional description of the tool
52    /// * `input_schema` - JSON schema describing the input parameters
53    /// * `handler` - Implementation of the tool's functionality
54    pub fn new<H>(
55        name: String,
56        description: Option<String>,
57        input_schema: Value,
58        handler: H,
59    ) -> Self
60    where
61        H: ToolHandler + 'static,
62    {
63        // Create validator from schema
64        let validator = if input_schema.is_object() {
65            Some(ParameterValidator::new(input_schema.clone()))
66        } else {
67            None
68        };
69
70        Self {
71            info: ToolInfo {
72                name,
73                description,
74                input_schema: ToolInputSchema {
75                    schema_type: "object".to_string(),
76                    properties: input_schema
77                        .get("properties")
78                        .and_then(|p| p.as_object())
79                        .map(|obj| obj.iter().map(|(k, v)| (k.clone(), v.clone())).collect()),
80                    required: input_schema
81                        .get("required")
82                        .and_then(|r| r.as_array())
83                        .map(|arr| {
84                            arr.iter()
85                                .filter_map(|v| v.as_str().map(String::from))
86                                .collect()
87                        }),
88                    additional_properties: input_schema
89                        .as_object()
90                        .unwrap_or(&serde_json::Map::new())
91                        .iter()
92                        .filter(|(k, _)| !["type", "properties", "required"].contains(&k.as_str()))
93                        .map(|(k, v)| (k.clone(), v.clone()))
94                        .collect(),
95                },
96                annotations: None,
97                title: None,
98                meta: None,
99            },
100            handler: Box::new(handler),
101            enabled: true,
102            validator,
103            enhanced_metadata: EnhancedToolMetadata::new(),
104        }
105    }
106
107    /// Create a new tool with custom validation configuration
108    pub fn with_validation<H>(
109        name: String,
110        description: Option<String>,
111        input_schema: Value,
112        handler: H,
113        validation_config: ValidationConfig,
114    ) -> Self
115    where
116        H: ToolHandler + 'static,
117    {
118        let mut tool = Self::new(name, description, input_schema.clone(), handler);
119        if input_schema.is_object() {
120            tool.validator = Some(ParameterValidator::with_config(
121                input_schema,
122                validation_config,
123            ));
124        }
125        tool
126    }
127
128    /// Enable the tool
129    pub fn enable(&mut self) {
130        self.enabled = true;
131    }
132
133    /// Disable the tool
134    pub fn disable(&mut self) {
135        self.enabled = false;
136    }
137
138    /// Check if the tool is enabled
139    pub fn is_enabled(&self) -> bool {
140        self.enabled
141    }
142
143    /// Execute the tool if it's enabled with parameter validation and performance tracking
144    ///
145    /// # Arguments
146    /// * `arguments` - Tool arguments as key-value pairs
147    ///
148    /// # Returns
149    /// Result containing the tool execution result or an error
150    pub async fn call(&self, mut arguments: HashMap<String, Value>) -> McpResult<ToolResult> {
151        if !self.enabled {
152            return Err(McpError::validation(format!(
153                "Tool '{}' is disabled",
154                self.info.name
155            )));
156        }
157
158        // Check for deprecation warning
159        if let Some(warning) = self.enhanced_metadata.deprecation_warning() {
160            eprintln!("Warning: {warning}");
161        }
162
163        // Validate and coerce parameters if validator is present
164        if let Some(ref validator) = self.validator {
165            validator.validate_and_coerce(&mut arguments).map_err(|e| {
166                McpError::validation(format!(
167                    "Tool '{}' parameter validation failed: {}",
168                    self.info.name, e
169                ))
170            })?;
171        }
172
173        // Track execution time and outcome
174        let start_time = Instant::now();
175        let result = self.handler.call(arguments).await;
176        let execution_time = start_time.elapsed();
177
178        // Update performance metrics using interior mutability
179        match &result {
180            Ok(_) => self.enhanced_metadata.record_success(execution_time),
181            Err(_) => self.enhanced_metadata.record_error(execution_time),
182        }
183
184        result
185    }
186
187    /// Execute the tool without validation or performance tracking (for advanced use cases)
188    pub async fn call_unchecked(&self, arguments: HashMap<String, Value>) -> McpResult<ToolResult> {
189        if !self.enabled {
190            return Err(McpError::validation(format!(
191                "Tool '{}' is disabled",
192                self.info.name
193            )));
194        }
195
196        self.handler.call(arguments).await
197    }
198
199    /// Validate parameters without executing the tool
200    pub fn validate_parameters(&self, arguments: &mut HashMap<String, Value>) -> McpResult<()> {
201        if let Some(ref validator) = self.validator {
202            validator.validate_and_coerce(arguments).map_err(|e| {
203                McpError::validation(format!(
204                    "Tool '{}' parameter validation failed: {}",
205                    self.info.name, e
206                ))
207            })
208        } else {
209            Ok(())
210        }
211    }
212
213    // Enhanced Metadata Management Methods
214
215    /// Set behavior hints for the tool
216    pub fn set_behavior_hints(&mut self, hints: ToolBehaviorHints) {
217        self.enhanced_metadata.behavior_hints = hints;
218    }
219
220    /// Get behavior hints for the tool
221    pub fn behavior_hints(&self) -> &ToolBehaviorHints {
222        &self.enhanced_metadata.behavior_hints
223    }
224
225    /// Set category for the tool
226    pub fn set_category(&mut self, category: ToolCategory) {
227        self.enhanced_metadata.category = Some(category);
228    }
229
230    /// Get category for the tool
231    pub fn category(&self) -> Option<&ToolCategory> {
232        self.enhanced_metadata.category.as_ref()
233    }
234
235    /// Set version for the tool
236    pub fn set_version(&mut self, version: String) {
237        self.enhanced_metadata.version = Some(version);
238    }
239
240    /// Get version of the tool
241    pub fn version(&self) -> Option<&String> {
242        self.enhanced_metadata.version.as_ref()
243    }
244
245    /// Set author for the tool
246    pub fn set_author(&mut self, author: String) {
247        self.enhanced_metadata.author = Some(author);
248    }
249
250    /// Get author of the tool
251    pub fn author(&self) -> Option<&String> {
252        self.enhanced_metadata.author.as_ref()
253    }
254
255    /// Mark tool as deprecated
256    pub fn deprecate(&mut self, deprecation: ToolDeprecation) {
257        self.enhanced_metadata.deprecation = Some(deprecation);
258    }
259
260    /// Check if tool is deprecated
261    pub fn is_deprecated(&self) -> bool {
262        self.enhanced_metadata.is_deprecated()
263    }
264
265    /// Get deprecation warning if tool is deprecated
266    pub fn deprecation_warning(&self) -> Option<String> {
267        self.enhanced_metadata.deprecation_warning()
268    }
269
270    /// Get performance metrics for the tool
271    pub fn performance_metrics(&self) -> crate::core::tool_metadata::ToolPerformanceMetrics {
272        self.enhanced_metadata.get_performance_snapshot()
273    }
274
275    /// Add custom metadata field
276    pub fn add_custom_metadata(&mut self, key: String, value: serde_json::Value) {
277        self.enhanced_metadata.custom.insert(key, value);
278    }
279
280    /// Get custom metadata field
281    pub fn get_custom_metadata(&self, key: &str) -> Option<&serde_json::Value> {
282        self.enhanced_metadata.custom.get(key)
283    }
284
285    /// Check if tool matches a category filter
286    pub fn matches_category_filter(&self, filter: &CategoryFilter) -> bool {
287        if let Some(ref category) = self.enhanced_metadata.category {
288            category.matches_filter(filter)
289        } else {
290            // If no category set, only match empty filters
291            filter.primary.is_none() && filter.secondary.is_none() && filter.tags.is_empty()
292        }
293    }
294
295    /// Check if tool is suitable for caching based on behavior hints
296    pub fn is_cacheable(&self) -> bool {
297        self.enhanced_metadata
298            .behavior_hints
299            .cacheable
300            .unwrap_or(false)
301            || (self
302                .enhanced_metadata
303                .behavior_hints
304                .read_only
305                .unwrap_or(false)
306                && self
307                    .enhanced_metadata
308                    .behavior_hints
309                    .idempotent
310                    .unwrap_or(false))
311    }
312
313    /// Check if tool is destructive
314    pub fn is_destructive(&self) -> bool {
315        self.enhanced_metadata
316            .behavior_hints
317            .destructive
318            .unwrap_or(false)
319    }
320
321    /// Check if tool is read-only
322    pub fn is_read_only(&self) -> bool {
323        self.enhanced_metadata
324            .behavior_hints
325            .read_only
326            .unwrap_or(false)
327    }
328
329    /// Check if tool is idempotent
330    pub fn is_idempotent(&self) -> bool {
331        self.enhanced_metadata
332            .behavior_hints
333            .idempotent
334            .unwrap_or(false)
335    }
336
337    /// Check if tool requires authentication
338    pub fn requires_auth(&self) -> bool {
339        self.enhanced_metadata
340            .behavior_hints
341            .requires_auth
342            .unwrap_or(false)
343    }
344}
345
346impl std::fmt::Debug for Tool {
347    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
348        f.debug_struct("Tool")
349            .field("info", &self.info)
350            .field("enabled", &self.enabled)
351            .field("has_validator", &self.validator.is_some())
352            .field("deprecated", &self.is_deprecated())
353            .field("category", &self.enhanced_metadata.category)
354            .field("version", &self.enhanced_metadata.version)
355            .field("execution_count", &self.enhanced_metadata.execution_count())
356            .field("success_rate", &self.enhanced_metadata.success_rate())
357            .finish()
358    }
359}
360
361/// Helper macro for creating tools with schema validation
362///
363/// # Examples
364/// ```rust
365/// use mcp_protocol_sdk::{tool, core::tool::ToolHandler};
366/// use serde_json::json;
367///
368/// struct MyHandler;
369/// #[async_trait::async_trait]
370/// impl ToolHandler for MyHandler {
371///     async fn call(&self, _args: std::collections::HashMap<String, serde_json::Value>) -> mcp_protocol_sdk::McpResult<mcp_protocol_sdk::protocol::types::ToolResult> {
372///         // Implementation here
373///         todo!()
374///     }
375/// }
376///
377/// let tool = tool!(
378///     "my_tool",
379///     "A sample tool",
380///     json!({
381///         "type": "object",
382///         "properties": {
383///             "input": { "type": "string" }
384///         }
385///     }),
386///     MyHandler
387/// );
388/// ```
389#[macro_export]
390macro_rules! tool {
391    ($name:expr_2021, $schema:expr_2021, $handler:expr_2021) => {
392        $crate::core::tool::Tool::new($name.to_string(), None, $schema, $handler)
393    };
394    ($name:expr_2021, $description:expr_2021, $schema:expr_2021, $handler:expr_2021) => {
395        $crate::core::tool::Tool::new(
396            $name.to_string(),
397            Some($description.to_string()),
398            $schema,
399            $handler,
400        )
401    };
402}
403
404// Common tool implementations
405
406/// Simple echo tool for testing
407pub struct EchoTool;
408
409#[async_trait]
410impl ToolHandler for EchoTool {
411    async fn call(&self, arguments: HashMap<String, Value>) -> McpResult<ToolResult> {
412        let message = arguments
413            .get("message")
414            .and_then(|v| v.as_str())
415            .unwrap_or("Hello, World!");
416
417        Ok(ToolResult {
418            content: vec![ContentBlock::Text {
419                text: message.to_string(),
420                annotations: None,
421                meta: None,
422            }],
423            is_error: None,
424            structured_content: None,
425            meta: None,
426        })
427    }
428}
429
430/// Tool for adding two numbers
431pub struct AdditionTool;
432
433#[async_trait]
434impl ToolHandler for AdditionTool {
435    async fn call(&self, arguments: HashMap<String, Value>) -> McpResult<ToolResult> {
436        let a = arguments
437            .get("a")
438            .and_then(|v| v.as_f64())
439            .ok_or_else(|| McpError::validation("Missing or invalid 'a' parameter"))?;
440
441        let b = arguments
442            .get("b")
443            .and_then(|v| v.as_f64())
444            .ok_or_else(|| McpError::validation("Missing or invalid 'b' parameter"))?;
445
446        let result = a + b;
447
448        Ok(ToolResult {
449            content: vec![ContentBlock::Text {
450                text: result.to_string(),
451                annotations: None,
452                meta: None,
453            }],
454            is_error: None,
455            structured_content: None,
456            meta: None,
457        })
458    }
459}
460
461/// Tool for getting current timestamp
462pub struct TimestampTool;
463
464#[async_trait]
465impl ToolHandler for TimestampTool {
466    async fn call(&self, _arguments: HashMap<String, Value>) -> McpResult<ToolResult> {
467        use std::time::{SystemTime, UNIX_EPOCH};
468
469        let timestamp = SystemTime::now()
470            .duration_since(UNIX_EPOCH)
471            .map_err(|e| McpError::internal(e.to_string()))?
472            .as_secs();
473
474        Ok(ToolResult {
475            content: vec![ContentBlock::Text {
476                text: timestamp.to_string(),
477                annotations: None,
478                meta: None,
479            }],
480            is_error: None,
481            structured_content: None,
482            meta: None,
483        })
484    }
485}
486
487/// Builder for creating tools with fluent API, advanced validation, and enhanced metadata
488pub struct ToolBuilder {
489    name: String,
490    description: Option<String>,
491    input_schema: Option<Value>,
492    validation_config: Option<ValidationConfig>,
493    title: Option<String>,
494    behavior_hints: ToolBehaviorHints,
495    category: Option<ToolCategory>,
496    version: Option<String>,
497    author: Option<String>,
498    deprecation: Option<ToolDeprecation>,
499    custom_metadata: HashMap<String, serde_json::Value>,
500}
501
502impl ToolBuilder {
503    /// Create a new tool builder with the given name
504    pub fn new<S: Into<String>>(name: S) -> Self {
505        Self {
506            name: name.into(),
507            description: None,
508            input_schema: None,
509            validation_config: None,
510            title: None,
511            behavior_hints: ToolBehaviorHints::new(),
512            category: None,
513            version: None,
514            author: None,
515            deprecation: None,
516            custom_metadata: HashMap::new(),
517        }
518    }
519
520    /// Set the tool description
521    pub fn description<S: Into<String>>(mut self, description: S) -> Self {
522        self.description = Some(description.into());
523        self
524    }
525
526    /// Set the tool title (for UI display)
527    pub fn title<S: Into<String>>(mut self, title: S) -> Self {
528        self.title = Some(title.into());
529        self
530    }
531
532    /// Set the input schema
533    pub fn schema(mut self, schema: Value) -> Self {
534        self.input_schema = Some(schema);
535        self
536    }
537
538    /// Set custom validation configuration
539    pub fn validation_config(mut self, config: ValidationConfig) -> Self {
540        self.validation_config = Some(config);
541        self
542    }
543
544    /// Enable strict validation (no additional properties, strict types)
545    pub fn strict_validation(mut self) -> Self {
546        self.validation_config = Some(ValidationConfig {
547            allow_additional: false,
548            coerce_types: false,
549            detailed_errors: true,
550            max_string_length: Some(1000),
551            max_array_length: Some(100),
552            max_object_properties: Some(50),
553        });
554        self
555    }
556
557    /// Enable permissive validation (allow additional properties, type coercion)
558    pub fn permissive_validation(mut self) -> Self {
559        self.validation_config = Some(ValidationConfig {
560            allow_additional: true,
561            coerce_types: true,
562            detailed_errors: false,
563            max_string_length: None,
564            max_array_length: None,
565            max_object_properties: None,
566        });
567        self
568    }
569
570    // Enhanced Metadata Builder Methods
571
572    /// Set behavior hints for the tool
573    pub fn behavior_hints(mut self, hints: ToolBehaviorHints) -> Self {
574        self.behavior_hints = hints;
575        self
576    }
577
578    /// Mark tool as read-only
579    pub fn read_only(mut self) -> Self {
580        self.behavior_hints = self.behavior_hints.read_only();
581        self
582    }
583
584    /// Mark tool as destructive
585    pub fn destructive(mut self) -> Self {
586        self.behavior_hints = self.behavior_hints.destructive();
587        self
588    }
589
590    /// Mark tool as idempotent
591    pub fn idempotent(mut self) -> Self {
592        self.behavior_hints = self.behavior_hints.idempotent();
593        self
594    }
595
596    /// Mark tool as requiring authentication
597    pub fn requires_auth(mut self) -> Self {
598        self.behavior_hints = self.behavior_hints.requires_auth();
599        self
600    }
601
602    /// Mark tool as potentially long-running
603    pub fn long_running(mut self) -> Self {
604        self.behavior_hints = self.behavior_hints.long_running();
605        self
606    }
607
608    /// Mark tool as resource-intensive
609    pub fn resource_intensive(mut self) -> Self {
610        self.behavior_hints = self.behavior_hints.resource_intensive();
611        self
612    }
613
614    /// Mark tool results as cacheable
615    pub fn cacheable(mut self) -> Self {
616        self.behavior_hints = self.behavior_hints.cacheable();
617        self
618    }
619
620    /// Set tool category
621    pub fn category(mut self, category: ToolCategory) -> Self {
622        self.category = Some(category);
623        self
624    }
625
626    /// Set tool category with primary and secondary classification
627    pub fn category_simple(mut self, primary: String, secondary: Option<String>) -> Self {
628        let mut cat = ToolCategory::new(primary);
629        if let Some(sec) = secondary {
630            cat = cat.with_secondary(sec);
631        }
632        self.category = Some(cat);
633        self
634    }
635
636    /// Add category tag
637    pub fn tag(mut self, tag: String) -> Self {
638        if let Some(ref mut category) = self.category {
639            category.tags.insert(tag);
640        } else {
641            let mut cat = ToolCategory::new("general".to_string());
642            cat.tags.insert(tag);
643            self.category = Some(cat);
644        }
645        self
646    }
647
648    /// Set tool version
649    pub fn version<S: Into<String>>(mut self, version: S) -> Self {
650        self.version = Some(version.into());
651        self
652    }
653
654    /// Set tool author
655    pub fn author<S: Into<String>>(mut self, author: S) -> Self {
656        self.author = Some(author.into());
657        self
658    }
659
660    /// Mark tool as deprecated
661    pub fn deprecated(mut self, deprecation: ToolDeprecation) -> Self {
662        self.deprecation = Some(deprecation);
663        self
664    }
665
666    /// Mark tool as deprecated with simple reason
667    pub fn deprecated_simple<S: Into<String>>(mut self, reason: S) -> Self {
668        self.deprecation = Some(ToolDeprecation::new(reason.into()));
669        self
670    }
671
672    /// Add custom metadata field
673    pub fn custom_metadata<S: Into<String>>(mut self, key: S, value: serde_json::Value) -> Self {
674        self.custom_metadata.insert(key.into(), value);
675        self
676    }
677
678    /// Build the tool with the given handler
679    pub fn build<H>(self, handler: H) -> McpResult<Tool>
680    where
681        H: ToolHandler + 'static,
682    {
683        let schema = self.input_schema.unwrap_or_else(|| {
684            serde_json::json!({
685                "type": "object",
686                "properties": {},
687                "additionalProperties": true
688            })
689        });
690
691        let mut tool = if let Some(config) = self.validation_config {
692            Tool::with_validation(self.name, self.description, schema, handler, config)
693        } else {
694            Tool::new(self.name, self.description, schema, handler)
695        };
696
697        // Set title if provided
698        if let Some(title) = self.title {
699            tool.info.title = Some(title);
700        }
701
702        // Apply enhanced metadata
703        let mut enhanced_metadata =
704            EnhancedToolMetadata::new().with_behavior_hints(self.behavior_hints);
705
706        if let Some(category) = self.category {
707            enhanced_metadata = enhanced_metadata.with_category(category);
708        }
709
710        if let Some(version) = self.version {
711            enhanced_metadata = enhanced_metadata.with_version(version);
712        }
713
714        if let Some(author) = self.author {
715            enhanced_metadata = enhanced_metadata.with_author(author);
716        }
717
718        if let Some(deprecation) = self.deprecation {
719            enhanced_metadata = enhanced_metadata.deprecated(deprecation);
720        }
721
722        // Add custom metadata fields
723        for (key, value) in self.custom_metadata {
724            enhanced_metadata = enhanced_metadata.with_custom_field(key, value);
725        }
726
727        tool.enhanced_metadata = enhanced_metadata;
728
729        Ok(tool)
730    }
731
732    /// Build the tool with validation chain - allows chaining parameter validation
733    pub fn build_with_validation_chain<H>(
734        self,
735        handler: H,
736        validation_fn: impl Fn(&mut HashMap<String, Value>) -> McpResult<()> + Send + Sync + 'static,
737    ) -> McpResult<ValidationChainTool>
738    where
739        H: ToolHandler + 'static,
740    {
741        let tool = self.build(handler)?;
742        Ok(ValidationChainTool {
743            tool,
744            custom_validator: Box::new(validation_fn),
745        })
746    }
747}
748
749/// Tool wrapper that supports custom validation chains
750/// Type alias for validation function to reduce complexity
751type ValidationFunction = Box<dyn Fn(&mut HashMap<String, Value>) -> McpResult<()> + Send + Sync>;
752
753pub struct ValidationChainTool {
754    tool: Tool,
755    custom_validator: ValidationFunction,
756}
757
758#[async_trait]
759impl ToolHandler for ValidationChainTool {
760    async fn call(&self, mut arguments: HashMap<String, Value>) -> McpResult<ToolResult> {
761        // Run custom validation first
762        (self.custom_validator)(&mut arguments)?;
763
764        // Then run the tool's built-in validation and execution
765        self.tool.call(arguments).await
766    }
767}
768
769// ============================================================================
770// Enhanced Tool Creation Helpers and Macros
771// ============================================================================
772
773/// Create a validated tool with typed parameters
774#[macro_export]
775macro_rules! validated_tool {
776    (
777        name: $name:expr_2021,
778        description: $desc:expr_2021,
779        parameters: {
780            $( $param_name:ident: $param_type:ident $( ( $( $constraint:ident: $value:expr_2021 ),* ) )? ),*
781        },
782        handler: $handler:expr_2021
783    ) => {{
784        use $crate::core::validation::{create_tool_schema, param_schema};
785
786        let params = vec![
787            $(
788                {
789                    let base_schema = param_schema!($param_type stringify!($param_name));
790                    // Apply constraints if any
791                    $(
792                        // This would need more complex macro expansion for constraints
793                        // For now, we'll use the base schema
794                    )?
795                    base_schema
796                }
797            ),*
798        ];
799
800        let required = vec![ $( stringify!($param_name) ),* ];
801        let schema = create_tool_schema(params, required);
802
803        $crate::core::tool::Tool::new(
804            $name.to_string(),
805            Some($desc.to_string()),
806            schema,
807            $handler
808        )
809    }};
810}
811
812/// Helper function to create a simple string parameter tool
813pub fn create_string_tool<H>(
814    name: &str,
815    description: &str,
816    param_name: &str,
817    param_description: &str,
818    handler: H,
819) -> Tool
820where
821    H: ToolHandler + 'static,
822{
823    use serde_json::json;
824
825    let schema = json!({
826        "type": "object",
827        "properties": {
828            param_name: {
829                "type": "string",
830                "description": param_description
831            }
832        },
833        "required": [param_name]
834    });
835
836    Tool::new(
837        name.to_string(),
838        Some(description.to_string()),
839        schema,
840        handler,
841    )
842}
843
844/// Helper function to create a tool with multiple typed parameters
845pub fn create_typed_tool<H>(
846    name: &str,
847    description: &str,
848    parameters: Vec<(&str, &str, Value)>, // (name, description, schema)
849    required: Vec<&str>,
850    handler: H,
851) -> Tool
852where
853    H: ToolHandler + 'static,
854{
855    use serde_json::{Map, json};
856
857    let mut properties = Map::new();
858    for (param_name, param_desc, param_schema) in parameters {
859        let mut schema_with_desc = param_schema;
860        if let Some(obj) = schema_with_desc.as_object_mut() {
861            obj.insert("description".to_string(), json!(param_desc));
862        }
863        properties.insert(param_name.to_string(), schema_with_desc);
864    }
865
866    let schema = json!({
867        "type": "object",
868        "properties": properties,
869        "required": required
870    });
871
872    Tool::new(
873        name.to_string(),
874        Some(description.to_string()),
875        schema,
876        handler,
877    )
878}
879
880/// Trait for tools that can provide their own parameter validation
881pub trait ValidatedToolHandler: ToolHandler {
882    /// Get the JSON schema for this tool's parameters
883    fn parameter_schema() -> Value;
884
885    /// Get validation configuration for this tool
886    fn validation_config() -> ValidationConfig {
887        ValidationConfig::default()
888    }
889
890    /// Create a tool instance with built-in validation
891    fn create_tool(name: String, description: Option<String>, handler: Self) -> Tool
892    where
893        Self: Sized + 'static,
894    {
895        Tool::with_validation(
896            name,
897            description,
898            Self::parameter_schema(),
899            handler,
900            Self::validation_config(),
901        )
902    }
903}
904
905// ============================================================================
906// Enhanced Built-in Tool Examples
907// ============================================================================
908
909/// Enhanced calculator tool with comprehensive validation
910pub struct CalculatorTool;
911
912#[async_trait]
913impl ToolHandler for CalculatorTool {
914    async fn call(&self, arguments: HashMap<String, Value>) -> McpResult<ToolResult> {
915        let operation = arguments
916            .get("operation")
917            .and_then(|v| v.as_str())
918            .ok_or_else(|| McpError::validation("Missing 'operation' parameter"))?;
919
920        let a = arguments
921            .get("a")
922            .and_then(|v| v.as_f64())
923            .ok_or_else(|| McpError::validation("Missing or invalid 'a' parameter"))?;
924
925        let b = arguments
926            .get("b")
927            .and_then(|v| v.as_f64())
928            .ok_or_else(|| McpError::validation("Missing or invalid 'b' parameter"))?;
929
930        let result = match operation {
931            "add" => a + b,
932            "subtract" => a - b,
933            "multiply" => a * b,
934            "divide" => {
935                if b == 0.0 {
936                    return Ok(ToolResult {
937                        content: vec![ContentBlock::Text {
938                            text: "Error: Division by zero".to_string(),
939                            annotations: None,
940                            meta: None,
941                        }],
942                        is_error: Some(true),
943                        structured_content: Some(serde_json::json!({
944                            "error": "division_by_zero",
945                            "message": "Cannot divide by zero"
946                        })),
947                        meta: None,
948                    });
949                }
950                a / b
951            }
952            _ => {
953                return Err(McpError::validation(format!(
954                    "Unsupported operation: {operation}"
955                )));
956            }
957        };
958
959        Ok(ToolResult {
960            content: vec![ContentBlock::Text {
961                text: result.to_string(),
962                annotations: None,
963                meta: None,
964            }],
965            is_error: None,
966            structured_content: Some(serde_json::json!({
967                "operation": operation,
968                "operands": [a, b],
969                "result": result
970            })),
971            meta: None,
972        })
973    }
974}
975
976impl ValidatedToolHandler for CalculatorTool {
977    fn parameter_schema() -> Value {
978        use crate::core::validation::create_tool_schema;
979        use crate::param_schema;
980
981        create_tool_schema(
982            vec![
983                param_schema!(enum "operation", values: ["add", "subtract", "multiply", "divide"]),
984                param_schema!(number "a", min: -1000000, max: 1000000),
985                param_schema!(number "b", min: -1000000, max: 1000000),
986            ],
987            vec!["operation", "a", "b"],
988        )
989    }
990
991    fn validation_config() -> ValidationConfig {
992        ValidationConfig {
993            allow_additional: false,
994            coerce_types: true,
995            detailed_errors: true,
996            max_string_length: Some(20),
997            max_array_length: Some(10),
998            max_object_properties: Some(10),
999        }
1000    }
1001}
1002
1003/// Text processing tool with advanced string validation
1004pub struct TextProcessorTool;
1005
1006#[async_trait]
1007impl ToolHandler for TextProcessorTool {
1008    async fn call(&self, arguments: HashMap<String, Value>) -> McpResult<ToolResult> {
1009        let text = arguments
1010            .get("text")
1011            .and_then(|v| v.as_str())
1012            .ok_or_else(|| McpError::validation("Missing 'text' parameter"))?;
1013
1014        let operation = arguments
1015            .get("operation")
1016            .and_then(|v| v.as_str())
1017            .unwrap_or("uppercase");
1018
1019        let result = match operation {
1020            "uppercase" => text.to_uppercase(),
1021            "lowercase" => text.to_lowercase(),
1022            "reverse" => text.chars().rev().collect(),
1023            "word_count" => text.split_whitespace().count().to_string(),
1024            "char_count" => text.len().to_string(),
1025            _ => {
1026                return Err(McpError::validation(format!(
1027                    "Unsupported operation: {operation}"
1028                )));
1029            }
1030        };
1031
1032        Ok(ToolResult {
1033            content: vec![ContentBlock::Text {
1034                text: result.clone(),
1035                annotations: None,
1036                meta: None,
1037            }],
1038            is_error: None,
1039            structured_content: Some(serde_json::json!({
1040                "original_text": text,
1041                "operation": operation,
1042                "result": result,
1043                "length": text.len()
1044            })),
1045            meta: None,
1046        })
1047    }
1048}
1049
1050impl ValidatedToolHandler for TextProcessorTool {
1051    fn parameter_schema() -> Value {
1052        use crate::core::validation::create_tool_schema;
1053        use crate::param_schema;
1054
1055        create_tool_schema(
1056            vec![
1057                param_schema!(string "text", min: 1, max: 10000),
1058                param_schema!(enum "operation", values: ["uppercase", "lowercase", "reverse", "word_count", "char_count"]),
1059            ],
1060            vec!["text"],
1061        )
1062    }
1063}
1064
1065#[cfg(test)]
1066mod tests {
1067    use super::*;
1068    use crate::Content;
1069    use serde_json::json;
1070
1071    #[tokio::test]
1072    async fn test_echo_tool() {
1073        let tool = EchoTool;
1074        let mut args = HashMap::new();
1075        args.insert("message".to_string(), json!("test message"));
1076
1077        let result = tool.call(args).await.unwrap();
1078        match &result.content[0] {
1079            Content::Text { text, .. } => assert_eq!(text, "test message"),
1080            _ => panic!("Expected text content"),
1081        }
1082    }
1083
1084    #[tokio::test]
1085    async fn test_addition_tool() {
1086        let tool = AdditionTool;
1087        let mut args = HashMap::new();
1088        args.insert("a".to_string(), json!(5.0));
1089        args.insert("b".to_string(), json!(3.0));
1090
1091        let result = tool.call(args).await.unwrap();
1092        match &result.content[0] {
1093            Content::Text { text, .. } => assert_eq!(text, "8"),
1094            _ => panic!("Expected text content"),
1095        }
1096    }
1097
1098    #[test]
1099    fn test_tool_creation() {
1100        let tool = Tool::new(
1101            "test_tool".to_string(),
1102            Some("Test tool".to_string()),
1103            json!({"type": "object"}),
1104            EchoTool,
1105        );
1106
1107        assert_eq!(tool.info.name, "test_tool");
1108        assert_eq!(tool.info.description, Some("Test tool".to_string()));
1109        assert!(tool.is_enabled());
1110    }
1111
1112    #[test]
1113    fn test_tool_enable_disable() {
1114        let mut tool = Tool::new(
1115            "test_tool".to_string(),
1116            None,
1117            json!({"type": "object"}),
1118            EchoTool,
1119        );
1120
1121        assert!(tool.is_enabled());
1122
1123        tool.disable();
1124        assert!(!tool.is_enabled());
1125
1126        tool.enable();
1127        assert!(tool.is_enabled());
1128    }
1129
1130    #[tokio::test]
1131    async fn test_disabled_tool() {
1132        let mut tool = Tool::new(
1133            "test_tool".to_string(),
1134            None,
1135            json!({"type": "object"}),
1136            EchoTool,
1137        );
1138
1139        tool.disable();
1140
1141        let result = tool.call(HashMap::new()).await;
1142        assert!(result.is_err());
1143        match result.unwrap_err() {
1144            McpError::Validation(msg) => assert!(msg.contains("disabled")),
1145            _ => panic!("Expected validation error"),
1146        }
1147    }
1148
1149    #[test]
1150    fn test_tool_builder() {
1151        let tool = ToolBuilder::new("test")
1152            .description("A test tool")
1153            .schema(json!({"type": "object", "properties": {"x": {"type": "number"}}}))
1154            .build(EchoTool)
1155            .unwrap();
1156
1157        assert_eq!(tool.info.name, "test");
1158        assert_eq!(tool.info.description, Some("A test tool".to_string()));
1159        assert!(tool.validator.is_some());
1160    }
1161
1162    #[test]
1163    fn test_enhanced_tool_builder() {
1164        let tool = ToolBuilder::new("enhanced_test")
1165            .title("Enhanced Test Tool")
1166            .description("A test tool with enhanced features")
1167            .strict_validation()
1168            .schema(json!({
1169                "type": "object",
1170                "properties": {
1171                    "name": {"type": "string", "minLength": 2},
1172                    "age": {"type": "integer", "minimum": 0}
1173                },
1174                "required": ["name"]
1175            }))
1176            .build(EchoTool)
1177            .unwrap();
1178
1179        assert_eq!(tool.info.name, "enhanced_test");
1180        assert_eq!(tool.info.title, Some("Enhanced Test Tool".to_string()));
1181        assert!(tool.validator.is_some());
1182    }
1183
1184    #[tokio::test]
1185    async fn test_parameter_validation() {
1186        let schema = json!({
1187            "type": "object",
1188            "properties": {
1189                "name": {"type": "string", "minLength": 2},
1190                "age": {"type": "integer", "minimum": 0, "maximum": 150}
1191            },
1192            "required": ["name", "age"]
1193        });
1194
1195        let tool = Tool::new(
1196            "validation_test".to_string(),
1197            Some("Test validation".to_string()),
1198            schema,
1199            EchoTool,
1200        );
1201
1202        // Valid parameters
1203        let mut valid_args = HashMap::new();
1204        valid_args.insert("name".to_string(), json!("Alice"));
1205        valid_args.insert("age".to_string(), json!(25));
1206        assert!(tool.validate_parameters(&mut valid_args).is_ok());
1207
1208        // Missing required parameter
1209        let mut invalid_args = HashMap::new();
1210        invalid_args.insert("name".to_string(), json!("Bob"));
1211        assert!(tool.validate_parameters(&mut invalid_args).is_err());
1212
1213        // Invalid parameter type with coercion
1214        let mut coercible_args = HashMap::new();
1215        coercible_args.insert("name".to_string(), json!("Charlie"));
1216        coercible_args.insert("age".to_string(), json!("30")); // String that can be coerced to number
1217        assert!(tool.validate_parameters(&mut coercible_args).is_ok());
1218        // After validation, should be coerced to number
1219        assert_eq!(coercible_args.get("age").unwrap().as_i64(), Some(30));
1220    }
1221
1222    #[tokio::test]
1223    async fn test_calculator_tool() {
1224        let tool = CalculatorTool::create_tool(
1225            "calculator".to_string(),
1226            Some("Advanced calculator".to_string()),
1227            CalculatorTool,
1228        );
1229
1230        // Test addition
1231        let mut args = HashMap::new();
1232        args.insert("operation".to_string(), json!("add"));
1233        args.insert("a".to_string(), json!(5));
1234        args.insert("b".to_string(), json!(3));
1235
1236        let result = tool.call(args).await.unwrap();
1237        assert_eq!(
1238            result.content[0],
1239            ContentBlock::Text {
1240                text: "8".to_string(),
1241                annotations: None,
1242                meta: None,
1243            }
1244        );
1245        assert!(result.structured_content.is_some());
1246
1247        // Test division by zero
1248        let mut args = HashMap::new();
1249        args.insert("operation".to_string(), json!("divide"));
1250        args.insert("a".to_string(), json!(10));
1251        args.insert("b".to_string(), json!(0));
1252
1253        let result = tool.call(args).await.unwrap();
1254        assert_eq!(result.is_error, Some(true));
1255        if let ContentBlock::Text { text, .. } = &result.content[0] {
1256            assert!(text.contains("Division by zero"));
1257        } else {
1258            panic!("Expected text content");
1259        }
1260    }
1261
1262    #[tokio::test]
1263    async fn test_text_processor_tool() {
1264        let tool = TextProcessorTool::create_tool(
1265            "text_processor".to_string(),
1266            Some("Text processing utility".to_string()),
1267            TextProcessorTool,
1268        );
1269
1270        // Test uppercase
1271        let mut args = HashMap::new();
1272        args.insert("text".to_string(), json!("hello world"));
1273        args.insert("operation".to_string(), json!("uppercase"));
1274
1275        let result = tool.call(args.clone()).await.unwrap();
1276        assert_eq!(
1277            result.content[0],
1278            ContentBlock::Text {
1279                text: "HELLO WORLD".to_string(),
1280                annotations: None,
1281                meta: None,
1282            }
1283        );
1284
1285        // Test word count
1286        args.insert("operation".to_string(), json!("word_count"));
1287        let result = tool.call(args).await.unwrap();
1288        assert_eq!(
1289            result.content[0],
1290            ContentBlock::Text {
1291                text: "2".to_string(),
1292                annotations: None,
1293                meta: None,
1294            }
1295        );
1296    }
1297
1298    #[test]
1299    fn test_create_typed_tool() {
1300        let tool = create_typed_tool(
1301            "typed_test",
1302            "A typed parameter test tool",
1303            vec![
1304                (
1305                    "username",
1306                    "User's name",
1307                    json!({"type": "string", "minLength": 3}),
1308                ),
1309                (
1310                    "age",
1311                    "User's age",
1312                    json!({"type": "integer", "minimum": 0}),
1313                ),
1314                (
1315                    "active",
1316                    "Whether user is active",
1317                    json!({"type": "boolean"}),
1318                ),
1319            ],
1320            vec!["username", "age"],
1321            EchoTool,
1322        );
1323
1324        assert_eq!(tool.info.name, "typed_test");
1325        assert!(tool.validator.is_some());
1326
1327        // Check that schema was built correctly
1328        let schema = &tool.info.input_schema;
1329        assert!(schema.properties.is_some());
1330        let props = schema.properties.as_ref().unwrap();
1331        assert!(props.contains_key("username"));
1332        assert!(props.contains_key("age"));
1333        assert!(props.contains_key("active"));
1334    }
1335
1336    #[test]
1337    fn test_validation_config_options() {
1338        // Test strict validation
1339        let strict_tool = ToolBuilder::new("strict")
1340            .strict_validation()
1341            .build(EchoTool)
1342            .unwrap();
1343        assert!(strict_tool.validator.is_some());
1344
1345        // Test permissive validation
1346        let permissive_tool = ToolBuilder::new("permissive")
1347            .permissive_validation()
1348            .build(EchoTool)
1349            .unwrap();
1350        assert!(permissive_tool.validator.is_some());
1351    }
1352}
1353
1354// ============================================================================
1355// Extension Trait for Better Ergonomics
1356// ============================================================================
1357
1358/// Extension trait for HashMap to make parameter extraction easier
1359pub trait ParameterExt {
1360    /// Extract a required string parameter
1361    fn get_string(&self, key: &str) -> McpResult<&str>;
1362
1363    /// Extract an optional string parameter
1364    fn get_optional_string(&self, key: &str) -> Option<&str>;
1365
1366    /// Extract a required number parameter
1367    fn get_number(&self, key: &str) -> McpResult<f64>;
1368
1369    /// Extract an optional number parameter
1370    fn get_optional_number(&self, key: &str) -> Option<f64>;
1371
1372    /// Extract a required integer parameter
1373    fn get_integer(&self, key: &str) -> McpResult<i64>;
1374
1375    /// Extract an optional integer parameter
1376    fn get_optional_integer(&self, key: &str) -> Option<i64>;
1377
1378    /// Extract a required boolean parameter
1379    fn get_boolean(&self, key: &str) -> McpResult<bool>;
1380
1381    /// Extract an optional boolean parameter
1382    fn get_optional_boolean(&self, key: &str) -> Option<bool>;
1383}
1384
1385impl ParameterExt for HashMap<String, Value> {
1386    fn get_string(&self, key: &str) -> McpResult<&str> {
1387        self.get(key).and_then(|v| v.as_str()).ok_or_else(|| {
1388            McpError::validation(format!("Missing or invalid string parameter: {key}"))
1389        })
1390    }
1391
1392    fn get_optional_string(&self, key: &str) -> Option<&str> {
1393        self.get(key).and_then(|v| v.as_str())
1394    }
1395
1396    fn get_number(&self, key: &str) -> McpResult<f64> {
1397        self.get(key).and_then(|v| v.as_f64()).ok_or_else(|| {
1398            McpError::validation(format!("Missing or invalid number parameter: {key}"))
1399        })
1400    }
1401
1402    fn get_optional_number(&self, key: &str) -> Option<f64> {
1403        self.get(key).and_then(|v| v.as_f64())
1404    }
1405
1406    fn get_integer(&self, key: &str) -> McpResult<i64> {
1407        self.get(key).and_then(|v| v.as_i64()).ok_or_else(|| {
1408            McpError::validation(format!("Missing or invalid integer parameter: {key}"))
1409        })
1410    }
1411
1412    fn get_optional_integer(&self, key: &str) -> Option<i64> {
1413        self.get(key).and_then(|v| v.as_i64())
1414    }
1415
1416    fn get_boolean(&self, key: &str) -> McpResult<bool> {
1417        self.get(key).and_then(|v| v.as_bool()).ok_or_else(|| {
1418            McpError::validation(format!("Missing or invalid boolean parameter: {key}"))
1419        })
1420    }
1421
1422    fn get_optional_boolean(&self, key: &str) -> Option<bool> {
1423        self.get(key).and_then(|v| v.as_bool())
1424    }
1425}
1426
1427#[cfg(test)]
1428mod enhanced_tests {
1429    use super::*;
1430    use crate::core::tool_metadata::*;
1431    use crate::prelude::ToolHandler;
1432    use std::time::Duration;
1433    use tokio;
1434
1435    // Test handler for basic tool functionality
1436    struct TestHandler {
1437        result: String,
1438        should_fail: bool,
1439    }
1440
1441    #[async_trait]
1442    impl ToolHandler for TestHandler {
1443        async fn call(&self, _arguments: HashMap<String, Value>) -> McpResult<ToolResult> {
1444            if self.should_fail {
1445                Err(McpError::validation("Test error".to_string()))
1446            } else {
1447                Ok(ToolResult {
1448                    content: vec![ContentBlock::Text {
1449                        text: self.result.clone(),
1450                        annotations: None,
1451                        meta: None,
1452                    }],
1453                    is_error: None,
1454                    structured_content: None,
1455                    meta: None,
1456                })
1457            }
1458        }
1459    }
1460
1461    #[tokio::test]
1462    async fn test_enhanced_tool_builder() {
1463        let handler = TestHandler {
1464            result: "test result".to_string(),
1465            should_fail: false,
1466        };
1467
1468        let tool = ToolBuilder::new("test_tool")
1469            .description("A test tool")
1470            .title("Test Tool")
1471            .version("1.0.0")
1472            .author("Test Author")
1473            .read_only()
1474            .idempotent()
1475            .cacheable()
1476            .category_simple("data".to_string(), Some("analysis".to_string()))
1477            .tag("testing".to_string())
1478            .tag("utility".to_string())
1479            .custom_metadata("priority".to_string(), serde_json::Value::from("high"))
1480            .build(handler)
1481            .expect("Failed to build tool");
1482
1483        assert_eq!(tool.info.name, "test_tool");
1484        assert_eq!(tool.info.description, Some("A test tool".to_string()));
1485        assert_eq!(tool.info.title, Some("Test Tool".to_string()));
1486        assert_eq!(tool.version(), Some(&"1.0.0".to_string()));
1487        assert_eq!(tool.author(), Some(&"Test Author".to_string()));
1488        assert!(tool.is_read_only());
1489        assert!(tool.is_idempotent());
1490        assert!(tool.is_cacheable());
1491        assert!(!tool.is_destructive());
1492        assert!(!tool.requires_auth());
1493
1494        let category = tool.category().unwrap();
1495        assert_eq!(category.primary, "data");
1496        assert_eq!(category.secondary, Some("analysis".to_string()));
1497        assert!(category.tags.contains("testing"));
1498        assert!(category.tags.contains("utility"));
1499
1500        let custom_priority = tool.get_custom_metadata("priority");
1501        assert_eq!(custom_priority, Some(&serde_json::Value::from("high")));
1502    }
1503
1504    #[tokio::test]
1505    async fn test_performance_tracking() {
1506        let handler = TestHandler {
1507            result: "success".to_string(),
1508            should_fail: false,
1509        };
1510
1511        let tool = ToolBuilder::new("performance_test")
1512            .build(handler)
1513            .expect("Failed to build tool");
1514
1515        // Initial state
1516        let metrics = tool.performance_metrics();
1517        assert_eq!(metrics.execution_count, 0);
1518        assert_eq!(metrics.success_count, 0);
1519        assert_eq!(metrics.error_count, 0);
1520
1521        // Execute tool successfully
1522        let result = tool.call(HashMap::new()).await;
1523        assert!(result.is_ok());
1524
1525        // Check updated metrics
1526        let metrics = tool.performance_metrics();
1527        assert_eq!(metrics.execution_count, 1);
1528        assert_eq!(metrics.success_count, 1);
1529        assert_eq!(metrics.error_count, 0);
1530        assert_eq!(metrics.success_rate, 100.0);
1531        assert!(metrics.average_execution_time > Duration::from_nanos(0));
1532    }
1533
1534    #[tokio::test]
1535    async fn test_performance_tracking_with_errors() {
1536        let handler = TestHandler {
1537            result: "".to_string(),
1538            should_fail: true,
1539        };
1540
1541        let tool = ToolBuilder::new("error_test")
1542            .build(handler)
1543            .expect("Failed to build tool");
1544
1545        // Execute tool with error
1546        let result = tool.call(HashMap::new()).await;
1547        assert!(result.is_err());
1548
1549        // Check error metrics
1550        let metrics = tool.performance_metrics();
1551        assert_eq!(metrics.execution_count, 1);
1552        assert_eq!(metrics.success_count, 0);
1553        assert_eq!(metrics.error_count, 1);
1554        assert_eq!(metrics.success_rate, 0.0);
1555    }
1556
1557    #[tokio::test]
1558    async fn test_deprecation_warning() {
1559        let handler = TestHandler {
1560            result: "deprecated result".to_string(),
1561            should_fail: false,
1562        };
1563
1564        let deprecation = ToolDeprecation::new("This tool is outdated".to_string())
1565            .with_replacement("new_tool".to_string())
1566            .with_severity(DeprecationSeverity::High);
1567
1568        let tool = ToolBuilder::new("deprecated_tool")
1569            .deprecated(deprecation)
1570            .build(handler)
1571            .expect("Failed to build tool");
1572
1573        assert!(tool.is_deprecated());
1574        let warning = tool.deprecation_warning().unwrap();
1575        assert!(warning.contains("deprecated"));
1576        assert!(warning.contains("outdated"));
1577        assert!(warning.contains("new_tool"));
1578    }
1579
1580    #[tokio::test]
1581    async fn test_category_filtering() {
1582        let category = ToolCategory::new("file".to_string())
1583            .with_secondary("read".to_string())
1584            .with_tag("filesystem".to_string())
1585            .with_tag("utility".to_string());
1586
1587        let handler = TestHandler {
1588            result: "filtered result".to_string(),
1589            should_fail: false,
1590        };
1591
1592        let tool = ToolBuilder::new("filterable_tool")
1593            .category(category)
1594            .build(handler)
1595            .expect("Failed to build tool");
1596
1597        // Test primary category filter
1598        let filter = CategoryFilter::new().with_primary("file".to_string());
1599        assert!(tool.matches_category_filter(&filter));
1600
1601        let filter = CategoryFilter::new().with_primary("network".to_string());
1602        assert!(!tool.matches_category_filter(&filter));
1603
1604        // Test tag filter
1605        let filter = CategoryFilter::new().with_tag("filesystem".to_string());
1606        assert!(tool.matches_category_filter(&filter));
1607
1608        let filter = CategoryFilter::new().with_tag("nonexistent".to_string());
1609        assert!(!tool.matches_category_filter(&filter));
1610
1611        // Test secondary category filter
1612        let filter = CategoryFilter::new().with_secondary("read".to_string());
1613        assert!(tool.matches_category_filter(&filter));
1614
1615        let filter = CategoryFilter::new().with_secondary("write".to_string());
1616        assert!(!tool.matches_category_filter(&filter));
1617    }
1618
1619    #[tokio::test]
1620    async fn test_behavior_hints() {
1621        let hints = ToolBehaviorHints::new()
1622            .read_only()
1623            .idempotent()
1624            .cacheable()
1625            .requires_auth()
1626            .long_running()
1627            .resource_intensive();
1628
1629        let handler = TestHandler {
1630            result: "hints result".to_string(),
1631            should_fail: false,
1632        };
1633
1634        let tool = ToolBuilder::new("hints_tool")
1635            .behavior_hints(hints)
1636            .build(handler)
1637            .expect("Failed to build tool");
1638
1639        assert!(tool.is_read_only());
1640        assert!(tool.is_idempotent());
1641        assert!(tool.is_cacheable());
1642        assert!(tool.requires_auth());
1643        assert!(!tool.is_destructive());
1644
1645        let behavior_hints = tool.behavior_hints();
1646        assert_eq!(behavior_hints.read_only, Some(true));
1647        assert_eq!(behavior_hints.idempotent, Some(true));
1648        assert_eq!(behavior_hints.cacheable, Some(true));
1649        assert_eq!(behavior_hints.requires_auth, Some(true));
1650        assert_eq!(behavior_hints.long_running, Some(true));
1651        assert_eq!(behavior_hints.resource_intensive, Some(true));
1652        assert_eq!(behavior_hints.destructive, None);
1653    }
1654
1655    #[tokio::test]
1656    async fn test_tool_enabling_disabling() {
1657        let handler = TestHandler {
1658            result: "enabled result".to_string(),
1659            should_fail: false,
1660        };
1661
1662        let mut tool = ToolBuilder::new("enable_test")
1663            .build(handler)
1664            .expect("Failed to build tool");
1665
1666        assert!(tool.is_enabled());
1667
1668        // Disable tool
1669        tool.disable();
1670        assert!(!tool.is_enabled());
1671
1672        // Try to call disabled tool
1673        let result = tool.call(HashMap::new()).await;
1674        assert!(result.is_err());
1675        assert!(result.unwrap_err().to_string().contains("disabled"));
1676
1677        // Re-enable tool
1678        tool.enable();
1679        assert!(tool.is_enabled());
1680
1681        // Should work again
1682        let result = tool.call(HashMap::new()).await;
1683        assert!(result.is_ok());
1684    }
1685
1686    #[tokio::test]
1687    async fn test_custom_metadata() {
1688        let handler = TestHandler {
1689            result: "metadata result".to_string(),
1690            should_fail: false,
1691        };
1692
1693        let mut tool = ToolBuilder::new("metadata_tool")
1694            .custom_metadata("priority".to_string(), serde_json::Value::from("high"))
1695            .custom_metadata("team".to_string(), serde_json::Value::from("backend"))
1696            .build(handler)
1697            .expect("Failed to build tool");
1698
1699        assert_eq!(
1700            tool.get_custom_metadata("priority"),
1701            Some(&serde_json::Value::from("high"))
1702        );
1703        assert_eq!(
1704            tool.get_custom_metadata("team"),
1705            Some(&serde_json::Value::from("backend"))
1706        );
1707        assert_eq!(tool.get_custom_metadata("nonexistent"), None);
1708
1709        // Add metadata after creation
1710        tool.add_custom_metadata(
1711            "environment".to_string(),
1712            serde_json::Value::from("production"),
1713        );
1714        assert_eq!(
1715            tool.get_custom_metadata("environment"),
1716            Some(&serde_json::Value::from("production"))
1717        );
1718    }
1719
1720    #[test]
1721    fn test_tool_debug_format() {
1722        let handler = TestHandler {
1723            result: "debug result".to_string(),
1724            should_fail: false,
1725        };
1726
1727        let tool = ToolBuilder::new("debug_tool")
1728            .version("2.0.0")
1729            .category_simple("debug".to_string(), None)
1730            .build(handler)
1731            .expect("Failed to build tool");
1732
1733        let debug_str = format!("{tool:?}");
1734        assert!(debug_str.contains("debug_tool"));
1735        assert!(debug_str.contains("enabled"));
1736        assert!(debug_str.contains("execution_count"));
1737        assert!(debug_str.contains("success_rate"));
1738    }
1739}