mcp_kit/types/
elicitation.rs1use serde::{Deserialize, Serialize};
36use serde_json::Value;
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
40#[serde(rename_all = "camelCase")]
41pub struct ElicitRequest {
42 pub message: String,
44
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub requested_schema: Option<ElicitSchema>,
48}
49
50impl ElicitRequest {
51 pub fn new(message: impl Into<String>) -> Self {
53 Self {
54 message: message.into(),
55 requested_schema: None,
56 }
57 }
58
59 pub fn with_schema(message: impl Into<String>, schema: ElicitSchema) -> Self {
61 Self {
62 message: message.into(),
63 requested_schema: Some(schema),
64 }
65 }
66
67 pub fn confirm(message: impl Into<String>) -> Self {
69 Self::with_schema(message, ElicitSchema::boolean())
70 }
71
72 pub fn text(message: impl Into<String>) -> Self {
74 Self::with_schema(message, ElicitSchema::string())
75 }
76
77 pub fn choice(message: impl Into<String>, options: Vec<String>) -> Self {
79 Self::with_schema(message, ElicitSchema::enum_values(options))
80 }
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct ElicitSchema {
86 #[serde(flatten)]
88 pub schema: Value,
89}
90
91impl ElicitSchema {
92 pub fn new(schema: Value) -> Self {
94 Self { schema }
95 }
96
97 pub fn boolean() -> Self {
99 Self::new(serde_json::json!({
100 "type": "boolean",
101 "description": "Confirmation response (true/false)"
102 }))
103 }
104
105 pub fn string() -> Self {
107 Self::new(serde_json::json!({
108 "type": "string"
109 }))
110 }
111
112 pub fn string_with_desc(description: impl Into<String>) -> Self {
114 Self::new(serde_json::json!({
115 "type": "string",
116 "description": description.into()
117 }))
118 }
119
120 pub fn enum_values(values: Vec<String>) -> Self {
122 Self::new(serde_json::json!({
123 "type": "string",
124 "enum": values
125 }))
126 }
127
128 pub fn number() -> Self {
130 Self::new(serde_json::json!({
131 "type": "number"
132 }))
133 }
134
135 pub fn number_range(min: f64, max: f64) -> Self {
137 Self::new(serde_json::json!({
138 "type": "number",
139 "minimum": min,
140 "maximum": max
141 }))
142 }
143
144 pub fn integer() -> Self {
146 Self::new(serde_json::json!({
147 "type": "integer"
148 }))
149 }
150
151 pub fn object(schema: Value) -> Self {
153 Self::new(schema)
154 }
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
159#[serde(rename_all = "camelCase")]
160pub struct ElicitResult {
161 pub action: ElicitAction,
163
164 #[serde(skip_serializing_if = "Option::is_none")]
166 pub content: Option<Value>,
167}
168
169impl ElicitResult {
170 pub fn accepted(content: Value) -> Self {
172 Self {
173 action: ElicitAction::Accepted,
174 content: Some(content),
175 }
176 }
177
178 pub fn declined() -> Self {
180 Self {
181 action: ElicitAction::Declined,
182 content: None,
183 }
184 }
185
186 pub fn cancelled() -> Self {
188 Self {
189 action: ElicitAction::Cancelled,
190 content: None,
191 }
192 }
193
194 pub fn is_accepted(&self) -> bool {
196 matches!(self.action, ElicitAction::Accepted)
197 }
198
199 pub fn is_declined(&self) -> bool {
201 matches!(self.action, ElicitAction::Declined)
202 }
203
204 pub fn is_cancelled(&self) -> bool {
206 matches!(self.action, ElicitAction::Cancelled)
207 }
208
209 pub fn content_as<T: serde::de::DeserializeOwned>(&self) -> Option<T> {
211 self.content
212 .as_ref()
213 .and_then(|v| serde_json::from_value(v.clone()).ok())
214 }
215
216 pub fn as_bool(&self) -> Option<bool> {
218 self.content_as()
219 }
220
221 pub fn as_string(&self) -> Option<String> {
223 self.content_as()
224 }
225}
226
227#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
229#[serde(rename_all = "lowercase")]
230pub enum ElicitAction {
231 Accepted,
233 Declined,
235 Cancelled,
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242
243 #[test]
244 fn test_confirm_request() {
245 let req = ElicitRequest::confirm("Delete all files?");
246 assert_eq!(req.message, "Delete all files?");
247 assert!(req.requested_schema.is_some());
248 }
249
250 #[test]
251 fn test_choice_request() {
252 let req = ElicitRequest::choice(
253 "Select format",
254 vec!["json".into(), "yaml".into(), "toml".into()],
255 );
256 assert_eq!(req.message, "Select format");
257 }
258
259 #[test]
260 fn test_result_accepted() {
261 let result = ElicitResult::accepted(serde_json::json!(true));
262 assert!(result.is_accepted());
263 assert_eq!(result.as_bool(), Some(true));
264 }
265
266 #[test]
267 fn test_result_declined() {
268 let result = ElicitResult::declined();
269 assert!(result.is_declined());
270 assert!(result.content.is_none());
271 }
272}