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