feat: Add MCP Elicitation support (#332)
* feat: implement MCP elicitation support for interactive user input 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. * feat: add typed elicitation API with enhanced error handling - 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 * fix: correct elicitation direction to comply with MCP 2025-06-18 - 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. * feat: add elicitation capability checking for server methods - 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 * fix: json rpc message schema * fix: doc tests * fix: cargo nightly fmt checks * fix: clippy * refactor: separate elicitation methods into dedicated impl block for 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 * revert: rollback LATEST protocol version to V_2025_03_26 * fix: remove protocol version assertions - Remove assertions for V_2025_06_18 protocol version * fix: fmt checks * feat: add timeout support for elicitation methods - Add peer_req_with_timeout macro variants for timeout-enabled methods - Implement create_elicitation_with_timeout() method - Implement elicit_with_timeout() for typed elicitation with timeout - Refactor elicit() to use elicit_with_timeout() internally - Add 8 comprehensive timeout tests covering validation, error handling, and realistic scenarios - Fix elicitation feature dependencies in Cargo.toml - Add proper feature gates for elicitation-specific code * feat: add timeout validation to prevent DoS attacks - Add InvalidTimeout error variant for comprehensive validation - Implement validate_timeout function with security limits (1ms-300s) - Integrate validation into peer_req_with_timeout macros - Add comprehensive security tests for timeout validation - Prevent DoS attacks through unreasonable timeout values * feat: separate UserDeclined and UserCancelled elicitation errors 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. * feat: add compile-time type safety for elicitation methods 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. * Revert "feat: add timeout validation to prevent DoS attacks" This reverts commit 829624212b4fb55ec32566329af7ec195c987ef5. * fix: correct doctest example in elicit_safe macro documentation - 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 * refactor: remove redundant elicitation direction tests - 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 * feat: add elicitation example with user name collection - 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 * fix: add Default impl to ElicitationServer for clippy Resolves clippy::new_without_default warning by implementing Default trait for ElicitationServer struct.
This commit is contained in:
parent
4e6c57ef14
commit
81ff3159f9
3 changed files with 2287 additions and 4 deletions
1466
crates/rmcp/tests/test_elicitation.rs
Normal file
1466
crates/rmcp/tests/test_elicitation.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -257,6 +257,17 @@
|
|||
"description": "```rust\n# use rmcp::model::ClientCapabilities;\nlet cap = ClientCapabilities::builder()\n .enable_experimental()\n .enable_roots()\n .enable_roots_list_changed()\n .build();\n```",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"elicitation": {
|
||||
"description": "Capability to handle elicitation requests from servers for interactive user input",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ElicitationCapability"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"experimental": {
|
||||
"type": [
|
||||
"object",
|
||||
|
|
@ -294,6 +305,9 @@
|
|||
{
|
||||
"$ref": "#/definitions/ListRootsResult"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/CreateElicitationResult"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/EmptyObject"
|
||||
}
|
||||
|
|
@ -319,6 +333,26 @@
|
|||
"argument"
|
||||
]
|
||||
},
|
||||
"CreateElicitationResult": {
|
||||
"description": "The result returned by a client in response to an elicitation request.\n\nContains the user's decision (accept/decline/cancel) and optionally their input data\nif they chose to accept the request.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"description": "The user's decision on how to handle the elicitation request",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ElicitationAction"
|
||||
}
|
||||
]
|
||||
},
|
||||
"content": {
|
||||
"description": "The actual data provided by the user, if they accepted the request.\nMust conform to the JSON schema specified in the original request.\nOnly present when action is Accept."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"action"
|
||||
]
|
||||
},
|
||||
"CreateMessageResult": {
|
||||
"description": "The result of a sampling/createMessage request containing the generated response.\n\nThis structure contains the generated message along with metadata about\nhow the generation was performed and why it stopped.",
|
||||
"type": "object",
|
||||
|
|
@ -357,6 +391,39 @@
|
|||
"content"
|
||||
]
|
||||
},
|
||||
"ElicitationAction": {
|
||||
"description": "Represents the possible actions a user can take in response to an elicitation request.\n\nWhen a server requests user input through elicitation, the user can:\n- Accept: Provide the requested information and continue\n- Decline: Refuse to provide the information but continue the operation\n- Cancel: Stop the entire operation",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "User accepts the request and provides the requested information",
|
||||
"type": "string",
|
||||
"const": "accept"
|
||||
},
|
||||
{
|
||||
"description": "User declines to provide the information but allows the operation to continue",
|
||||
"type": "string",
|
||||
"const": "decline"
|
||||
},
|
||||
{
|
||||
"description": "User cancels the entire operation",
|
||||
"type": "string",
|
||||
"const": "cancel"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ElicitationCapability": {
|
||||
"description": "Capability for handling elicitation requests from servers.\n\nElicitation allows servers to request interactive input from users during tool execution.\nThis capability indicates that a client can handle elicitation requests and present\nappropriate UI to users for collecting the requested information.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"schemaValidation": {
|
||||
"description": "Whether the client supports JSON Schema validation for elicitation responses.\nWhen true, the client will validate user input against the requested_schema\nbefore sending the response back to the server.",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"EmptyObject": {
|
||||
"description": "This is commonly used for representing empty objects in MCP messages.\n\nwithout returning any specific data.",
|
||||
"type": "object"
|
||||
|
|
|
|||
|
|
@ -405,6 +405,45 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"CreateElicitationRequestParam": {
|
||||
"description": "Parameters for creating an elicitation request to gather user input.\n\nThis structure contains everything needed to request interactive input from a user:\n- A human-readable message explaining what information is needed\n- A JSON schema defining the expected structure of the response",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"description": "Human-readable message explaining what input is needed from the user.\nThis should be clear and provide sufficient context for the user to understand\nwhat information they need to provide.",
|
||||
"type": "string"
|
||||
},
|
||||
"requestedSchema": {
|
||||
"description": "JSON Schema defining the expected structure and validation rules for the user's response.\nThis allows clients to validate input and provide appropriate UI controls.\nMust be a valid JSON Schema Draft 2020-12 object.",
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"message",
|
||||
"requestedSchema"
|
||||
]
|
||||
},
|
||||
"CreateElicitationResult": {
|
||||
"description": "The result returned by a client in response to an elicitation request.\n\nContains the user's decision (accept/decline/cancel) and optionally their input data\nif they chose to accept the request.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"description": "The user's decision on how to handle the elicitation request",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ElicitationAction"
|
||||
}
|
||||
]
|
||||
},
|
||||
"content": {
|
||||
"description": "The actual data provided by the user, if they accepted the request.\nMust conform to the JSON schema specified in the original request.\nOnly present when action is Accept."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"action"
|
||||
]
|
||||
},
|
||||
"CreateMessageRequestMethod": {
|
||||
"type": "string",
|
||||
"format": "const",
|
||||
|
|
@ -483,6 +522,31 @@
|
|||
"maxTokens"
|
||||
]
|
||||
},
|
||||
"ElicitationAction": {
|
||||
"description": "Represents the possible actions a user can take in response to an elicitation request.\n\nWhen a server requests user input through elicitation, the user can:\n- Accept: Provide the requested information and continue\n- Decline: Refuse to provide the information but continue the operation\n- Cancel: Stop the entire operation",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "User accepts the request and provides the requested information",
|
||||
"type": "string",
|
||||
"const": "accept"
|
||||
},
|
||||
{
|
||||
"description": "User declines to provide the information but allows the operation to continue",
|
||||
"type": "string",
|
||||
"const": "decline"
|
||||
},
|
||||
{
|
||||
"description": "User cancels the entire operation",
|
||||
"type": "string",
|
||||
"const": "cancel"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ElicitationCreateRequestMethod": {
|
||||
"type": "string",
|
||||
"format": "const",
|
||||
"const": "elicitation/create"
|
||||
},
|
||||
"EmptyObject": {
|
||||
"description": "This is commonly used for representing empty objects in MCP messages.\n\nwithout returning any specific data.",
|
||||
"type": "object"
|
||||
|
|
@ -686,6 +750,9 @@
|
|||
},
|
||||
{
|
||||
"$ref": "#/definitions/RequestNoParam2"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/Request2"
|
||||
}
|
||||
],
|
||||
"required": [
|
||||
|
|
@ -1307,6 +1374,22 @@
|
|||
"params"
|
||||
]
|
||||
},
|
||||
"Request2": {
|
||||
"description": "Represents a JSON-RPC request with method, parameters, and extensions.\n\nThis is the core structure for all MCP requests, containing:\n- `method`: The name of the method being called\n- `params`: The parameters for the method\n- `extensions`: Additional context data (similar to HTTP headers)",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"method": {
|
||||
"$ref": "#/definitions/ElicitationCreateRequestMethod"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/CreateElicitationRequestParam"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"method",
|
||||
"params"
|
||||
]
|
||||
},
|
||||
"RequestNoParam": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
@ -1546,6 +1629,9 @@
|
|||
{
|
||||
"$ref": "#/definitions/ListToolsResult"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/CreateElicitationResult"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/EmptyObject"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue