turul_mcp_builders/traits/elicitation_traits.rs
1//! Framework traits for MCP elicitation construction
2//!
3//! **IMPORTANT**: These are framework features, NOT part of the MCP specification.
4
5use turul_mcp_protocol::elicitation::{ElicitCreateRequest, ElicitationSchema};
6use serde_json::Value;
7use std::collections::HashMap;
8
9pub trait HasElicitationMetadata {
10 /// The message to present to the user
11 fn message(&self) -> &str;
12
13 /// Optional title for the elicitation dialog
14 fn title(&self) -> Option<&str> {
15 None
16 }
17}
18
19/// Trait for elicitation schema definition (restricted to primitive types per MCP spec)
20pub trait HasElicitationSchema {
21 /// Restricted schema defining structure of input to collect (primitives only)
22 fn requested_schema(&self) -> &ElicitationSchema;
23
24 /// Validate that schema only contains primitive types (per MCP spec)
25 fn validate_schema(&self) -> Result<(), String> {
26 // All schemas in ElicitationSchema are already primitive-only by design
27 Ok(())
28 }
29}
30
31/// Trait for elicitation validation and handling
32pub trait HasElicitationHandling {
33 /// Validate submitted content against the schema
34 fn validate_content(&self, _content: &HashMap<String, Value>) -> Result<(), String> {
35 // Basic validation - can be extended
36 Ok(())
37 }
38
39 /// Process accepted content (transform, normalize, etc.)
40 fn process_content(
41 &self,
42 content: HashMap<String, Value>,
43 ) -> Result<HashMap<String, Value>, String> {
44 Ok(content)
45 }
46}
47
48/// **Complete MCP Elicitation Creation** - Build schema-validated user input collection systems.
49///
50/// This trait represents a **complete, working MCP elicitation** that can prompt users
51/// for structured input and validate their responses against JSON schemas. When you implement
52/// the required metadata traits, you automatically get `ElicitationDefinition` for free
53/// via blanket implementation.
54///
55/// # What You're Building
56///
57/// An elicitation is a sophisticated user input system that:
58/// - Presents a clear message/prompt to the user
59/// - Defines a JSON schema for expected input structure
60/// - Validates user responses against that schema
61/// - Processes the validated data for your application
62///
63/// # How to Create an Elicitation
64///
65/// Implement these three traits on your struct:
66///
67/// ```rust
68/// # use turul_mcp_protocol::elicitation::*;
69/// # use turul_mcp_builders::prelude::*;
70/// # use serde_json::Value;
71/// # use std::collections::HashMap;
72///
73/// // This struct will automatically implement ElicitationDefinition!
74/// struct UserPreferencesForm {
75/// context: String,
76/// schema: ElicitationSchema,
77/// }
78///
79/// impl UserPreferencesForm {
80/// fn new(context: String) -> Self {
81/// let mut properties = HashMap::new();
82/// properties.insert("theme".to_string(), PrimitiveSchemaDefinition::Enum(EnumSchema {
83/// schema_type: "string".to_string(),
84/// title: None,
85/// description: Some("UI theme preference".to_string()),
86/// enum_values: vec!["dark".to_string(), "light".to_string()],
87/// enum_names: None,
88/// }));
89/// properties.insert("notifications".to_string(), PrimitiveSchemaDefinition::Boolean(BooleanSchema {
90/// schema_type: "boolean".to_string(),
91/// title: None,
92/// description: Some("Enable notifications".to_string()),
93/// default: Some(true),
94/// }));
95/// properties.insert("max_items".to_string(), PrimitiveSchemaDefinition::Number(NumberSchema {
96/// schema_type: "number".to_string(),
97/// title: None,
98/// description: Some("Maximum items to display".to_string()),
99/// minimum: Some(1.0),
100/// maximum: Some(100.0),
101/// }));
102///
103/// let schema = ElicitationSchema {
104/// schema_type: "object".to_string(),
105/// properties,
106/// required: Some(vec!["theme".to_string()]),
107/// };
108///
109/// Self { context, schema }
110/// }
111/// }
112///
113/// impl HasElicitationMetadata for UserPreferencesForm {
114/// fn message(&self) -> &str {
115/// "Please configure your preferences for this project"
116/// }
117/// }
118///
119/// impl HasElicitationSchema for UserPreferencesForm {
120/// fn requested_schema(&self) -> &ElicitationSchema {
121/// &self.schema
122/// }
123/// }
124///
125/// impl HasElicitationHandling for UserPreferencesForm {
126/// fn process_content(&self, content: HashMap<String, Value>) -> Result<HashMap<String, Value>, String> {
127/// // Validate that theme is acceptable
128/// if let Some(theme) = content.get("theme") {
129/// if !["dark", "light"].contains(&theme.as_str().unwrap_or("")) {
130/// return Err("Theme must be 'dark' or 'light'".to_string());
131/// }
132/// }
133///
134/// // Process and potentially transform the data
135/// let mut processed = content.clone();
136/// processed.insert("processed_at".to_string(), Value::String("2024-01-01T00:00:00Z".to_string()));
137/// Ok(processed)
138/// }
139/// }
140///
141/// // Now you can use it with the server:
142/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
143/// let form = UserPreferencesForm::new("project-setup".to_string());
144///
145/// // The elicitation automatically implements ElicitationDefinition
146/// let create_request = form.to_create_request();
147/// # Ok(())
148/// # }
149/// ```
150///
151/// # Key Benefits
152///
153/// - **Type Safety**: Schema validation happens at the protocol level
154/// - **Automatic Implementation**: Just implement the three component traits
155/// - **Flexible Processing**: Handle and transform user input as needed
156/// - **MCP Compliant**: Fully compatible with MCP 2025-06-18 specification
157///
158/// # Common Use Cases
159///
160/// - Configuration forms with validation
161/// - User preference collection
162/// - Survey and feedback systems
163/// - Structured data entry workflows
164/// - Multi-step input wizards
165pub trait ElicitationDefinition:
166 HasElicitationMetadata + HasElicitationSchema + HasElicitationHandling
167{
168 /// Convert this elicitation definition to a protocol ElicitCreateRequest
169 fn to_create_request(&self) -> ElicitCreateRequest {
170 ElicitCreateRequest::new(self.message(), self.requested_schema().clone())
171 }
172}
173
174// Blanket implementation: any type implementing the fine-grained traits automatically gets ElicitationDefinition
175impl<T> ElicitationDefinition for T where
176 T: HasElicitationMetadata + HasElicitationSchema + HasElicitationHandling
177{
178}