turbomcp_protocol/types/
elicitation.rs

1//! User input elicitation types (MCP 2025-06-18)
2//!
3//! This module contains types for server-initiated user input requests,
4//! allowing servers to request structured input from users.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9/// Elicitation action taken by user
10#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
11#[serde(rename_all = "camelCase")]
12pub enum ElicitationAction {
13    /// User submitted the form/confirmed the action
14    Accept,
15    /// User explicitly declined the action
16    Decline,
17    /// User dismissed without making an explicit choice
18    Cancel,
19}
20
21/// Schema for elicitation input validation
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct ElicitationSchema {
24    /// Schema type (must be "object", required by MCP spec)
25    #[serde(rename = "type")]
26    pub schema_type: String,
27    /// Schema properties (required by MCP spec)
28    pub properties: HashMap<String, PrimitiveSchemaDefinition>,
29    /// Required properties
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub required: Option<Vec<String>>,
32    /// Additional properties allowed
33    #[serde(
34        rename = "additionalProperties",
35        skip_serializing_if = "Option::is_none"
36    )]
37    pub additional_properties: Option<bool>,
38}
39
40impl ElicitationSchema {
41    /// Create a new elicitation schema
42    pub fn new() -> Self {
43        Self {
44            schema_type: "object".to_string(),
45            properties: HashMap::new(),
46            required: Some(Vec::new()),
47            additional_properties: Some(false),
48        }
49    }
50
51    /// Add a string property to the schema
52    pub fn add_string_property(
53        mut self,
54        name: String,
55        required: bool,
56        description: Option<String>,
57    ) -> Self {
58        let property = PrimitiveSchemaDefinition::String {
59            title: None,
60            description,
61            format: None,
62            min_length: None,
63            max_length: None,
64            enum_values: None,
65            enum_names: None,
66        };
67
68        self.properties.insert(name.clone(), property);
69
70        if required && let Some(ref mut required_fields) = self.required {
71            required_fields.push(name);
72        }
73
74        self
75    }
76
77    /// Add a number property to the schema
78    pub fn add_number_property(
79        mut self,
80        name: String,
81        required: bool,
82        description: Option<String>,
83        minimum: Option<f64>,
84        maximum: Option<f64>,
85    ) -> Self {
86        let property = PrimitiveSchemaDefinition::Number {
87            title: None,
88            description,
89            minimum,
90            maximum,
91        };
92
93        self.properties.insert(name.clone(), property);
94
95        if required && let Some(ref mut required_fields) = self.required {
96            required_fields.push(name);
97        }
98
99        self
100    }
101
102    /// Add a boolean property to the schema
103    pub fn add_boolean_property(
104        mut self,
105        name: String,
106        required: bool,
107        description: Option<String>,
108        default: Option<bool>,
109    ) -> Self {
110        let property = PrimitiveSchemaDefinition::Boolean {
111            title: None,
112            description,
113            default,
114        };
115
116        self.properties.insert(name.clone(), property);
117
118        if required && let Some(ref mut required_fields) = self.required {
119            required_fields.push(name);
120        }
121
122        self
123    }
124}
125
126impl Default for ElicitationSchema {
127    fn default() -> Self {
128        Self::new()
129    }
130}
131
132/// Primitive schema definition for elicitation fields
133#[derive(Debug, Clone, Serialize, Deserialize)]
134#[serde(tag = "type")]
135pub enum PrimitiveSchemaDefinition {
136    /// String field schema definition
137    #[serde(rename = "string")]
138    String {
139        /// Field title
140        #[serde(skip_serializing_if = "Option::is_none")]
141        title: Option<String>,
142        /// Field description
143        #[serde(skip_serializing_if = "Option::is_none")]
144        description: Option<String>,
145        /// String format (email, uri, date, date-time, etc.)
146        #[serde(skip_serializing_if = "Option::is_none")]
147        format: Option<String>,
148        /// Minimum string length
149        #[serde(skip_serializing_if = "Option::is_none")]
150        #[serde(rename = "minLength")]
151        min_length: Option<u32>,
152        /// Maximum string length
153        #[serde(skip_serializing_if = "Option::is_none")]
154        #[serde(rename = "maxLength")]
155        max_length: Option<u32>,
156        /// Allowed enum values
157        #[serde(skip_serializing_if = "Option::is_none")]
158        #[serde(rename = "enum")]
159        enum_values: Option<Vec<String>>,
160        /// Display names for enum values
161        #[serde(skip_serializing_if = "Option::is_none")]
162        #[serde(rename = "enumNames")]
163        enum_names: Option<Vec<String>>,
164    },
165    /// Number field schema definition
166    #[serde(rename = "number")]
167    Number {
168        /// Field title
169        #[serde(skip_serializing_if = "Option::is_none")]
170        title: Option<String>,
171        /// Field description
172        #[serde(skip_serializing_if = "Option::is_none")]
173        description: Option<String>,
174        /// Minimum value
175        #[serde(skip_serializing_if = "Option::is_none")]
176        minimum: Option<f64>,
177        /// Maximum value
178        #[serde(skip_serializing_if = "Option::is_none")]
179        maximum: Option<f64>,
180    },
181    /// Integer field schema definition
182    #[serde(rename = "integer")]
183    Integer {
184        /// Field title
185        #[serde(skip_serializing_if = "Option::is_none")]
186        title: Option<String>,
187        /// Field description
188        #[serde(skip_serializing_if = "Option::is_none")]
189        description: Option<String>,
190        /// Minimum value
191        #[serde(skip_serializing_if = "Option::is_none")]
192        minimum: Option<i64>,
193        /// Maximum value
194        #[serde(skip_serializing_if = "Option::is_none")]
195        maximum: Option<i64>,
196    },
197    /// Boolean field schema definition
198    #[serde(rename = "boolean")]
199    Boolean {
200        /// Field title
201        #[serde(skip_serializing_if = "Option::is_none")]
202        title: Option<String>,
203        /// Field description
204        #[serde(skip_serializing_if = "Option::is_none")]
205        description: Option<String>,
206        /// Default value
207        #[serde(skip_serializing_if = "Option::is_none")]
208        default: Option<bool>,
209    },
210}
211
212/// Elicit request parameters
213#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct ElicitRequestParams {
215    /// Human-readable message for the user
216    pub message: String,
217    /// Schema for input validation (per MCP specification)
218    #[serde(rename = "requestedSchema")]
219    pub schema: ElicitationSchema,
220    /// Optional timeout in milliseconds
221    #[serde(rename = "timeoutMs", skip_serializing_if = "Option::is_none")]
222    pub timeout_ms: Option<u32>,
223    /// Whether the request can be cancelled
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub cancellable: Option<bool>,
226}
227
228/// Elicit request wrapper
229#[derive(Debug, Clone, Serialize, Deserialize)]
230pub struct ElicitRequest {
231    /// Elicitation parameters
232    #[serde(flatten)]
233    pub params: ElicitRequestParams,
234    /// Optional metadata per MCP 2025-06-18 specification
235    #[serde(skip_serializing_if = "Option::is_none")]
236    pub _meta: Option<serde_json::Value>,
237}
238
239/// Elicit result
240#[derive(Debug, Clone, Serialize, Deserialize)]
241pub struct ElicitResult {
242    /// The action taken by the user
243    pub action: ElicitationAction,
244    /// User input content (if action was Accept) - per MCP specification
245    #[serde(skip_serializing_if = "Option::is_none")]
246    pub content: Option<std::collections::HashMap<String, serde_json::Value>>,
247    /// Optional metadata per MCP 2025-06-18 specification
248    #[serde(skip_serializing_if = "Option::is_none")]
249    pub _meta: Option<serde_json::Value>,
250}