Support elicitation/create per MCP specification

## Motivation and Context

The MCP specification (2025-11-25) defines elicitation as a server-to-client
request mechanism that allows servers to request user input during tool execution:
https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation

This aligns with the Python SDK, which also exposes separate methods for form and URL mode elicitation.
Three methods are added following the Python SDK's API design:

- `create_form_elicitation`: collects structured data via JSON Schema
- `create_url_elicitation`: directs users to external URLs for sensitive
  interactions (OAuth, credentials, etc.)
- `notify_elicitation_complete`: notifies the client that a URL mode
  interaction has completed

`URLElicitationRequiredError` (error code `-32042`) is defined as a subclass of `RequestHandlerError`
with its own `error_code` and `error_data` attributes. `JsonRpcHandler#handle_request_error` is
extended to check for these attributes so that MCP-specific error codes can be propagated without
embedding MCP knowledge in the generic JSON-RPC layer.

## How Has This Been Tested?

- Unit tests for `ServerSession` form/URL mode, capability checks, nil
  capabilities, notification, and `URLElicitationRequiredError`
- `ServerContext` delegation tests for all three elicitation methods
- `JsonRpcHandler` tests for custom error code and fallback behavior
- `Methods` capability test for `NOTIFICATIONS_ELICITATION_COMPLETE`
- Conformance tests pass (40/40), including `tools-call-elicitation`,
  `elicitation-sep1034-defaults`, and `elicitation-sep1330-enums`
- All tests pass (`rake test`), RuboCop is clean
- Verified form mode elicitation end-to-end with a sample MCP server
  running in Claude Code

## Breaking Changes

None. This is a new feature addition.
This commit is contained in:
Koichi ITO 2026-04-06 17:14:26 +09:00
commit 985fd03ecb

View file

@ -117,20 +117,27 @@ module JsonRpcHandler
end
def handle_request_error(error, id, id_validation_pattern)
error_type = error.respond_to?(:error_type) ? error.error_type : nil
if error.respond_to?(:error_code) && error.error_code
code = error.error_code
message = error.message
else
error_type = error.respond_to?(:error_type) ? error.error_type : nil
code, message = case error_type
when :invalid_request then [ErrorCode::INVALID_REQUEST, "Invalid Request"]
when :invalid_params then [ErrorCode::INVALID_PARAMS, "Invalid params"]
when :parse_error then [ErrorCode::PARSE_ERROR, "Parse error"]
when :internal_error then [ErrorCode::INTERNAL_ERROR, "Internal error"]
else [ErrorCode::INTERNAL_ERROR, "Internal error"]
code, message = case error_type
when :invalid_request then [ErrorCode::INVALID_REQUEST, "Invalid Request"]
when :invalid_params then [ErrorCode::INVALID_PARAMS, "Invalid params"]
when :parse_error then [ErrorCode::PARSE_ERROR, "Parse error"]
when :internal_error then [ErrorCode::INTERNAL_ERROR, "Internal error"]
else [ErrorCode::INTERNAL_ERROR, "Internal error"]
end
end
data = error.respond_to?(:error_data) && error.error_data ? error.error_data : error.message
error_response(id: id, id_validation_pattern: id_validation_pattern, error: {
code: code,
message: message,
data: error.message,
data: data,
})
end