Skip to main content

rmcp_soddygo/service/
server.rs

1// Sampling/Roots/Logging are SEP-2577-deprecated; internal references are expected.
2#![expect(deprecated)]
3use std::borrow::Cow;
4#[cfg(feature = "elicitation")]
5use std::collections::HashSet;
6
7use thiserror::Error;
8#[cfg(feature = "elicitation")]
9use url::Url;
10
11use super::*;
12#[cfg(feature = "elicitation")]
13use crate::model::{ElicitRequest, ElicitRequestParams, ElicitResult, ElicitationAction};
14use crate::{
15    model::{
16        CancelledNotification, CancelledNotificationParam, ClientInfo, ClientJsonRpcMessage,
17        ClientNotification, ClientRequest, ClientResult, CreateMessageRequest,
18        CreateMessageRequestParams, CreateMessageResult, EmptyResult, ErrorData, ListRootsRequest,
19        ListRootsResult, LoggingMessageNotification, LoggingMessageNotificationParam,
20        ProgressNotification, ProgressNotificationParam, PromptListChangedNotification,
21        ProtocolVersion, ResourceListChangedNotification, ResourceUpdatedNotification,
22        ResourceUpdatedNotificationParam, ServerInfo, ServerNotification, ServerRequest,
23        ServerResult, ToolListChangedNotification,
24    },
25    transport::DynamicTransportError,
26};
27
28#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
29#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
30pub struct RoleServer;
31
32impl ServiceRole for RoleServer {
33    type Req = ServerRequest;
34    type Resp = ServerResult;
35    type Not = ServerNotification;
36    type PeerReq = ClientRequest;
37    type PeerResp = ClientResult;
38    type PeerNot = ClientNotification;
39    type Info = ServerInfo;
40    type PeerInfo = ClientInfo;
41
42    type InitializeError = ServerInitializeError;
43    const IS_CLIENT: bool = false;
44}
45
46/// It represents the error that may occur when serving the server.
47///
48/// if you want to handle the error, you can use `serve_server_with_ct` or `serve_server` with `Result<RunningService<RoleServer, S>, ServerError>`
49#[derive(Error, Debug)]
50#[non_exhaustive]
51pub enum ServerInitializeError {
52    #[error("expect initialized request, but received: {0:?}")]
53    ExpectedInitializeRequest(Option<ClientJsonRpcMessage>),
54
55    #[deprecated(
56        since = "1.4.0",
57        note = "The server no longer gates on the initialized notification. This variant is never constructed and will be removed in a future major release."
58    )]
59    #[error("expect initialized notification, but received: {0:?}")]
60    ExpectedInitializedNotification(Option<ClientJsonRpcMessage>),
61
62    #[error("connection closed: {0}")]
63    ConnectionClosed(String),
64
65    #[error("unexpected initialize result: {0:?}")]
66    UnexpectedInitializeResponse(ServerResult),
67
68    #[error("initialize failed: {0}")]
69    InitializeFailed(ErrorData),
70
71    #[deprecated(
72        since = "1.8.0",
73        note = "Negotiation now falls back to the server-configured version. This variant is never constructed and will be removed in a future major release."
74    )]
75    #[error("unsupported protocol version: {0}")]
76    UnsupportedProtocolVersion(ProtocolVersion),
77
78    #[error("Send message error {error}, when {context}")]
79    TransportError {
80        error: DynamicTransportError,
81        context: Cow<'static, str>,
82    },
83
84    #[error("Cancelled")]
85    Cancelled,
86}
87
88impl ServerInitializeError {
89    pub fn transport<T: Transport<RoleServer> + 'static>(
90        error: T::Error,
91        context: impl Into<Cow<'static, str>>,
92    ) -> Self {
93        Self::TransportError {
94            error: DynamicTransportError::new::<T, _>(error),
95            context: context.into(),
96        }
97    }
98}
99pub type ClientSink = Peer<RoleServer>;
100
101impl<S: Service<RoleServer>> ServiceExt<RoleServer> for S {
102    fn serve_with_ct<T, E, A>(
103        self,
104        transport: T,
105        ct: CancellationToken,
106    ) -> impl Future<Output = Result<RunningService<RoleServer, Self>, ServerInitializeError>>
107    + MaybeSendFuture
108    where
109        T: IntoTransport<RoleServer, E, A>,
110        E: std::error::Error + Send + Sync + 'static,
111        Self: Sized,
112    {
113        serve_server_with_ct(self, transport, ct)
114    }
115}
116
117pub async fn serve_server<S, T, E, A>(
118    service: S,
119    transport: T,
120) -> Result<RunningService<RoleServer, S>, ServerInitializeError>
121where
122    S: Service<RoleServer>,
123    T: IntoTransport<RoleServer, E, A>,
124    E: std::error::Error + Send + Sync + 'static,
125{
126    serve_server_with_ct(service, transport, CancellationToken::new()).await
127}
128
129/// Helper function to get the next message from the stream
130async fn expect_next_message<T>(
131    transport: &mut T,
132    context: &str,
133) -> Result<ClientJsonRpcMessage, ServerInitializeError>
134where
135    T: Transport<RoleServer>,
136{
137    transport
138        .receive()
139        .await
140        .ok_or_else(|| ServerInitializeError::ConnectionClosed(context.to_string()))
141}
142
143pub async fn serve_server_with_ct<S, T, E, A>(
144    service: S,
145    transport: T,
146    ct: CancellationToken,
147) -> Result<RunningService<RoleServer, S>, ServerInitializeError>
148where
149    S: Service<RoleServer>,
150    T: IntoTransport<RoleServer, E, A>,
151    E: std::error::Error + Send + Sync + 'static,
152{
153    tokio::select! {
154        result = serve_server_with_ct_inner(service, transport.into_transport(), ct.clone()) => { result }
155        _ = ct.cancelled() => {
156            Err(ServerInitializeError::Cancelled)
157        }
158    }
159}
160
161/// Echoes the client-requested version if known; otherwise returns `server_fallback`.
162pub(crate) fn negotiate_protocol_version(
163    client_requested: &ProtocolVersion,
164    server_fallback: ProtocolVersion,
165) -> ProtocolVersion {
166    if ProtocolVersion::KNOWN_VERSIONS.contains(client_requested) {
167        client_requested.clone()
168    } else {
169        tracing::warn!(
170            client_requested = %client_requested,
171            server_fallback = %server_fallback,
172            "client requested unsupported protocol version; falling back to server default"
173        );
174        server_fallback
175    }
176}
177
178async fn serve_server_with_ct_inner<S, T>(
179    service: S,
180    transport: T,
181    ct: CancellationToken,
182) -> Result<RunningService<RoleServer, S>, ServerInitializeError>
183where
184    S: Service<RoleServer>,
185    T: Transport<RoleServer> + 'static,
186{
187    let mut transport = transport.into_transport();
188    let id_provider = <Arc<AtomicU32RequestIdProvider>>::default();
189
190    // Get initialize request; the MCP spec permits ping before initialize.
191    // See: https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle#initialization
192    let (request, id) = loop {
193        let msg = expect_next_message(&mut transport, "initialize request").await?;
194        match msg {
195            ClientJsonRpcMessage::Request(req)
196                if matches!(req.request, ClientRequest::PingRequest(_)) =>
197            {
198                transport
199                    .send(ServerJsonRpcMessage::response(
200                        ServerResult::EmptyResult(EmptyResult {}),
201                        req.id,
202                    ))
203                    .await
204                    .map_err(|error| {
205                        ServerInitializeError::transport::<T>(
206                            error,
207                            "sending pre-init ping response",
208                        )
209                    })?;
210            }
211            ClientJsonRpcMessage::Request(req) => break (req.request, req.id),
212            other => {
213                return Err(ServerInitializeError::ExpectedInitializeRequest(Some(
214                    other,
215                )));
216            }
217        }
218    };
219
220    let initialize_request = match request {
221        ClientRequest::InitializeRequest(request) => request,
222        mut request => {
223            if !request
224                .get_meta()
225                .missing_required_keys(&ProtocolVersion::V_2026_07_28)
226                .is_empty()
227            {
228                return Err(ServerInitializeError::ExpectedInitializeRequest(Some(
229                    ClientJsonRpcMessage::request(request, id),
230                )));
231            }
232            let (peer, peer_rx) = Peer::new(id_provider, None);
233            peer.require_request_metadata();
234            let context = RequestContext {
235                ct: ct.child_token(),
236                id: id.clone(),
237                meta: std::mem::take(request.get_meta_mut()),
238                extensions: std::mem::take(request.extensions_mut()),
239                peer: peer.clone(),
240            };
241            let response = match service.handle_request(request, context).await {
242                Ok(result) => ServerJsonRpcMessage::response(result, id),
243                Err(error) => ServerJsonRpcMessage::error(error, Some(id)),
244            };
245            transport.send(response).await.map_err(|error| {
246                ServerInitializeError::transport::<T>(error, "sending negotiated request response")
247            })?;
248            return Ok(serve_inner(service, transport, peer, peer_rx, ct));
249        }
250    };
251    let requested_protocol_version = initialize_request.params.protocol_version.clone();
252    let mut negotiated_peer_info = initialize_request.params.clone();
253    let (peer, peer_rx) = Peer::new(id_provider, Some(negotiated_peer_info.clone()));
254    let request = ClientRequest::InitializeRequest(initialize_request);
255    let context = RequestContext {
256        ct: ct.child_token(),
257        id: id.clone(),
258        meta: request.get_meta().clone(),
259        extensions: request.extensions().clone(),
260        peer: peer.clone(),
261    };
262    // Send initialize response
263    let init_response = service.handle_request(request, context).await;
264    let mut init_response = match init_response {
265        Ok(ServerResult::InitializeResult(init_response)) => init_response,
266        Ok(result) => {
267            return Err(ServerInitializeError::UnexpectedInitializeResponse(result));
268        }
269        Err(e) => {
270            transport
271                .send(ServerJsonRpcMessage::error(e.clone(), Some(id)))
272                .await
273                .map_err(|error| {
274                    ServerInitializeError::transport::<T>(error, "sending error response")
275                })?;
276            return Err(ServerInitializeError::InitializeFailed(e));
277        }
278    };
279    init_response.protocol_version =
280        negotiate_protocol_version(&requested_protocol_version, init_response.protocol_version);
281    // Update peer_info so context.protocol_version() reflects the negotiated
282    // version in all subsequent request handlers.
283    negotiated_peer_info.protocol_version = init_response.protocol_version.clone();
284    peer.set_peer_info(negotiated_peer_info);
285    transport
286        .send(ServerJsonRpcMessage::response(
287            ServerResult::InitializeResult(init_response),
288            id,
289        ))
290        .await
291        .map_err(|error| {
292            ServerInitializeError::transport::<T>(error, "sending initialize response")
293        })?;
294
295    // Enter the main service loop immediately after sending InitializeResult.
296    // The initialized notification will be handled as a regular notification by serve_inner.
297    // This matches the TypeScript SDK behavior: no init gate, no waiting for initialized.
298    // Streamable HTTP has no ordering guarantee between POSTs, and the MCP spec uses
299    // SHOULD NOT (not MUST NOT) for pre-initialized messages, so any request arriving
300    // before initialized is processed normally.
301    Ok(serve_inner(service, transport, peer, peer_rx, ct))
302}
303
304macro_rules! method {
305    ($(#[$meta:meta])* peer_req $method:ident $Req:ident() => $Resp: ident ) => {
306        $(#[$meta])*
307        pub async fn $method(&self) -> Result<$Resp, ServiceError> {
308            let result = self
309                .send_request(ServerRequest::$Req($Req {
310                    method: Default::default(),
311                    extensions: Default::default(),
312                }))
313                .await?;
314            match result {
315                ClientResult::$Resp(result) => Ok(result),
316                _ => Err(ServiceError::UnexpectedResponse),
317            }
318        }
319    };
320    ($(#[$meta:meta])* peer_req $method:ident $Req:ident($Param: ident) => $Resp: ident ) => {
321        $(#[$meta])*
322        pub async fn $method(&self, params: $Param) -> Result<$Resp, ServiceError> {
323            let result = self
324                .send_request(ServerRequest::$Req($Req {
325                    method: Default::default(),
326                    params,
327                    extensions: Default::default(),
328                }))
329                .await?;
330            match result {
331                ClientResult::$Resp(result) => Ok(result),
332                _ => Err(ServiceError::UnexpectedResponse),
333            }
334        }
335    };
336    ($(#[$meta:meta])* peer_req $method:ident $Req:ident($Param: ident)) => {
337        $(#[$meta])*
338        pub fn $method(
339            &self,
340            params: $Param,
341        ) -> impl Future<Output = Result<(), ServiceError>> + Send + '_ {
342            async move {
343                let result = self
344                    .send_request(ServerRequest::$Req($Req {
345                        method: Default::default(),
346                        params,
347                    }))
348                    .await?;
349                match result {
350                    ClientResult::EmptyResult(_) => Ok(()),
351                    _ => Err(ServiceError::UnexpectedResponse),
352                }
353            }
354        }
355    };
356
357    ($(#[$meta:meta])* peer_not $method:ident $Not:ident($Param: ident)) => {
358        $(#[$meta])*
359        pub async fn $method(&self, params: $Param) -> Result<(), ServiceError> {
360            self.send_notification(ServerNotification::$Not($Not {
361                method: Default::default(),
362                params,
363                extensions: Default::default(),
364            }))
365            .await?;
366            Ok(())
367        }
368    };
369    ($(#[$meta:meta])* peer_not $method:ident $Not:ident) => {
370        $(#[$meta])*
371        pub async fn $method(&self) -> Result<(), ServiceError> {
372            self.send_notification(ServerNotification::$Not($Not {
373                method: Default::default(),
374                extensions: Default::default(),
375            }))
376            .await?;
377            Ok(())
378        }
379    };
380
381    // Timeout-only variants (base method should be created separately with peer_req)
382    ($(#[$meta:meta])* peer_req_with_timeout $method_with_timeout:ident $Req:ident() => $Resp: ident) => {
383        $(#[$meta])*
384        pub async fn $method_with_timeout(
385            &self,
386            timeout: Option<std::time::Duration>,
387        ) -> Result<$Resp, ServiceError> {
388            let request = ServerRequest::$Req($Req {
389                method: Default::default(),
390                extensions: Default::default(),
391            });
392            let options = crate::service::PeerRequestOptions {
393                timeout,
394                meta: None,
395                reset_timeout_on_progress: false,
396                max_total_timeout: None,
397            };
398            let result = self
399                .send_request_with_option(request, options)
400                .await?
401                .await_response()
402                .await?;
403            match result {
404                ClientResult::$Resp(result) => Ok(result),
405                _ => Err(ServiceError::UnexpectedResponse),
406            }
407        }
408    };
409
410    ($(#[$meta:meta])* peer_req_with_timeout $method_with_timeout:ident $Req:ident($Param: ident) => $Resp: ident) => {
411        $(#[$meta])*
412        pub async fn $method_with_timeout(
413            &self,
414            params: $Param,
415            timeout: Option<std::time::Duration>,
416        ) -> Result<$Resp, ServiceError> {
417            let request = ServerRequest::$Req($Req {
418                method: Default::default(),
419                params,
420                extensions: Default::default(),
421            });
422            let options = crate::service::PeerRequestOptions {
423                timeout,
424                meta: None,
425                reset_timeout_on_progress: false,
426                max_total_timeout: None,
427            };
428            let result = self
429                .send_request_with_option(request, options)
430                .await?
431                .await_response()
432                .await?;
433            match result {
434                ClientResult::$Resp(result) => Ok(result),
435                _ => Err(ServiceError::UnexpectedResponse),
436            }
437        }
438    };
439}
440
441impl Peer<RoleServer> {
442    /// Check if the client supports sampling tools capability.
443    pub fn supports_sampling_tools(&self) -> bool {
444        if let Some(client_info) = self.peer_info() {
445            client_info
446                .capabilities
447                .sampling
448                .as_ref()
449                .and_then(|s| s.tools.as_ref())
450                .is_some()
451        } else {
452            false
453        }
454    }
455
456    #[deprecated(
457        since = "1.8.0",
458        note = "Sampling is deprecated by SEP-2577 and will be removed in a future release. See https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577"
459    )]
460    pub async fn create_message(
461        &self,
462        params: CreateMessageRequestParams,
463    ) -> Result<CreateMessageResult, ServiceError> {
464        // MUST throw error when tools/toolChoice provided without capability
465        if (params.tools.is_some() || params.tool_choice.is_some())
466            && !self.supports_sampling_tools()
467        {
468            return Err(ServiceError::McpError(ErrorData::invalid_params(
469                "tools or toolChoice provided but client does not support sampling tools capability",
470                None,
471            )));
472        }
473        // Validate message structure
474        params
475            .validate()
476            .map_err(|e| ServiceError::McpError(ErrorData::invalid_params(e, None)))?;
477        let result = self
478            .send_request(ServerRequest::CreateMessageRequest(CreateMessageRequest {
479                method: Default::default(),
480                params,
481                extensions: Default::default(),
482            }))
483            .await?;
484        match result {
485            ClientResult::CreateMessageResult(result) => Ok(*result),
486            _ => Err(ServiceError::UnexpectedResponse),
487        }
488    }
489    method!(
490        #[deprecated(
491            since = "1.8.0",
492            note = "Roots is deprecated by SEP-2577 and will be removed in a future release. See https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577"
493        )]
494        peer_req list_roots ListRootsRequest() => ListRootsResult
495    );
496    #[cfg(feature = "elicitation")]
497    method!(peer_req create_elicitation ElicitRequest(ElicitRequestParams) => ElicitResult);
498    #[cfg(feature = "elicitation")]
499    method!(peer_req_with_timeout create_elicitation_with_timeout ElicitRequest(ElicitRequestParams) => ElicitResult);
500
501    method!(peer_not notify_cancelled CancelledNotification(CancelledNotificationParam));
502    method!(peer_not notify_progress ProgressNotification(ProgressNotificationParam));
503    method!(
504        #[deprecated(
505            since = "1.8.0",
506            note = "Logging is deprecated by SEP-2577 and will be removed in a future release. See https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577"
507        )]
508        peer_not notify_logging_message LoggingMessageNotification(LoggingMessageNotificationParam)
509    );
510    method!(peer_not notify_resource_updated ResourceUpdatedNotification(ResourceUpdatedNotificationParam));
511    method!(peer_not notify_resource_list_changed ResourceListChangedNotification);
512    method!(peer_not notify_tool_list_changed ToolListChangedNotification);
513    method!(peer_not notify_prompt_list_changed PromptListChangedNotification);
514}
515
516// =============================================================================
517// ELICITATION CONVENIENCE METHODS
518// These methods are specific to server role and provide typed elicitation functionality
519// =============================================================================
520
521/// Errors that can occur during typed elicitation operations
522#[cfg(feature = "elicitation")]
523#[derive(Error, Debug)]
524#[non_exhaustive]
525pub enum ElicitationError {
526    /// The elicitation request failed at the service level
527    #[error("Service error: {0}")]
528    Service(#[from] ServiceError),
529
530    /// User explicitly declined to provide the requested information
531    /// This indicates a conscious decision by the user to reject the request
532    /// (e.g., clicked "Reject", "Decline", "No", etc.)
533    #[error("User explicitly declined the request")]
534    UserDeclined,
535
536    /// User dismissed the request without making an explicit choice
537    /// This indicates the user cancelled without explicitly declining
538    /// (e.g., closed dialog, clicked outside, pressed Escape, etc.)
539    #[error("User cancelled/dismissed the request")]
540    UserCancelled,
541
542    /// The response data could not be parsed into the requested type
543    #[error("Failed to parse response data: {error}\nReceived data: {data}")]
544    ParseError {
545        error: serde_json::Error,
546        data: serde_json::Value,
547    },
548
549    /// No response content was provided by the user
550    #[error("No response content provided")]
551    NoContent,
552
553    /// Client does not support elicitation capability
554    #[error("Client does not support elicitation - capability not declared during initialization")]
555    CapabilityNotSupported,
556}
557
558/// Marker trait to ensure that elicitation types generate object-type JSON schemas.
559///
560/// This trait provides compile-time safety to ensure that types used with
561/// `elicit<T>()` methods will generate JSON schemas of type "object", which
562/// aligns with MCP client expectations for structured data input.
563///
564/// # Type Safety Rationale
565///
566/// MCP clients typically expect JSON objects for elicitation schemas to
567/// provide structured forms and validation. This trait prevents common
568/// mistakes like:
569///
570/// ```compile_fail
571/// // These would not compile due to missing ElicitationSafe bound:
572/// let name: String = server.elicit("Enter name").await?;        // Primitive
573/// let items: Vec<i32> = server.elicit("Enter items").await?;    // Array
574/// ```
575#[cfg(feature = "elicitation")]
576pub trait ElicitationSafe: schemars::JsonSchema {}
577
578/// Macro to mark types as safe for elicitation by verifying they generate object schemas.
579///
580/// This macro automatically implements the `ElicitationSafe` trait for struct types
581/// that should be used with `elicit<T>()` methods.
582///
583/// # Example
584///
585/// ```rust
586/// use rmcp::elicit_safe;
587/// use schemars::JsonSchema;
588/// use serde::{Deserialize, Serialize};
589///
590/// #[derive(Serialize, Deserialize, JsonSchema)]
591/// struct UserProfile {
592///     name: String,
593///     email: String,
594/// }
595///
596/// elicit_safe!(UserProfile);
597///
598/// // Now safe to use in async context:
599/// // let profile: UserProfile = server.elicit("Enter profile").await?;
600/// ```
601#[cfg(feature = "elicitation")]
602#[macro_export]
603macro_rules! elicit_safe {
604    ($($t:ty),* $(,)?) => {
605        $(
606            impl $crate::service::ElicitationSafe for $t {}
607        )*
608    };
609}
610
611#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
612#[non_exhaustive]
613pub enum ElicitationMode {
614    Form,
615    Url,
616}
617
618#[cfg(feature = "elicitation")]
619impl Peer<RoleServer> {
620    /// Check if the client supports elicitation capability
621    ///
622    /// Returns true if the client declared elicitation capability during initialization,
623    /// false otherwise. According to MCP 2025-06-18 specification, clients that support
624    /// elicitation MUST declare the capability during initialization.
625    pub fn supported_elicitation_modes(&self) -> HashSet<ElicitationMode> {
626        if let Some(client_info) = self.peer_info() {
627            if let Some(elicit_capability) = &client_info.capabilities.elicitation {
628                let mut modes = HashSet::new();
629                // Backward compatibility: if neither form nor url is specified, assume form
630                if elicit_capability.form.is_none() && elicit_capability.url.is_none() {
631                    modes.insert(ElicitationMode::Form);
632                } else {
633                    if elicit_capability.form.is_some() {
634                        modes.insert(ElicitationMode::Form);
635                    }
636                    if elicit_capability.url.is_some() {
637                        modes.insert(ElicitationMode::Url);
638                    }
639                }
640                modes
641            } else {
642                HashSet::new()
643            }
644        } else {
645            HashSet::new()
646        }
647    }
648
649    /// Request typed data from the user with automatic schema generation.
650    ///
651    /// This method automatically generates the JSON schema from the Rust type using `schemars`,
652    /// eliminating the need to manually create schemas. The response is automatically parsed
653    /// into the requested type.
654    ///
655    /// **Requires the `elicitation` feature to be enabled.**
656    ///
657    /// # Type Requirements
658    /// The type `T` must implement:
659    /// - `schemars::JsonSchema` - for automatic schema generation
660    /// - `serde::Deserialize` - for parsing the response
661    ///
662    /// # Arguments
663    /// * `message` - The prompt message for the user
664    ///
665    /// # Returns
666    /// * `Ok(Some(data))` if user provided valid data that matches type T
667    /// * `Err(ElicitationError::UserDeclined)` if user explicitly declined the request
668    /// * `Err(ElicitationError::UserCancelled)` if user cancelled/dismissed the request
669    /// * `Err(ElicitationError::ParseError { .. })` if response data couldn't be parsed into type T
670    /// * `Err(ElicitationError::NoContent)` if no response content was provided
671    /// * `Err(ElicitationError::Service(_))` if the underlying service call failed
672    ///
673    /// # Example
674    ///
675    /// Add to your `Cargo.toml`:
676    /// ```toml
677    /// [dependencies]
678    /// rmcp = { version = "0.3", features = ["elicitation"] }
679    /// serde = { version = "1.0", features = ["derive"] }
680    /// schemars = "1.0"
681    /// ```
682    ///
683    /// ```rust,no_run
684    /// # use rmcp::*;
685    /// # use rmcp::service::ElicitationError;
686    /// # use serde::{Deserialize, Serialize};
687    /// # use schemars::JsonSchema;
688    /// #
689    /// #[derive(Debug, Serialize, Deserialize, JsonSchema)]
690    /// struct UserProfile {
691    ///     #[schemars(description = "Full name")]
692    ///     name: String,
693    ///     #[schemars(description = "Email address")]
694    ///     email: String,
695    ///     #[schemars(description = "Age")]
696    ///     age: u8,
697    /// }
698    ///
699    /// // Mark as safe for elicitation (generates object schema)
700    /// rmcp::elicit_safe!(UserProfile);
701    ///
702    /// # async fn example(peer: Peer<RoleServer>) -> Result<(), Box<dyn std::error::Error>> {
703    /// match peer.elicit::<UserProfile>("Please enter your profile information").await {
704    ///     Ok(Some(profile)) => {
705    ///         println!("Name: {}, Email: {}, Age: {}", profile.name, profile.email, profile.age);
706    ///     }
707    ///     Ok(None) => {
708    ///         println!("User provided no content");
709    ///     }
710    ///     Err(ElicitationError::UserDeclined) => {
711    ///         println!("User explicitly declined to provide information");
712    ///         // Handle explicit decline - perhaps offer alternatives
713    ///     }
714    ///     Err(ElicitationError::UserCancelled) => {
715    ///         println!("User cancelled the request");
716    ///         // Handle cancellation - perhaps prompt again later
717    ///     }
718    ///     Err(ElicitationError::ParseError { error, data }) => {
719    ///         println!("Failed to parse response: {}\nData: {}", error, data);
720    ///     }
721    ///     Err(e) => return Err(e.into()),
722    /// }
723    /// # Ok(())
724    /// # }
725    /// ```
726    #[cfg(all(feature = "schemars", feature = "elicitation"))]
727    pub async fn elicit<T>(&self, message: impl Into<String>) -> Result<Option<T>, ElicitationError>
728    where
729        T: ElicitationSafe + for<'de> serde::Deserialize<'de>,
730    {
731        self.elicit_with_timeout(message, None).await
732    }
733
734    /// Request typed data from the user with custom timeout.
735    ///
736    /// Same as `elicit()` but allows specifying a custom timeout for the request.
737    /// If the user doesn't respond within the timeout, the request will be cancelled.
738    ///
739    /// # Arguments
740    /// * `message` - The prompt message for the user
741    /// * `timeout` - Optional timeout duration. If None, uses default timeout behavior
742    ///
743    /// # Returns
744    /// Same as `elicit()` but may also return `ServiceError::Timeout` if timeout expires
745    ///
746    /// # Example
747    /// ```rust,no_run
748    /// # use rmcp::*;
749    /// # use rmcp::service::ElicitationError;
750    /// # use serde::{Deserialize, Serialize};
751    /// # use schemars::JsonSchema;
752    /// # use std::time::Duration;
753    /// #
754    /// #[derive(Debug, Serialize, Deserialize, JsonSchema)]
755    /// struct QuickResponse {
756    ///     answer: String,
757    /// }
758    ///
759    /// // Mark as safe for elicitation
760    /// rmcp::elicit_safe!(QuickResponse);
761    ///
762    /// # async fn example(peer: Peer<RoleServer>) -> Result<(), Box<dyn std::error::Error>> {
763    /// // Give user 30 seconds to respond
764    /// let timeout = Some(Duration::from_secs(30));
765    /// match peer.elicit_with_timeout::<QuickResponse>(
766    ///     "Quick question - what's your answer?",
767    ///     timeout
768    /// ).await {
769    ///     Ok(Some(response)) => println!("Got answer: {}", response.answer),
770    ///     Ok(None) => println!("User provided no content"),
771    ///     Err(ElicitationError::UserDeclined) => {
772    ///         println!("User explicitly declined");
773    ///         // Handle explicit decline
774    ///     }
775    ///     Err(ElicitationError::UserCancelled) => {
776    ///         println!("User cancelled/dismissed");
777    ///         // Handle cancellation
778    ///     }
779    ///     Err(ElicitationError::Service(ServiceError::Timeout { .. })) => {
780    ///         println!("User didn't respond in time");
781    ///     }
782    ///     Err(e) => return Err(e.into()),
783    /// }
784    /// # Ok(())
785    /// # }
786    /// ```
787    #[cfg(all(feature = "schemars", feature = "elicitation"))]
788    pub async fn elicit_with_timeout<T>(
789        &self,
790        message: impl Into<String>,
791        timeout: Option<std::time::Duration>,
792    ) -> Result<Option<T>, ElicitationError>
793    where
794        T: ElicitationSafe + for<'de> serde::Deserialize<'de>,
795    {
796        // Check if client supports form elicitation capability
797        if !self
798            .supported_elicitation_modes()
799            .contains(&ElicitationMode::Form)
800        {
801            return Err(ElicitationError::CapabilityNotSupported);
802        }
803
804        // Generate schema automatically from type
805        let schema = crate::model::ElicitationSchema::from_type::<T>().map_err(|e| {
806            ElicitationError::Service(ServiceError::McpError(crate::ErrorData::invalid_params(
807                format!(
808                    "Invalid schema for type {}: {}",
809                    std::any::type_name::<T>(),
810                    e
811                ),
812                None,
813            )))
814        })?;
815
816        let response = self
817            .create_elicitation_with_timeout(
818                ElicitRequestParams::FormElicitationParams {
819                    meta: None,
820                    message: message.into(),
821                    requested_schema: schema,
822                },
823                timeout,
824            )
825            .await?;
826
827        match response.action {
828            crate::model::ElicitationAction::Accept => {
829                if let Some(value) = response.content {
830                    match serde_json::from_value::<T>(value.clone()) {
831                        Ok(parsed) => Ok(Some(parsed)),
832                        Err(error) => Err(ElicitationError::ParseError { error, data: value }),
833                    }
834                } else {
835                    Err(ElicitationError::NoContent)
836                }
837            }
838            crate::model::ElicitationAction::Decline => Err(ElicitationError::UserDeclined),
839            crate::model::ElicitationAction::Cancel => Err(ElicitationError::UserCancelled),
840        }
841    }
842
843    /// Request the user to visit a URL and confirm completion.
844    ///
845    /// This method sends a URL elicitation request to the client, prompting the user
846    /// to visit the specified URL and confirm completion. It returns the user's action
847    /// (accept/decline/cancel) without any additional data.
848    /// **Requires the `elicitation` feature to be enabled.**
849    ///
850    /// # Arguments
851    /// * `message` - The prompt message for the user
852    /// * `url` - The URL the user is requested to visit
853    /// * `elicitation_id` - A unique identifier for this elicitation request
854    /// # Returns
855    /// * `Ok(action)` indicating the user's response action
856    /// * `Err(ElicitationError::CapabilityNotSupported)` if client does not support elicitation via URL
857    /// * `Err(ElicitationError::Service(_))` if the underlying service call failed
858    /// # Example
859    /// ```rust,no_run
860    /// # use rmcp::*;
861    /// # use rmcp::model::ElicitationAction;
862    /// # use url::Url;
863    ///
864    /// async fn example(peer: Peer<RoleServer>) -> Result<(), Box<dyn std::error::Error>> {
865    /// let elicit_result = peer.elicit_url("Please visit the following URL to complete the action",
866    ///      Url::parse("https://example.com/complete_action")?, "elicit_123").await?;
867    ///  match elicit_result {
868    ///        ElicitationAction::Accept => {
869    ///        println!("User accepted and confirmed completion");
870    ///     }
871    ///     ElicitationAction::Decline => {
872    ///          println!("User declined the request");
873    ///     }
874    ///     ElicitationAction::Cancel => {
875    ///         println!("User cancelled/dismissed the request");
876    ///     }
877    ///     _ => {}
878    ///  }
879    ///  Ok(())
880    /// }
881    /// ```
882    #[cfg(feature = "elicitation")]
883    pub async fn elicit_url(
884        &self,
885        message: impl Into<String>,
886        url: impl Into<Url>,
887        elicitation_id: impl Into<String>,
888    ) -> Result<ElicitationAction, ElicitationError> {
889        self.elicit_url_with_timeout(message, url, elicitation_id, None)
890            .await
891    }
892
893    /// Request the user to visit a URL and confirm completion.
894    ///
895    /// Same as `elicit_url()` but allows specifying a custom timeout for the request.
896    ///
897    /// # Arguments
898    /// * `message` - The prompt message for the user
899    /// * `url` - The URL the user is requested to visit
900    /// * `elicitation_id` - A unique identifier for this elicitation request
901    /// * `timeout` - Optional timeout duration. If None, uses default timeout behavior
902    /// # Returns
903    /// * `Ok(action)` indicating the user's response action
904    /// * `Err(ElicitationError::CapabilityNotSupported)` if client does not support elicitation via URL
905    /// * `Err(ElicitationError::Service(_))` if the underlying service call failed
906    /// # Example
907    /// ```rust,no_run
908    /// # use std::time::Duration;
909    /// use rmcp::*;
910    /// # use rmcp::model::ElicitationAction;
911    /// # use url::Url;
912    ///
913    /// async fn example(peer: Peer<RoleServer>) -> Result<(), Box<dyn std::error::Error>> {
914    /// let elicit_result = peer.elicit_url_with_timeout("Please visit the following URL to complete the action",
915    ///      Url::parse("https://example.com/complete_action")?,
916    ///     "elicit_123",
917    ///     Some(Duration::from_secs(30))).await?;
918    ///  match elicit_result {
919    ///        ElicitationAction::Accept => {
920    ///        println!("User accepted and confirmed completion");
921    ///     }
922    ///     ElicitationAction::Decline => {
923    ///          println!("User declined the request");
924    ///     }
925    ///     ElicitationAction::Cancel => {
926    ///         println!("User cancelled/dismissed the request");
927    ///     }
928    ///     _ => {}
929    ///  }
930    ///  Ok(())
931    /// }
932    /// ```
933    #[cfg(feature = "elicitation")]
934    pub async fn elicit_url_with_timeout(
935        &self,
936        message: impl Into<String>,
937        url: impl Into<Url>,
938        elicitation_id: impl Into<String>,
939        timeout: Option<std::time::Duration>,
940    ) -> Result<ElicitationAction, ElicitationError> {
941        // Check if client supports url elicitation
942        if !self
943            .supported_elicitation_modes()
944            .contains(&ElicitationMode::Url)
945        {
946            return Err(ElicitationError::CapabilityNotSupported);
947        }
948
949        let action = self
950            .create_elicitation_with_timeout(
951                ElicitRequestParams::UrlElicitationParams {
952                    meta: None,
953                    message: message.into(),
954                    url: url.into().to_string(),
955                    elicitation_id: elicitation_id.into(),
956                },
957                timeout,
958            )
959            .await?
960            .action;
961        Ok(action)
962    }
963}