mcpkit_server/capability/
elicitation.rs1use crate::context::Context;
7use mcpkit_core::error::McpError;
8use mcpkit_core::types::elicitation::{
9 ElicitRequest, ElicitResult, ElicitationSchema, ElicitAction,
10};
11use serde_json::Value;
12use std::future::Future;
13use std::pin::Pin;
14
15pub type BoxedElicitationFn = Box<
17 dyn for<'a> Fn(
18 ElicitRequest,
19 &'a Context<'a>,
20 ) -> Pin<Box<dyn Future<Output = Result<ElicitResult, McpError>> + Send + 'a>>
21 + Send
22 + Sync,
23>;
24
25pub struct ElicitationService {
30 handler: Option<BoxedElicitationFn>,
31}
32
33impl Default for ElicitationService {
34 fn default() -> Self {
35 Self::new()
36 }
37}
38
39impl ElicitationService {
40 pub fn new() -> Self {
42 Self { handler: None }
43 }
44
45 pub fn with_handler<F, Fut>(mut self, handler: F) -> Self
47 where
48 F: Fn(ElicitRequest, &Context<'_>) -> Fut + Send + Sync + 'static,
49 Fut: Future<Output = Result<ElicitResult, McpError>> + Send + 'static,
50 {
51 self.handler = Some(Box::new(move |req, ctx| Box::pin(handler(req, ctx))));
52 self
53 }
54
55 pub fn is_supported(&self) -> bool {
57 self.handler.is_some()
58 }
59
60 pub async fn elicit(
62 &self,
63 request: ElicitRequest,
64 ctx: &Context<'_>,
65 ) -> Result<ElicitResult, McpError> {
66 let handler = self.handler.as_ref().ok_or_else(|| {
67 McpError::invalid_request("Elicitation not supported")
68 })?;
69
70 (handler)(request, ctx).await
71 }
72}
73
74pub struct ElicitationRequestBuilder {
76 message: String,
77 schema: Option<ElicitationSchema>,
78}
79
80impl ElicitationRequestBuilder {
81 pub fn new(message: impl Into<String>) -> Self {
83 Self {
84 message: message.into(),
85 schema: None,
86 }
87 }
88
89 pub fn schema(mut self, schema: ElicitationSchema) -> Self {
91 self.schema = Some(schema);
92 self
93 }
94
95 pub fn text_response(self, field_name: impl Into<String>) -> Self {
97 self.schema(
98 ElicitationSchema::object()
99 .property(field_name, mcpkit_core::types::elicitation::PropertySchema::string()),
100 )
101 }
102
103 pub fn boolean_response(self, field_name: impl Into<String>) -> Self {
105 self.schema(
106 ElicitationSchema::object()
107 .property(field_name, mcpkit_core::types::elicitation::PropertySchema::boolean()),
108 )
109 }
110
111 pub fn build(self) -> ElicitRequest {
113 ElicitRequest {
114 message: self.message,
115 requested_schema: self.schema.unwrap_or_else(ElicitationSchema::object),
116 }
117 }
118}
119
120pub struct ElicitationResultBuilder {
122 action: ElicitAction,
123 content: Option<serde_json::Map<String, Value>>,
124}
125
126impl ElicitationResultBuilder {
127 pub fn new() -> Self {
129 Self {
130 action: ElicitAction::Accept,
131 content: None,
132 }
133 }
134
135 pub fn accepted(mut self, content: Value) -> Self {
137 self.action = ElicitAction::Accept;
138 self.content = match content {
140 Value::Object(map) => Some(map),
141 other => {
142 let mut map = serde_json::Map::new();
143 map.insert("value".to_string(), other);
144 Some(map)
145 }
146 };
147 self
148 }
149
150 pub fn accepted_map(mut self, content: serde_json::Map<String, Value>) -> Self {
152 self.action = ElicitAction::Accept;
153 self.content = Some(content);
154 self
155 }
156
157 pub fn declined(mut self) -> Self {
159 self.action = ElicitAction::Decline;
160 self.content = None;
161 self
162 }
163
164 pub fn cancelled(mut self) -> Self {
166 self.action = ElicitAction::Cancel;
167 self.content = None;
168 self
169 }
170
171 pub fn build(self) -> ElicitResult {
173 ElicitResult {
174 action: self.action,
175 content: self.content,
176 }
177 }
178}
179
180impl Default for ElicitationResultBuilder {
181 fn default() -> Self {
182 Self::new()
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn test_elicitation_request_builder() {
192 let request = ElicitationRequestBuilder::new("Please enter your name")
193 .text_response("name")
194 .build();
195
196 assert_eq!(request.message, "Please enter your name");
197 }
198
199 #[test]
200 fn test_elicitation_result_builder() {
201 let result = ElicitationResultBuilder::new()
202 .accepted(serde_json::json!({"name": "John Doe"}))
203 .build();
204
205 assert_eq!(result.action, ElicitAction::Accept);
206 assert!(result.content.is_some());
207 }
208
209 #[test]
210 fn test_elicitation_service_default() {
211 let service = ElicitationService::new();
212 assert!(!service.is_supported());
213 }
214
215 #[tokio::test]
216 async fn test_elicitation_service_with_handler() {
217 let service = ElicitationService::new().with_handler(|_req, _ctx| async {
218 Ok(ElicitationResultBuilder::new()
219 .accepted(serde_json::json!({"response": "test"}))
220 .build())
221 });
222
223 assert!(service.is_supported());
224 }
225}