Skip to main content

things3_cli/mcp/middleware/
validation.rs

1//! Validation middleware
2
3use super::{McpMiddleware, MiddlewareContext, MiddlewareResult};
4use crate::mcp::{CallToolRequest, McpError, McpResult};
5
6pub struct ValidationMiddleware {
7    strict_mode: bool,
8}
9
10impl ValidationMiddleware {
11    /// Create a new validation middleware
12    #[must_use]
13    pub fn new(strict_mode: bool) -> Self {
14        Self { strict_mode }
15    }
16
17    /// Create with strict mode enabled
18    #[must_use]
19    pub fn strict() -> Self {
20        Self::new(true)
21    }
22
23    /// Create with strict mode disabled
24    #[must_use]
25    pub fn lenient() -> Self {
26        Self::new(false)
27    }
28
29    fn validate_request(&self, request: &CallToolRequest) -> McpResult<()> {
30        // Basic validation
31        if request.name.is_empty() {
32            return Err(McpError::validation_error("Tool name cannot be empty"));
33        }
34
35        // Validate tool name format (alphanumeric and underscores only)
36        if !request
37            .name
38            .chars()
39            .all(|c| c.is_alphanumeric() || c == '_')
40        {
41            return Err(McpError::validation_error(
42                "Tool name must contain only alphanumeric characters and underscores",
43            ));
44        }
45
46        // In strict mode, validate arguments structure
47        if self.strict_mode {
48            if let Some(args) = &request.arguments {
49                if !args.is_object() {
50                    return Err(McpError::validation_error(
51                        "Arguments must be a JSON object",
52                    ));
53                }
54            }
55        }
56
57        Ok(())
58    }
59}
60
61#[async_trait::async_trait]
62impl McpMiddleware for ValidationMiddleware {
63    fn name(&self) -> &'static str {
64        "validation"
65    }
66
67    fn priority(&self) -> i32 {
68        50 // Medium priority
69    }
70
71    async fn before_request(
72        &self,
73        request: &CallToolRequest,
74        context: &mut MiddlewareContext,
75    ) -> McpResult<MiddlewareResult> {
76        if let Err(error) = self.validate_request(request) {
77            context.set_metadata(
78                "validation_error".to_string(),
79                serde_json::Value::String(error.to_string()),
80            );
81            return Ok(MiddlewareResult::Error(error));
82        }
83
84        context.set_metadata("validated".to_string(), serde_json::Value::Bool(true));
85        Ok(MiddlewareResult::Continue)
86    }
87}