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
2 changed files with 566 additions and 36 deletions

View file

@ -1,5 +1 @@
server:
# TODO: Server-to-client requests (elicitation/create) are not implemented.
- tools-call-elicitation
- elicitation-sep1034-defaults
- elicitation-sep1330-enums
server: []

View file

@ -178,7 +178,6 @@ module Conformance
end
end
# TODO: Implement when `Transport` supports server-to-client requests.
class TestElicitation < MCP::Tool
tool_name "test_elicitation"
description "A tool that requests user input from the client"
@ -188,41 +187,96 @@ module Conformance
)
class << self
def call(message:)
MCP::Tool::Response.new(
[MCP::Content::Text.new("Elicitation not supported in this SDK version").to_h],
error: true,
def call(server_context:, message:)
result = server_context.create_form_elicitation(
message: message,
requested_schema: {
type: "object",
properties: {
username: { type: "string", description: "User's response" },
email: { type: "string", description: "User's email address" },
},
required: ["username", "email"],
},
)
MCP::Tool::Response.new([MCP::Content::Text.new("User response: #{result}").to_h])
end
end
end
# TODO: Implement when `Transport` supports server-to-client requests.
class TestElicitationSep1034Defaults < MCP::Tool
tool_name "test_elicitation_sep1034_defaults"
description "A tool that tests elicitation with default values"
class << self
def call(**_args)
MCP::Tool::Response.new(
[MCP::Content::Text.new("Elicitation not supported in this SDK version").to_h],
error: true,
def call(server_context:, **_args)
result = server_context.create_form_elicitation(
message: "Please provide your information (with defaults)",
requested_schema: {
type: "object",
properties: {
name: { type: "string", default: "John Doe" },
age: { type: "integer", default: 30 },
score: { type: "number", default: 95.5 },
status: { type: "string", enum: ["active", "inactive", "pending"], default: "active" },
verified: { type: "boolean", default: true },
},
},
)
MCP::Tool::Response.new([MCP::Content::Text.new("Elicitation result: #{result}").to_h])
end
end
end
# TODO: Implement when `Transport` supports server-to-client requests.
class TestElicitationSep1330Enums < MCP::Tool
tool_name "test_elicitation_sep1330_enums"
description "A tool that tests elicitation with enum schemas"
class << self
def call(**_args)
MCP::Tool::Response.new(
[MCP::Content::Text.new("Elicitation not supported in this SDK version").to_h],
error: true,
def call(server_context:, **_args)
result = server_context.create_form_elicitation(
message: "Please select options",
requested_schema: {
type: "object",
properties: {
untitledSingle: {
type: "string",
enum: ["option1", "option2", "option3"],
},
titledSingle: {
type: "string",
oneOf: [
{ const: "value1", title: "First Option" },
{ const: "value2", title: "Second Option" },
{ const: "value3", title: "Third Option" },
],
},
legacyEnum: {
type: "string",
enum: ["opt1", "opt2", "opt3"],
enumNames: ["Option One", "Option Two", "Option Three"],
},
untitledMulti: {
type: "array",
items: {
type: "string",
enum: ["option1", "option2", "option3"],
},
},
titledMulti: {
type: "array",
items: {
anyOf: [
{ const: "value1", title: "First Choice" },
{ const: "value2", title: "Second Choice" },
{ const: "value3", title: "Third Choice" },
],
},
},
},
},
)
MCP::Tool::Response.new([MCP::Content::Text.new("Elicitation result: #{result}").to_h])
end
end
end