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