Skip to content

feat: Add MCP Elicitation support #332

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: main
Choose a base branch
from

Conversation

bug-ops
Copy link

@bug-ops bug-ops commented Jul 23, 2025

This PR implements comprehensive MCP Elicitation support according to the MCP 2025-06-18 specification, enabling servers to request interactive user input during tool execution with full JSON Schema validation and type-safe convenience methods.

Closes #304

Motivation and Context

Elicitation functionality introduced in the MCP 2025-06-18 specification. This allows servers to create interactive workflows that require user input during tool execution.

Solution: This change adds complete elicitation support, enabling:

  • Interactive confirmations for destructive operations (e.g., "Delete this file?")
  • Dynamic parameter collection when initial context is insufficient
  • Structured data input with JSON Schema validation
  • User-guided workflows for complex multi-step processes

Context: Based on MCP PR #382 which introduced elicitation to the MCP specification. This implementation ensures rust-sdk maintains full feature parity with the latest MCP standards.

How Has This Been Tested?

Comprehensive Test Suite (11 test cases):

  • JSON serialization/deserialization - All data structures correctly serialize to/from JSON
  • JSON-RPC protocol compliance - Full request/response cycle validation
  • MCP 2025-06-18 specification compliance - Method names, protocol version, enum values
  • Error handling and edge cases - Invalid schemas, empty messages, malformed requests
  • Performance benchmarks - 1000 serialization cycles under 1000ms
  • Capabilities integration - Builder pattern and schema validation flags
  • Convenience methods validation - All four helper methods produce correct schemas
  • Complex JSON Schema support - Nested objects, arrays, validation rules

Quality Assurance:

  • cargo check - No compilation errors
  • cargo clippy - No linting warnings
  • cargo test - All 11 elicitation tests pass
  • cargo fmt - Code properly formatted

Scenarios Tested:

  • Simple yes/no confirmations
  • Text input with/without validation requirements
  • Multiple choice selections
  • Complex structured data collection with nested JSON schemas
  • Error conditions and user cancellation flows

Breaking Changes

No breaking changes - This is a purely additive implementation:

  • All existing APIs remain unchanged
  • Backward compatibility maintained for all protocol versions
  • Elicitation is opt-in via capabilities negotiation
  • Servers without elicitation support continue working normally

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Implementation Highlights:

Core Architecture:

  • ElicitationAction enum - Accept, Decline, Cancel actions
  • Type-safe structures - CreateElicitationRequestParam, CreateElicitationResult
  • Protocol integration - New V_2025_06_18 version with method constants
  • JSON-RPC compliance - Full request/response cycle support
  • "elicitation" feature with schemars dependency

Developer Experience:

  • convenience methods for common patterns:
    • elicit_structured_input() - Complex JSON Schema validation
    • elicit<T>() - Typed data request with JSON auto generation

Capabilities System:

  • ElicitationCapability with optional schema validation
  • Builder pattern integration - enable_elicitation(), enable_elicitation_schema_validation()
  • Seamless integration with existing capabilities architecture

This implementation makes rust-sdk compatible with MCP 2025-06-18 and enables interactive workflows while maintaining full backward compatibility.

Adds comprehensive elicitation functionality according to MCP 2025-06-18 specification:

Core Features:
- ElicitationAction enum (Accept, Decline, Cancel)
- CreateElicitationRequestParam and CreateElicitationResult structures
- Protocol version V_2025_06_18 with elicitation methods
- Full JSON-RPC integration with method constants

Capabilities Integration:
- ElicitationCapability with schema validation support
- ClientCapabilities builder pattern integration
- enable_elicitation() and enable_elicitation_schema_validation() methods

Handler Support:
- create_elicitation method in ClientHandler and ServerHandler traits
- Integration with existing request/response union types
- Async/await compatible implementation

