@@ -22,6 +22,29 @@ use crate::{
22
22
transport:: DynamicTransportError ,
23
23
} ;
24
24
25
+ /// Errors that can occur during typed elicitation operations
26
+ #[ derive( Error , Debug ) ]
27
+ pub enum ElicitationError {
28
+ /// The elicitation request failed at the service level
29
+ #[ error( "Service error: {0}" ) ]
30
+ Service ( #[ from] ServiceError ) ,
31
+
32
+ /// User declined to provide input or cancelled the request
33
+ #[ error( "User declined or cancelled the request" ) ]
34
+ UserDeclined ,
35
+
36
+ /// The response data could not be parsed into the requested type
37
+ #[ error( "Failed to parse response data: {error}\n Received data: {data}" ) ]
38
+ ParseError {
39
+ error : serde_json:: Error ,
40
+ data : serde_json:: Value ,
41
+ } ,
42
+
43
+ /// No response content was provided by the user
44
+ #[ error( "No response content provided" ) ]
45
+ NoContent ,
46
+ }
47
+
25
48
/// It represents the error that may occur when serving the client.
26
49
///
27
50
/// if you want to handle the error, you can use `serve_client_with_ct` or `serve_client` with `Result<RunningService<RoleClient, S>, ClientError>`
@@ -397,247 +420,154 @@ impl Peer<RoleClient> {
397
420
// ELICITATION CONVENIENCE METHODS
398
421
// =============================================================================
399
422
400
- /// Request a simple yes/no confirmation from the user.
401
- ///
402
- /// This is a convenience method for requesting boolean confirmation
403
- /// from users during tool execution.
404
- ///
405
- /// # Arguments
406
- /// * `message` - The question to ask the user
407
- ///
408
- /// # Returns
409
- /// * `Ok(Some(true))` if user accepted and confirmed
410
- /// * `Ok(Some(false))` if user accepted but declined
411
- /// * `Ok(None)` if user declined to answer or cancelled
412
- ///
413
- /// # Example
414
- /// ```rust,no_run
415
- /// # use rmcp::*;
416
- /// # async fn example(peer: Peer<RoleClient>) -> Result<(), ServiceError> {
417
- /// let confirmed = peer.elicit_confirmation("Delete this file?").await?;
418
- /// match confirmed {
419
- /// Some(true) => println!("User confirmed deletion"),
420
- /// Some(false) => println!("User declined deletion"),
421
- /// None => println!("User cancelled or declined to answer"),
422
- /// }
423
- /// # Ok(())
424
- /// # }
425
- /// ```
426
- pub async fn elicit_confirmation (
427
- & self ,
428
- message : impl Into < String > ,
429
- ) -> Result < Option < bool > , ServiceError > {
430
- use serde_json:: json;
431
-
432
- let response = self
433
- . create_elicitation ( CreateElicitationRequestParam {
434
- message : message. into ( ) ,
435
- requested_schema : json ! ( {
436
- "type" : "boolean" ,
437
- "description" : "User confirmation (true for yes, false for no)"
438
- } )
439
- . as_object ( )
440
- . unwrap ( )
441
- . clone ( ) ,
442
- } )
443
- . await ?;
444
-
445
- match response. action {
446
- crate :: model:: ElicitationAction :: Accept => {
447
- if let Some ( value) = response. content {
448
- Ok ( value. as_bool ( ) )
449
- } else {
450
- Ok ( None )
451
- }
452
- }
453
- _ => Ok ( None ) ,
454
- }
455
- }
456
-
457
- /// Request text input from the user.
423
+ /// Request structured data from the user using a custom JSON schema.
458
424
///
459
- /// This is a convenience method for requesting string input from users.
425
+ /// This is the most flexible elicitation method, allowing you to request
426
+ /// any kind of structured input using JSON Schema validation.
460
427
///
461
428
/// # Arguments
462
429
/// * `message` - The prompt message for the user
463
- /// * `required ` - Whether the input is required (cannot be empty)
430
+ /// * `schema ` - JSON Schema defining the expected data structure
464
431
///
465
432
/// # Returns
466
- /// * `Ok(Some(text ))` if user provided input
433
+ /// * `Ok(Some(data ))` if user provided valid data
467
434
/// * `Ok(None)` if user declined or cancelled
468
435
///
469
436
/// # Example
470
437
/// ```rust,no_run
471
438
/// # use rmcp::*;
439
+ /// # use serde_json::json;
472
440
/// # async fn example(peer: Peer<RoleClient>) -> Result<(), ServiceError> {
473
- /// let name = peer.elicit_text_input("Please enter your name:", false).await?;
474
- /// if let Some(name) = name {
475
- /// println!("Hello, {}!", name);
441
+ /// let schema = json!({
442
+ /// "type": "object",
443
+ /// "properties": {
444
+ /// "name": {"type": "string"},
445
+ /// "email": {"type": "string", "format": "email"},
446
+ /// "age": {"type": "integer", "minimum": 0}
447
+ /// },
448
+ /// "required": ["name", "email"]
449
+ /// });
450
+ ///
451
+ /// let user_data = peer.elicit_structured_input(
452
+ /// "Please provide your contact information:",
453
+ /// schema.as_object().unwrap()
454
+ /// ).await?;
455
+ ///
456
+ /// if let Some(data) = user_data {
457
+ /// println!("Received user data: {}", data);
476
458
/// }
477
459
/// # Ok(())
478
460
/// # }
479
461
/// ```
480
- pub async fn elicit_text_input (
462
+ pub async fn elicit_structured_input (
481
463
& self ,
482
464
message : impl Into < String > ,
483
- required : bool ,
484
- ) -> Result < Option < String > , ServiceError > {
485
- use serde_json:: json;
486
-
487
- let mut schema = json ! ( {
488
- "type" : "string" ,
489
- "description" : "User text input"
490
- } ) ;
491
-
492
- if required {
493
- schema[ "minLength" ] = json ! ( 1 ) ;
494
- }
495
-
465
+ schema : & crate :: model:: JsonObject ,
466
+ ) -> Result < Option < serde_json:: Value > , ServiceError > {
496
467
let response = self
497
468
. create_elicitation ( CreateElicitationRequestParam {
498
469
message : message. into ( ) ,
499
- requested_schema : schema. as_object ( ) . unwrap ( ) . clone ( ) ,
470
+ requested_schema : schema. clone ( ) ,
500
471
} )
501
472
. await ?;
502
473
503
474
match response. action {
504
- crate :: model:: ElicitationAction :: Accept => {
505
- if let Some ( value) = response. content {
506
- Ok ( value. as_str ( ) . map ( |s| s. to_string ( ) ) )
507
- } else {
508
- Ok ( None )
509
- }
510
- }
475
+ crate :: model:: ElicitationAction :: Accept => Ok ( response. content ) ,
511
476
_ => Ok ( None ) ,
512
477
}
513
478
}
514
479
515
- /// Request the user to choose from multiple options.
480
+ /// Request typed data from the user with automatic schema generation.
481
+ ///
482
+ /// This method automatically generates the JSON schema from the Rust type using `schemars`,
483
+ /// eliminating the need to manually create schemas. The response is automatically parsed
484
+ /// into the requested type.
485
+ ///
486
+ /// **Requires the `elicitation` feature to be enabled.**
516
487
///
517
- /// This is a convenience method for presenting users with a list of choices.
488
+ /// # Type Requirements
489
+ /// The type `T` must implement:
490
+ /// - `schemars::JsonSchema` - for automatic schema generation
491
+ /// - `serde::Deserialize` - for parsing the response
518
492
///
519
493
/// # Arguments
520
494
/// * `message` - The prompt message for the user
521
- /// * `options` - The available options to choose from
522
495
///
523
496
/// # Returns
524
- /// * `Ok(Some(index))` if user selected an option (0-based index)
525
- /// * `Ok(None)` if user declined or cancelled
497
+ /// * `Ok(Some(data))` if user provided valid data that matches type T
498
+ /// * `Err(ElicitationError::UserDeclined)` if user declined or cancelled the request
499
+ /// * `Err(ElicitationError::ParseError { .. })` if response data couldn't be parsed into type T
500
+ /// * `Err(ElicitationError::NoContent)` if no response content was provided
501
+ /// * `Err(ElicitationError::Service(_))` if the underlying service call failed
526
502
///
527
503
/// # Example
504
+ ///
505
+ /// Add to your `Cargo.toml`:
506
+ /// ```toml
507
+ /// [dependencies]
508
+ /// rmcp = { version = "0.3", features = ["elicitation"] }
509
+ /// serde = { version = "1.0", features = ["derive"] }
510
+ /// schemars = "1.0"
511
+ /// ```
512
+ ///
528
513
/// ```rust,no_run
529
514
/// # use rmcp::*;
530
- /// # async fn example(peer: Peer<RoleClient>) -> Result<(), ServiceError> {
531
- /// let options = vec!["Save", "Discard", "Cancel"];
532
- /// let choice = peer.elicit_choice("What would you like to do?", &options).await?;
533
- /// match choice {
534
- /// Some(0) => println!("User chose to save"),
535
- /// Some(1) => println!("User chose to discard"),
536
- /// Some(2) => println!("User chose to cancel"),
537
- /// _ => println!("User made no choice"),
515
+ /// # use serde::{Deserialize, Serialize};
516
+ /// # use schemars::JsonSchema;
517
+ /// #
518
+ /// #[derive(Debug, Serialize, Deserialize, JsonSchema)]
519
+ /// struct UserProfile {
520
+ /// #[schemars(description = "Full name")]
521
+ /// name: String,
522
+ /// #[schemars(description = "Email address")]
523
+ /// email: String,
524
+ /// #[schemars(description = "Age")]
525
+ /// age: u8,
526
+ /// }
527
+ ///
528
+ /// # async fn example(peer: Peer<RoleClient>) -> Result<(), Box<dyn std::error::Error>> {
529
+ /// match peer.elicit::<UserProfile>("Please enter your profile information").await {
530
+ /// Ok(Some(profile)) => {
531
+ /// println!("Name: {}, Email: {}, Age: {}", profile.name, profile.email, profile.age);
532
+ /// }
533
+ /// Err(ElicitationError::UserDeclined) => {
534
+ /// println!("User declined to provide information");
535
+ /// }
536
+ /// Err(ElicitationError::ParseError { error, data }) => {
537
+ /// println!("Failed to parse response: {}\nData: {}", error, data);
538
+ /// }
539
+ /// Err(e) => return Err(e.into()),
538
540
/// }
539
541
/// # Ok(())
540
542
/// # }
541
543
/// ```
542
- pub async fn elicit_choice (
543
- & self ,
544
- message : impl Into < String > ,
545
- options : & [ impl AsRef < str > ] ,
546
- ) -> Result < Option < usize > , ServiceError > {
547
- use serde_json:: json;
548
-
549
- let option_strings: Vec < String > = options. iter ( ) . map ( |s| s. as_ref ( ) . to_string ( ) ) . collect ( ) ;
544
+ #[ cfg( feature = "schemars" ) ]
545
+ pub async fn elicit < T > ( & self , message : impl Into < String > ) -> Result < Option < T > , ElicitationError >
546
+ where
547
+ T : schemars:: JsonSchema + for < ' de > serde:: Deserialize < ' de > ,
548
+ {
549
+ // Generate schema automatically from type
550
+ let schema = crate :: handler:: server:: tool:: schema_for_type :: < T > ( ) ;
550
551
551
552
let response = self
552
553
. create_elicitation ( CreateElicitationRequestParam {
553
554
message : message. into ( ) ,
554
- requested_schema : json ! ( {
555
- "type" : "integer" ,
556
- "minimum" : 0 ,
557
- "maximum" : option_strings. len( ) - 1 ,
558
- "description" : format!( "Choose an option: {}" , option_strings. join( ", " ) )
559
- } )
560
- . as_object ( )
561
- . unwrap ( )
562
- . clone ( ) ,
555
+ requested_schema : schema,
563
556
} )
564
557
. await ?;
565
558
566
559
match response. action {
567
560
crate :: model:: ElicitationAction :: Accept => {
568
561
if let Some ( value) = response. content {
569
- if let Some ( index) = value. as_u64 ( ) {
570
- let index = index as usize ;
571
- if index < options. len ( ) {
572
- Ok ( Some ( index) )
573
- } else {
574
- Ok ( None ) // Invalid index
575
- }
576
- } else {
577
- Ok ( None )
562
+ match serde_json:: from_value :: < T > ( value. clone ( ) ) {
563
+ Ok ( parsed) => Ok ( Some ( parsed) ) ,
564
+ Err ( error) => Err ( ElicitationError :: ParseError { error, data : value } ) ,
578
565
}
579
566
} else {
580
- Ok ( None )
567
+ Err ( ElicitationError :: NoContent )
581
568
}
582
569
}
583
- _ => Ok ( None ) ,
584
- }
585
- }
586
-
587
- /// Request structured data from the user using a custom JSON schema.
588
- ///
589
- /// This is the most flexible elicitation method, allowing you to request
590
- /// any kind of structured input using JSON Schema validation.
591
- ///
592
- /// # Arguments
593
- /// * `message` - The prompt message for the user
594
- /// * `schema` - JSON Schema defining the expected data structure
595
- ///
596
- /// # Returns
597
- /// * `Ok(Some(data))` if user provided valid data
598
- /// * `Ok(None)` if user declined or cancelled
599
- ///
600
- /// # Example
601
- /// ```rust,no_run
602
- /// # use rmcp::*;
603
- /// # use serde_json::json;
604
- /// # async fn example(peer: Peer<RoleClient>) -> Result<(), ServiceError> {
605
- /// let schema = json!({
606
- /// "type": "object",
607
- /// "properties": {
608
- /// "name": {"type": "string"},
609
- /// "email": {"type": "string", "format": "email"},
610
- /// "age": {"type": "integer", "minimum": 0}
611
- /// },
612
- /// "required": ["name", "email"]
613
- /// });
614
- ///
615
- /// let user_data = peer.elicit_structured_input(
616
- /// "Please provide your contact information:",
617
- /// schema.as_object().unwrap()
618
- /// ).await?;
619
- ///
620
- /// if let Some(data) = user_data {
621
- /// println!("Received user data: {}", data);
622
- /// }
623
- /// # Ok(())
624
- /// # }
625
- /// ```
626
- pub async fn elicit_structured_input (
627
- & self ,
628
- message : impl Into < String > ,
629
- schema : & crate :: model:: JsonObject ,
630
- ) -> Result < Option < serde_json:: Value > , ServiceError > {
631
- let response = self
632
- . create_elicitation ( CreateElicitationRequestParam {
633
- message : message. into ( ) ,
634
- requested_schema : schema. clone ( ) ,
635
- } )
636
- . await ?;
637
-
638
- match response. action {
639
- crate :: model:: ElicitationAction :: Accept => Ok ( response. content ) ,
640
- _ => Ok ( None ) ,
570
+ _ => Err ( ElicitationError :: UserDeclined ) ,
641
571
}
642
572
}
643
573
}
0 commit comments