things3_cli/mcp/middleware/
validation.rs1use super::{McpMiddleware, MiddlewareContext, MiddlewareResult};
4use crate::mcp::{CallToolRequest, McpError, McpResult};
5
6pub struct ValidationMiddleware {
7 strict_mode: bool,
8}
9
10impl ValidationMiddleware {
11 #[must_use]
13 pub fn new(strict_mode: bool) -> Self {
14 Self { strict_mode }
15 }
16
17 #[must_use]
19 pub fn strict() -> Self {
20 Self::new(true)
21 }
22
23 #[must_use]
25 pub fn lenient() -> Self {
26 Self::new(false)
27 }
28
29 fn validate_request(&self, request: &CallToolRequest) -> McpResult<()> {
30 if request.name.is_empty() {
32 return Err(McpError::validation_error("Tool name cannot be empty"));
33 }
34
35 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 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 }
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}