Service Layer:
- Basic create_elicitation method via macro expansion
- Four convenience methods for common scenarios:
  * elicit_confirmation() - yes/no questions
  * elicit_text_input() - string input with optional requirements
  * elicit_choice() - selection from multiple options
  * elicit_structured_input() - complex data via JSON Schema

Comprehensive Testing:
- 11 test cases covering all functionality aspects
- JSON serialization/deserialization validation
- MCP specification compliance verification
- Error handling and edge cases
- Performance benchmarks
- Capabilities integration tests

All tests pass and code follows project standards.
@github-actions github-actions bot added T-test Testing related changes T-core Core library changes T-handler Handler implementation changes T-model Model/data structure changes T-service Service layer changes labels Jul 23, 2025
@bug-ops bug-ops changed the title feat: Add MCP Elicitation support for interactive user input feat: Add MCP Elicitation support Jul 23, 2025
- Add new 'elicitation' feature that depends on 'client' and 'schemars'
- Implement elicit<T>() method for type-safe elicitation with automatic schema generation
- Remove convenience methods (elicit_confirmation, elicit_text_input, elicit_choice)
- Add ElicitationError enum with detailed error variants:
  - Service: underlying service errors
  - UserDeclined: user cancelled or declined request
  - ParseError: response parsing failed with context
  - NoContent: no response content provided
- Update documentation with comprehensive examples and error handling
- Add comprehensive tests for typed elicitation and error handling
@github-actions github-actions bot added T-dependencies Dependencies related changes T-config Configuration file changes labels Jul 23, 2025
@bug-ops bug-ops marked this pull request as ready for review July 23, 2025 21:26
@bug-ops bug-ops marked this pull request as draft July 23, 2025 21:45
bug-ops added 2 commits July 24, 2025 01:17
- Remove CreateElicitationRequest from ClientRequest - clients cannot initiate elicitation
- Move elicit methods from client to server - servers now request user input
- Add comprehensive direction tests verifying Server→Client→Server flow
- Maintain CreateElicitationResult in ClientResult for proper responses
- Update handlers to reflect correct message routing
- Add elicitation feature flag for typed schema generation

Fixes elicitation direction to match specification where servers request
interactive user input from clients, not the reverse.
- Add supports_elicitation() method to check client capabilities
- Add CapabilityNotSupported error variant to ElicitationError
- Update elicit_structured_input() to check capabilities before execution
- Update elicit<T>() method to check capabilities before execution
- Add comprehensive tests for capability checking functionality
- Tests verify that servers check client capabilities before sending elicitation requests
- Ensures compliance with MCP 2025-06-18 specification requirement
@bug-ops bug-ops marked this pull request as ready for review July 23, 2025 23:11
@jokemanfire jokemanfire added this to the version 0.3.x milestone Jul 24, 2025
@bug-ops
Copy link
Author

bug-ops commented Jul 24, 2025

I think we might want to consider implementing different request options (including timeouts) depending on the request type. For elicitation calls, they could potentially require longer timeouts and would benefit from being configurable.

pub const V_2025_03_26: Self = Self(Cow::Borrowed("2025-03-26"));
pub const V_2024_11_05: Self = Self(Cow::Borrowed("2024-11-05"));
pub const LATEST: Self = Self::V_2025_03_26;
pub const LATEST: Self = Self::V_2025_06_18;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't change it to 20250618 now, since we haven't implment all the new features

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! Reverted LATEST back to V_2025_03_26 in commit 923d9aa.

@4t145
Copy link
Collaborator

4t145 commented Jul 28, 2025

Sorry for late response, it looks good to me.

@bug-ops bug-ops requested a review from 4t145 July 30, 2025 22:47
@jokemanfire
Copy link
Collaborator

CI fail , check pls

bug-ops added 6 commits July 31, 2025 23:28
…RoleServer

