Skip to main content

turul_mcp_builders/traits/
root_traits.rs

1//! Framework traits for MCP root construction
2//!
3//! **IMPORTANT**: These are framework features, NOT part of the MCP specification.
4
5// Implements the SEP-2577-deprecated-but-present roots feature; the `Root` protocol
6// type is deprecated in 2026-07-28 but still valid and intentionally supported here.
7#![allow(deprecated)]
8
9use serde_json::Value;
10use std::collections::HashMap;
11use turul_mcp_protocol::roots::Root;
12
13pub trait HasRootMetadata {
14    /// The root URI (must start with "file://")
15    fn uri(&self) -> &str;
16
17    /// Optional human-readable name
18    fn name(&self) -> Option<&str> {
19        None
20    }
21
22    /// Optional description or additional metadata
23    fn description(&self) -> Option<&str> {
24        None
25    }
26}
27
28/// Trait for root permissions and security
29pub trait HasRootPermissions {
30    /// Check if read access is allowed for this path
31    fn can_read(&self, _path: &str) -> bool {
32        true
33    }
34
35    /// Check if write access is allowed for this path
36    fn can_write(&self, _path: &str) -> bool {
37        false // Default: read-only
38    }
39
40    /// Get maximum depth for directory traversal
41    fn max_depth(&self) -> Option<usize> {
42        None // No limit by default
43    }
44}
45
46/// Trait for root filtering and exclusions
47pub trait HasRootFiltering {
48    /// File extensions to include (None = all)
49    fn allowed_extensions(&self) -> Option<&[String]> {
50        None
51    }
52
53    /// File patterns to exclude (glob patterns)
54    fn excluded_patterns(&self) -> Option<&[String]> {
55        None
56    }
57
58    /// Check if a file should be included
59    fn should_include(&self, path: &str) -> bool {
60        // Default: include everything unless filtered
61        if let Some(patterns) = self.excluded_patterns() {
62            for pattern in patterns {
63                if path.contains(pattern) {
64                    return false;
65                }
66            }
67        }
68
69        if let Some(extensions) = self.allowed_extensions() {
70            if let Some(ext) = path.split('.').next_back() {
71                return extensions.contains(&ext.to_string());
72            }
73            return false;
74        }
75
76        true
77    }
78}
79
80/// Trait for root annotations and custom metadata
81pub trait HasRootAnnotations {
82    /// Get custom metadata
83    fn annotations(&self) -> Option<&HashMap<String, Value>> {
84        None
85    }
86
87    /// Get root-specific tags or labels
88    fn tags(&self) -> Option<&[String]> {
89        None
90    }
91}
92
93/// **Complete MCP Root Creation** - Build secure file system access boundaries.
94///
95/// This trait represents a **complete, working MCP root** that defines secure access
96/// boundaries for file system operations with permissions, filtering, and metadata.
97/// When you implement the required metadata traits, you automatically get
98/// `RootDefinition` for free via blanket implementation.
99///
100/// # What You're Building
101///
102/// A root is a secure file system boundary that:
103/// - Defines accessible file system paths for clients
104/// - Enforces security permissions and access control
105/// - Filters files and directories based on rules
106/// - Provides metadata annotations for client context
107///
108/// # How to Create a Root
109///
110/// Implement these four traits on your struct:
111///
112/// ```rust
113/// # use turul_mcp_protocol::roots::*;
114/// # use turul_mcp_builders::prelude::*;
115/// # use serde_json::{Value, json};
116/// # use std::collections::HashMap;
117///
118/// // This struct will automatically implement RootDefinition!
119/// struct ProjectRoot {
120///     base_path: String,
121///     project_name: String,
122/// }
123///
124/// impl HasRootMetadata for ProjectRoot {
125///     fn uri(&self) -> &str {
126///         &self.base_path
127///     }
128///
129///     fn name(&self) -> Option<&str> {
130///         Some(&self.project_name)
131///     }
132/// }
133///
134/// impl HasRootPermissions for ProjectRoot {
135///     fn can_read(&self, _path: &str) -> bool {
136///         true // Allow reading all files in project
137///     }
138///
139///     fn can_write(&self, path: &str) -> bool {
140///         // Only allow writing to src/ and tests/ directories
141///         path.contains("/src/") || path.contains("/tests/")
142///     }
143///
144///     fn max_depth(&self) -> Option<usize> {
145///         Some(10) // Limit depth to prevent infinite recursion
146///     }
147/// }
148///
149/// impl HasRootFiltering for ProjectRoot {
150///     fn excluded_patterns(&self) -> Option<&[String]> {
151///         static PATTERNS: &[String] = &[];
152///         None // Use default filtering
153///     }
154///
155///     fn should_include(&self, path: &str) -> bool {
156///         // Exclude hidden files and build artifacts
157///         !path.contains("/.") && !path.contains("/target/")
158///     }
159/// }
160///
161/// impl HasRootAnnotations for ProjectRoot {
162///     fn annotations(&self) -> Option<&HashMap<String, Value>> {
163///         // Static annotations for this example
164///         None
165///     }
166/// }
167///
168/// // Now you can use it with the server:
169/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
170/// let root = ProjectRoot {
171///     base_path: "file:///workspace/my-project".to_string(),
172///     project_name: "My Rust Project".to_string(),
173/// };
174///
175/// // The root automatically implements RootDefinition
176/// let protocol_root = root.to_root();
177/// let validation_result = root.validate();
178/// # Ok(())
179/// # }
180/// ```
181///
182/// # Key Benefits
183///
184/// - **Security**: Fine-grained access control for file operations
185/// - **Filtering**: Automatic exclusion of unwanted files/directories
186/// - **Metadata**: Rich annotations for client context
187/// - **MCP Compliant**: Fully compatible with MCP 2025-11-25 specification
188///
189/// # Common Use Cases
190///
191/// - Project workspace boundaries
192/// - Secure document repositories
193/// - Code review access control
194/// - Filtered file system views
195/// - Multi-tenant file access
196pub trait RootDefinition:
197    HasRootMetadata + HasRootPermissions + HasRootFiltering + HasRootAnnotations
198{
199    /// Convert this root definition to a protocol Root
200    fn to_root(&self) -> Root {
201        let mut root = Root::new(self.uri());
202        if let Some(name) = self.name() {
203            root = root.with_name(name);
204        }
205        if let Some(annotations) = self.annotations() {
206            root = root.with_meta(annotations.clone());
207        }
208        root
209    }
210
211    /// Validate this root definition
212    fn validate(&self) -> Result<(), String> {
213        if !self.uri().starts_with("file://") {
214            return Err("Root URI must start with 'file://'".to_string());
215        }
216        Ok(())
217    }
218}
219
220// Blanket implementation: any type implementing the fine-grained traits automatically gets RootDefinition
221impl<T> RootDefinition for T where
222    T: HasRootMetadata + HasRootPermissions + HasRootFiltering + HasRootAnnotations
223{
224}