@@ -3,11 +3,14 @@ use std::borrow::Cow;
3
3
use thiserror:: Error ;
4
4
5
5
use super :: * ;
6
+ #[ cfg( feature = "elicitation" ) ]
7
+ use crate :: model:: {
8
+ CreateElicitationRequest , CreateElicitationRequestParam , CreateElicitationResult ,
9
+ } ;
6
10
use crate :: {
7
11
model:: {
8
12
CancelledNotification , CancelledNotificationParam , ClientInfo , ClientJsonRpcMessage ,
9
- ClientNotification , ClientRequest , ClientResult , CreateElicitationRequest ,
10
- CreateElicitationRequestParam , CreateElicitationResult , CreateMessageRequest ,
13
+ ClientNotification , ClientRequest , ClientResult , CreateMessageRequest ,
11
14
CreateMessageRequestParam , CreateMessageResult , ErrorData , ListRootsRequest ,
12
15
ListRootsResult , LoggingMessageNotification , LoggingMessageNotificationParam ,
13
16
ProgressNotification , ProgressNotificationParam , PromptListChangedNotification ,
@@ -325,12 +328,68 @@ macro_rules! method {
325
328
Ok ( ( ) )
326
329
}
327
330
} ;
331
+
332
+ // Timeout-only variants (base method should be created separately with peer_req)
333
+ ( peer_req_with_timeout $method_with_timeout: ident $Req: ident( ) => $Resp: ident) => {
334
+ pub async fn $method_with_timeout(
335
+ & self ,
336
+ timeout: Option <std:: time:: Duration >,
337
+ ) -> Result <$Resp, ServiceError > {
338
+ let request = ServerRequest :: $Req( $Req {
339
+ method: Default :: default ( ) ,
340
+ extensions: Default :: default ( ) ,
341
+ } ) ;
342
+ let options = crate :: service:: PeerRequestOptions {
343
+ timeout,
344
+ meta: None ,
345
+ } ;
346
+ let result = self
347
+ . send_request_with_option( request, options)
348
+ . await ?
349
+ . await_response( )
350
+ . await ?;
351
+ match result {
352
+ ClientResult :: $Resp( result) => Ok ( result) ,
353
+ _ => Err ( ServiceError :: UnexpectedResponse ) ,
354
+ }
355
+ }
356
+ } ;
357
+
358
+ ( peer_req_with_timeout $method_with_timeout: ident $Req: ident( $Param: ident) => $Resp: ident) => {
359
+ pub async fn $method_with_timeout(
360
+ & self ,
361
+ params: $Param,
362
+ timeout: Option <std:: time:: Duration >,
363
+ ) -> Result <$Resp, ServiceError > {
364
+ let request = ServerRequest :: $Req( $Req {
365
+ method: Default :: default ( ) ,
366
+ params,
367
+ extensions: Default :: default ( ) ,
368
+ } ) ;
369
+ let options = crate :: service:: PeerRequestOptions {
370
+ timeout,
371
+ meta: None ,
372
+ } ;
373
+ let result = self
374
+ . send_request_with_option( request, options)
375
+ . await ?
376
+ . await_response( )
377
+ . await ?;
378
+ match result {
379
+ ClientResult :: $Resp( result) => Ok ( result) ,
380
+ _ => Err ( ServiceError :: UnexpectedResponse ) ,
381
+ }
382
+ }
383
+ } ;
328
384
}
329
385
330
386
impl Peer < RoleServer > {
331
387
method ! ( peer_req create_message CreateMessageRequest ( CreateMessageRequestParam ) => CreateMessageResult ) ;
332
388
method ! ( peer_req list_roots ListRootsRequest ( ) => ListRootsResult ) ;
389
+ #[ cfg( feature = "elicitation" ) ]
333
390
method ! ( peer_req create_elicitation CreateElicitationRequest ( CreateElicitationRequestParam ) => CreateElicitationResult ) ;
391
+ #[ cfg( feature = "elicitation" ) ]
392
+ method ! ( peer_req_with_timeout create_elicitation_with_timeout CreateElicitationRequest ( CreateElicitationRequestParam ) => CreateElicitationResult ) ;
334
393
335
394
method ! ( peer_not notify_cancelled CancelledNotification ( CancelledNotificationParam ) ) ;
336
395
method ! ( peer_not notify_progress ProgressNotification ( ProgressNotificationParam ) ) ;
@@ -347,6 +406,7 @@ impl Peer<RoleServer> {
347
406
// =============================================================================
348
407
349
408
/// Errors that can occur during typed elicitation operations
409
+ #[ cfg( feature = "elicitation" ) ]
350
410
#[ derive( Error , Debug ) ]
351
411
pub enum ElicitationError {
352
412
/// The elicitation request failed at the service level
@@ -373,6 +433,7 @@ pub enum ElicitationError {
373
433
CapabilityNotSupported ,
374
434
}
375
435
436
+ #[ cfg( feature = "elicitation" ) ]
376
437
impl Peer < RoleServer > {
377
438
/// Check if the client supports elicitation capability
378
439
///
@@ -387,69 +448,6 @@ impl Peer<RoleServer> {
387
448
}
388
449
}
389
450
390
- /// Request structured data from the user using a custom JSON schema.
391
- ///
392
- /// This is the most flexible elicitation method, allowing you to request
393
- /// any kind of structured input using JSON Schema validation.
394
- ///
395
- /// # Arguments
396
- /// * `message` - The prompt message for the user
397
- /// * `schema` - JSON Schema defining the expected data structure
398
- ///
399
- /// # Returns
400
- /// * `Ok(Some(data))` if user provided valid data
401
- /// * `Ok(None)` if user declined or cancelled
402
- ///
403
- /// # Example
404
- /// ```rust,no_run
405
- /// # use rmcp::*;
406
- /// # use rmcp::service::ElicitationError;
407
- /// # use serde_json::json;
408
- /// # async fn example(peer: Peer<RoleServer>) -> Result<(), ElicitationError> {
409
- /// let schema = json!({
410
- /// "type": "object",
411
- /// "properties": {
412
- /// "name": {"type": "string"},
413
- /// "email": {"type": "string", "format": "email"},
414
- /// "age": {"type": "integer", "minimum": 0}
415
- /// },
416
- /// "required": ["name", "email"]
417
- /// });
418
- ///
419
- /// let user_data = peer.elicit_structured_input(
420
- /// "Please provide your contact information:",
421
- /// schema.as_object().unwrap()
422
- /// ).await?;
423
- ///
424
- /// if let Some(data) = user_data {
425
- /// println!("Received user data: {}", data);
426
- /// }
427
- /// # Ok(())
428
- /// # }
429
- /// ```
430
- pub async fn elicit_structured_input (
431
- & self ,
432
- message : impl Into < String > ,
433
- schema : & crate :: model:: JsonObject ,
434
- ) -> Result < Option < serde_json:: Value > , ElicitationError > {
435
- // Check if client supports elicitation capability
436
- if !self . supports_elicitation ( ) {
437
- return Err ( ElicitationError :: CapabilityNotSupported ) ;
438
- }
439
-
440
- let response = self
441
- . create_elicitation ( CreateElicitationRequestParam {
442
- message : message. into ( ) ,
443
- requested_schema : schema. clone ( ) ,
444
- } )
445
- . await ?;
446
-
447
- match response. action {
448
- crate :: model:: ElicitationAction :: Accept => Ok ( response. content ) ,
449
- _ => Ok ( None ) ,
450
- }
451
- }
452
-
453
451
/// Request typed data from the user with automatic schema generation.
454
452
///
455
453
/// This method automatically generates the JSON schema from the Rust type using `schemars`,
@@ -518,8 +516,62 @@ impl Peer<RoleServer> {
518
516
/// # Ok(())
519
517
/// # }
520
518
/// ```
521
- #[ cfg( feature = "schemars" ) ]
519
+ #[ cfg( all ( feature = "schemars" , feature = "elicitation" ) ) ]
522
520
pub async fn elicit < T > ( & self , message : impl Into < String > ) -> Result < Option < T > , ElicitationError >
521
+ where
522
+ T : schemars:: JsonSchema + for < ' de > serde:: Deserialize < ' de > ,
523
+ {
524
+ self . elicit_with_timeout ( message, None ) . await
525
+ }
526
+
527
+ /// Request typed data from the user with custom timeout.
528
+ ///
529
+ /// Same as `elicit()` but allows specifying a custom timeout for the request.
530
+ /// If the user doesn't respond within the timeout, the request will be cancelled.
531
+ ///
532
+ /// # Arguments
533
+ /// * `message` - The prompt message for the user
534
+ /// * `timeout` - Optional timeout duration. If None, uses default timeout behavior
535
+ ///
536
+ /// # Returns
537
+ /// Same as `elicit()` but may also return `ServiceError::Timeout` if timeout expires
538
+ ///
539
+ /// # Example
540
+ /// ```rust,no_run
541
+ /// # use rmcp::*;
542
+ /// # use rmcp::service::ElicitationError;
543
+ /// # use serde::{Deserialize, Serialize};
544
+ /// # use schemars::JsonSchema;
545
+ /// # use std::time::Duration;
546
+ /// #
547
+ /// #[derive(Debug, Serialize, Deserialize, JsonSchema)]
548
+ /// struct QuickResponse {
549
+ /// answer: String,
550
+ /// }
551
+ ///
552
+ /// # async fn example(peer: Peer<RoleServer>) -> Result<(), Box<dyn std::error::Error>> {
553
+ /// // Give user 30 seconds to respond
554
+ /// let timeout = Some(Duration::from_secs(30));
555
+ /// match peer.elicit_with_timeout::<QuickResponse>(
556
+ /// "Quick question - what's your answer?",
557
+ /// timeout
558
+ /// ).await {
559
+ /// Ok(Some(response)) => println!("Got answer: {}", response.answer),
560
+ /// Ok(None) => println!("User declined"),
561
+ /// Err(ElicitationError::Service(ServiceError::Timeout { .. })) => {
562
+ /// println!("User didn't respond in time");
563
+ /// }
564
+ /// Err(e) => return Err(e.into()),
565
+ /// }
566
+ /// # Ok(())
567
+ /// # }
568
+ /// ```
569
+ #[ cfg( all( feature = "schemars" , feature = "elicitation" ) ) ]
570
+ pub async fn elicit_with_timeout < T > (
571
+ & self ,
572
+ message : impl Into < String > ,
573
+ timeout : Option < std:: time:: Duration > ,
574
+ ) -> Result < Option < T > , ElicitationError >
523
575
where
524
576
T : schemars:: JsonSchema + for < ' de > serde:: Deserialize < ' de > ,
525
577
{
@@ -532,10 +584,13 @@ impl Peer<RoleServer> {
532
584
let schema = crate :: handler:: server:: tool:: schema_for_type :: < T > ( ) ;
533
585
534
586
let response = self
535
- . create_elicitation ( CreateElicitationRequestParam {
536
- message : message. into ( ) ,
537
- requested_schema : schema,
538
- } )
587
+ . create_elicitation_with_timeout (
588
+ CreateElicitationRequestParam {
589
+ message : message. into ( ) ,
590
+ requested_schema : schema,
591
+ } ,
592
+ timeout,
593
+ )
539
594
. await ?;
540
595
541
596
match response. action {
0 commit comments