- Move (supports_elicitation, elicit_structured_input, elicit) to separate impl block
- Move ElicitationError definition to elicitation methods section
- Keep base methods (create_message, list_roots, notify_*) in main impl block with macro
- Add section comments to distinguish general and elicitation-specific methods
@bug-ops bug-ops force-pushed the feature/elicitation branch from 923d9aa to 01dd3e8 Compare July 31, 2025 20:31
@bug-ops bug-ops marked this pull request as draft July 31, 2025 20:38
- Remove assertions for V_2025_06_18 protocol version
According to MCP specification and PR feedback, decline and cancel
actions should be handled differently:

- UserDeclined: explicit user rejection (clicked "Decline", "No", etc.)
- UserCancelled: dismissal without explicit choice (closed dialog, Escape, etc.)

Changes:
- Split ElicitationError::UserDeclined into two distinct error types
- Update error handling logic to map each ElicitationAction correctly
- Improve documentation with proper action semantics
- Add comprehensive tests for new error types and action mapping
- Update examples to demonstrate proper error handling

This provides better error granularity allowing servers to handle
explicit declines vs cancellations appropriately as per MCP spec.
Add ElicitationSafe trait and elicit_safe\! macro to ensure elicit<T>()
methods are only used with types that generate appropriate JSON object
schemas, addressing type safety concerns from PR feedback.

Features:
- ElicitationSafe marker trait for compile-time constraints
- elicit_safe\! macro for opt-in type safety declaration
- Updated elicit<T> and elicit_with_timeout<T> to require ElicitationSafe bound
- Comprehensive documentation with examples and rationale
- Full test coverage for new type safety features

This prevents common mistakes like:
- elicit::<String>() - primitives not suitable for object schemas
- elicit::<Vec<i32>>() - arrays don't match client expectations

Breaking change: Existing code must add elicit_safe\!(TypeName) declarations
for types used with elicit methods. This is an intentional safety improvement.
@bug-ops bug-ops marked this pull request as ready for review August 12, 2025 14:55
@bug-ops bug-ops requested a review from jamadeo August 12, 2025 14:55
jamadeo
jamadeo previously approved these changes Aug 12, 2025
Copy link
Collaborator

@jamadeo jamadeo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks reasonable to me, but I question the addition of the validate_timeout function.

@@ -3,6 +3,34 @@ use std::borrow::Cow;
use thiserror::Error;

use super::*;

/// Validates timeout values to prevent DoS attacks and ensure reasonable limits
fn validate_timeout(timeout: Option<std::time::Duration>) -> Result<(), ServiceError> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm is this necessary? Why does it need to be limited to 1ms-5min? Values outside of that might be very uncommon but I'd think that should be left to the server developer.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jamadeo You're absolutely right. Hard-coding these limits was an oversight - server developers should have control over timeout policies for their specific use cases.

I'll remove the validation and let developers implement their own timeout constraints as needed. The library shouldn't enforce arbitrary business logic decisions.

Thanks for catching this!

@bug-ops bug-ops marked this pull request as draft August 12, 2025 18:41
- Remove invalid async/await usage in doctest example
- Comment out the actual usage line to show intent without compilation errors
- Maintain clear documentation of the macro's purpose and usage
@bug-ops bug-ops marked this pull request as ready for review August 12, 2025 18:51
@bug-ops bug-ops requested a review from jamadeo August 12, 2025 18:51
4t145
4t145 previously approved these changes Aug 14, 2025
Copy link
Collaborator

@4t145 4t145 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me

@4t145 4t145 requested a review from Copilot August 14, 2025 07:00
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements comprehensive MCP Elicitation support according to the MCP 2025-06-18 specification, enabling servers to request interactive user input during tool execution with full JSON Schema validation and type-safe convenience methods.

  • Added core elicitation data structures and types for the "elicitation/create" protocol
  • Implemented server-side convenience methods with automatic schema generation for typed data collection
  • Added client capabilities system integration with ElicitationCapability support

