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