Skip to main content

turul_mcp_builders/traits/
resource_traits.rs

1//! Framework traits for MCP resource construction
2//!
3//! **IMPORTANT**: These are framework features, NOT part of the MCP specification.
4//! The MCP specification defines concrete types only.
5
6use serde_json::Value;
7use std::collections::HashMap;
8
9// Import protocol types (spec-defined)
10use turul_mcp_protocol::Resource;
11use turul_mcp_protocol::meta::Annotations;
12
13pub trait HasResourceMetadata {
14    /// Programmatic identifier (fallback display name)
15    fn name(&self) -> &str;
16
17    /// Human-readable display name (UI contexts)
18    fn title(&self) -> Option<&str> {
19        None
20    }
21}
22
23/// Resource description trait
24pub trait HasResourceDescription {
25    fn description(&self) -> Option<&str> {
26        None
27    }
28}
29
30/// Resource URI trait
31pub trait HasResourceUri {
32    fn uri(&self) -> &str;
33}
34
35/// Resource MIME type trait
36pub trait HasResourceMimeType {
37    fn mime_type(&self) -> Option<&str> {
38        None
39    }
40}
41
42/// Resource size trait
43pub trait HasResourceSize {
44    fn size(&self) -> Option<u64> {
45        None
46    }
47}
48
49/// Resource annotations trait
50pub trait HasResourceAnnotations {
51    fn annotations(&self) -> Option<&Annotations> {
52        None
53    }
54}
55
56/// Resource-specific meta trait (separate from RPC _meta)
57pub trait HasResourceMeta {
58    fn resource_meta(&self) -> Option<&HashMap<String, Value>> {
59        None
60    }
61}
62
63/// **Complete MCP Resource Creation** - Build readable resources that clients can access.
64///
65/// This trait represents a **complete, working MCP resource** that can be registered with a server
66/// and accessed by clients. When you implement the required metadata traits, you automatically
67/// get `ResourceDefinition` for free via blanket implementation.
68///
69/// ## What This Enables
70///
71/// Resources implementing `ResourceDefinition` become **full MCP citizens** that are:
72/// - 🔍 **Discoverable** via `resources/list` requests
73/// - 📖 **Readable** via `resources/read` requests
74/// - 🎯 **Template-capable** with URI variable substitution
75/// - 📡 **Protocol-ready** for JSON-RPC communication
76///
77/// ## Complete Working Example
78///
79/// ```rust
80/// use turul_mcp_protocol::Resource;
81/// use turul_mcp_protocol::meta::Annotations;
82/// use turul_mcp_builders::prelude::*;  // Import framework traits
83/// use std::collections::HashMap;
84///
85/// // This struct will automatically implement ResourceDefinition!
86/// struct ApiDataFeed {
87///     uri: String,
88/// }
89///
90/// impl ApiDataFeed {
91///     fn new() -> Self {
92///         Self {
93///             uri: "https://api.example.com/data/{dataset}".to_string(),
94///         }
95///     }
96/// }
97///
98/// impl HasResourceMetadata for ApiDataFeed {
99///     fn name(&self) -> &str { "api_data" }
100///     fn title(&self) -> Option<&str> { Some("Live API Data Feed") }
101/// }
102///
103/// impl HasResourceDescription for ApiDataFeed {
104///     fn description(&self) -> Option<&str> {
105///         Some("Real-time data feed from external API")
106///     }
107/// }
108///
109/// impl HasResourceUri for ApiDataFeed {
110///     fn uri(&self) -> &str { &self.uri }
111/// }
112///
113/// impl HasResourceMimeType for ApiDataFeed {
114///     fn mime_type(&self) -> Option<&str> { Some("application/json") }
115/// }
116///
117/// impl HasResourceSize for ApiDataFeed {
118///     fn size(&self) -> Option<u64> { None }
119/// }
120///
121/// impl HasResourceAnnotations for ApiDataFeed {
122///     fn annotations(&self) -> Option<&Annotations> { None }
123/// }
124///
125/// impl HasResourceMeta for ApiDataFeed {
126///     fn resource_meta(&self) -> Option<&HashMap<String, serde_json::Value>> { None }
127/// }
128///
129/// // 🎉 ApiDataFeed now automatically implements ResourceDefinition!
130/// let resource = ApiDataFeed::new();
131/// assert_eq!(resource.name(), "api_data");
132/// assert_eq!(resource.mime_type(), Some("application/json"));
133/// ```
134///
135/// ## Usage Patterns
136///
137/// ### Easy: Use Derive Macros (see turul-mcp-derive crate)
138/// ```rust
139/// // Example of manual implementation without macros
140/// use turul_mcp_protocol::Resource;
141/// use turul_mcp_protocol::meta::Annotations;
142/// use turul_mcp_builders::prelude::*;  // Import framework traits
143/// use std::collections::HashMap;
144///
145/// struct LogFiles;
146///
147/// impl HasResourceMetadata for LogFiles {
148///     fn name(&self) -> &str { "logs" }
149///     fn title(&self) -> Option<&str> { None }
150/// }
151///
152/// impl HasResourceDescription for LogFiles {
153///     fn description(&self) -> Option<&str> { None }
154/// }
155///
156/// impl HasResourceUri for LogFiles {
157///     fn uri(&self) -> &str { "file:///var/log/{service}.log" }
158/// }
159///
160/// impl HasResourceMimeType for LogFiles {
161///     fn mime_type(&self) -> Option<&str> { None }
162/// }
163///
164/// impl HasResourceSize for LogFiles {
165///     fn size(&self) -> Option<u64> { None }
166/// }
167///
168/// impl HasResourceAnnotations for LogFiles {
169///     fn annotations(&self) -> Option<&Annotations> { None }
170/// }
171///
172/// impl HasResourceMeta for LogFiles {
173///     fn resource_meta(&self) -> Option<&HashMap<String, serde_json::Value>> { None }
174/// }
175///
176/// let resource = LogFiles;
177/// assert_eq!(resource.name(), "logs");
178/// ```
179///
180/// ### Advanced: Manual Implementation (shown above)
181/// Perfect when you need dynamic URIs, custom metadata, or complex content handling.
182///
183/// ## Real-World Resource Ideas
184///
185/// - **File Resources**: Config files, logs, documentation, data exports
186/// - **API Resources**: REST endpoints, GraphQL schemas, webhook data
187/// - **Data Resources**: Database views, CSV exports, JSON feeds, report data
188/// - **System Resources**: Process info, system stats, environment configs
189/// - **Template Resources**: Dynamic content with `{variable}` substitution
190///
191/// ## URI Template Power
192///
193/// Resources support powerful URI templating:
194/// ```text
195/// Static:   "file:///config.json"              → Single resource
196/// Template: "file:///logs/{service}.log"       → Multiple resources
197/// Complex:  "api://data/{type}/{id}?fmt={fmt}" → Full parameterization
198/// ```
199///
200/// ## How It Works in MCP
201///
202/// 1. **Registration**: Server registers your resource during startup
203/// 2. **Discovery**: Client calls `resources/list` → sees available resources
204/// 3. **Template Resolution**: Client expands URI templates with parameters
205/// 4. **Reading**: Client calls `resources/read` with resolved URI
206/// 5. **Content Delivery**: Your resource returns actual content
207/// 6. **Response**: Framework serializes content back to client
208///
209/// The framework handles URI template parsing, parameter validation, and protocol serialization!
210pub trait ResourceDefinition:
211    HasResourceMetadata +       // name, title (from BaseMetadata)
212    HasResourceDescription +    // description
213    HasResourceUri +           // uri
214    HasResourceMimeType +      // mimeType
215    HasResourceSize +          // size
216    HasResourceAnnotations +   // annotations
217    HasResourceMeta +          // _meta (resource-specific)
218    super::icon_traits::HasIcons + // icons (MCP 2025-11-25)
219    Send +
220    Sync
221{
222    /// Display name precedence: title > name (matches TypeScript spec)
223    fn display_name(&self) -> &str {
224        self.title().unwrap_or_else(|| self.name())
225    }
226
227    /// Convert to concrete Resource struct for protocol serialization
228    fn to_resource(&self) -> Resource {
229        Resource {
230            uri: self.uri().to_string(),
231            name: self.name().to_string(),
232            title: self.title().map(String::from),
233            description: self.description().map(String::from),
234            mime_type: self.mime_type().map(String::from),
235            size: self.size(),
236            annotations: self.annotations().cloned(),
237            icons: self.icons().cloned(),
238            meta: self.resource_meta().cloned(),
239        }
240    }
241}
242impl<T> ResourceDefinition for T where
243    T: HasResourceMetadata
244        + HasResourceDescription
245        + HasResourceUri
246        + HasResourceMimeType
247        + HasResourceSize
248        + HasResourceAnnotations
249        + HasResourceMeta
250        + super::icon_traits::HasIcons
251        + Send
252        + Sync
253{
254}