Reviewed Changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
crates/rmcp/tests/test_message_schema/server_json_rpc_message_schema.json Added JSON schema definitions for elicitation request/response structures
crates/rmcp/tests/test_message_schema/client_json_rpc_message_schema.json Added client-side elicitation capability and result schema definitions
crates/rmcp/tests/test_elicitation.rs Comprehensive test suite covering serialization, protocol compliance, and typed elicitation functionality
crates/rmcp/src/service/server.rs Added elicitation convenience methods, error types, and typed elicitation with timeout support
crates/rmcp/src/model/meta.rs Added CreateElicitationRequest to ServerRequest variant extensions
crates/rmcp/src/model/capabilities.rs Added ElicitationCapability struct and builder methods for capability negotiation
crates/rmcp/src/model.rs Added core elicitation types, protocol version, and JSON-RPC integration
crates/rmcp/src/handler/client.rs Added client handler method for processing elicitation requests
crates/rmcp/Cargo.toml Added "elicitation" feature flag and test configuration

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.


// Verify the list doesn't contain elicitation
assert!(!valid_client_requests.contains(&"CreateElicitationRequest"));
assert_eq!(valid_client_requests.len(), 13); // Should be 13, not 14
Copy link
Preview

Copilot AI Aug 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coded magic number 13 in the assertion makes the test brittle. If new request types are added, this test will break. Consider using the actual length of the array or a more robust validation approach.

Suggested change
assert_eq!(valid_client_requests.len(), 13); // Should be 13, not 14

Copilot uses AI. Check for mistakes.

Copy link
Collaborator

@4t145 4t145 Aug 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get the point of this testcase, it looks like some LLM generated stuff

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're absolutely right - I missed cleaning up this redundant code during refactoring. The presence of CreateElicitationRequest in ServerRequest is already verified by the test_elicitation_direction_server_to_client test, which
comprehensively checks the protocol direction constraints.

This match block can be safely removed since it's purely duplicative testing. I'll clean this up.

- Remove test_elicitation_not_in_client_request (duplicated functionality)
- Remove redundant ServerRequest match in test_elicitation_direction_server_to_client
- Direction compliance is already verified by the remaining comprehensive test
- Reduces test fragility and maintenance burden
4t145
4t145 previously approved these changes Aug 14, 2025
@4t145
Copy link
Collaborator

4t145 commented Aug 14, 2025

@jamadeo Could you review again?

jamadeo
jamadeo previously approved these changes Aug 15, 2025
@jamadeo
Copy link
Collaborator

jamadeo commented Aug 15, 2025

I tested this out from a client perspective by adding a trivial handler in Goose + the "everything" test server, and looked good!

@jokemanfire
Copy link
Collaborator

Thanks , LGTM , If you can add an example about this feature, that will be greater.

@@ -324,11 +328,68 @@ macro_rules! method {
Ok(())
}
};

// Timeout-only variants (base method should be created separately with peer_req)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these two macros have same public code ,we can extract it.

- Add elicitation server example demonstrating real MCP usage
- Implement greet_user tool with context.peer.elicit::<T>() API
- Show type-safe elicitation with elicit_safe! macro
- Include reset_name tool and MCP Inspector instructions
- Update examples documentation and dependencies
@bug-ops bug-ops dismissed stale reviews from jamadeo and 4t145 via fd54781 August 16, 2025 20:08
@github-actions github-actions bot added T-documentation Documentation improvements T-examples Example code changes labels Aug 16, 2025
@bug-ops bug-ops requested a review from jokemanfire August 16, 2025 20:14
@bug-ops
Copy link
Author

bug-ops commented Aug 16, 2025

@jokemanfire Added a simple elicitation example demonstrating the feature in action

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-config Configuration file changes T-core Core library changes T-dependencies Dependencies related changes T-documentation Documentation improvements T-examples Example code changes T-handler Handler implementation changes T-model Model/data structure changes T-service Service layer changes T-test Testing related changes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Feature Request] Implement MCP Elicitation Support (MCP 2025-06-18)
5 participants