Module: ActionMCP::Server::Elicitation

Included in:
TransportHandler
Defined in:
lib/action_mcp/server/elicitation.rb

Overview

Handles elicitation requests from the server to the client.

Two modes per MCP 2025-11-25:

- Form mode: structured data collection with JSON Schema validation
- URL mode: out-of-band interaction via external URL (sensitive data, OAuth flows)

Constant Summary collapse

URL_ELICITATION_REQUIRED_CODE =
-32_042

Instance Method Summary collapse

Instance Method Details

#send_elicitation_complete_notification(elicitation_id) ⇒ Object

Send a completion notification for a URL mode elicitation. Informs the client that the out-of-band interaction has completed.

Parameters:

  • elicitation_id (String)

    The elicitation ID from the original request



61
62
63
64
65
66
67
68
69
70
71
# File 'lib/action_mcp/server/elicitation.rb', line 61

def send_elicitation_complete_notification(elicitation_id)
  require_client_elicitation_support!(:url)
  unless elicitation_id.is_a?(String) && elicitation_id.present?
    raise ArgumentError, "elicitation_id must be a non-empty string"
  end

  send_jsonrpc_notification(
    "notifications/elicitation/complete",
    { elicitationId: elicitation_id }
  )
end

#send_elicitation_create(message:, requested_schema:, _meta: {}, task: nil) ⇒ Object

Send a form mode elicitation request to the client.

Parameters:

  • message (String)

    Human-readable message explaining why input is needed

  • requested_schema (Hash)

    JSON Schema for the expected response (primitive types only)

  • _meta (Hash) (defaults to: {})

    Optional metadata (e.g. related task)



20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/action_mcp/server/elicitation.rb', line 20

def send_elicitation_create(message:, requested_schema:, _meta: {}, task: nil)
  require_client_elicitation_support!(:form)
  require_client_elicitation_task_support! if task

  request = ElicitationRequest.new(
    message: message,
    requested_schema: requested_schema,
    _meta: _meta,
    task: task
  )
  request.assert_valid!

  send_jsonrpc_request("elicitation/create", params: request.to_params)
end

#send_elicitation_create_url(message:, url:, elicitation_id: nil, _meta: {}, task: nil) ⇒ Object

Send a URL mode elicitation request to the client. Used for sensitive data collection (API keys, OAuth, payments) that must not pass through the MCP client.

Parameters:

  • message (String)

    Human-readable message explaining why navigation is needed

  • url (String)

    The URL the user should navigate to

  • elicitation_id (String) (defaults to: nil)

    Unique identifier for this elicitation

  • _meta (Hash) (defaults to: {})

    Optional metadata (e.g. related task)



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/action_mcp/server/elicitation.rb', line 42

def send_elicitation_create_url(message:, url:, elicitation_id: nil, _meta: {}, task: nil)
  require_client_elicitation_support!(:url)
  require_client_elicitation_task_support! if task

  request = UrlElicitationRequest.new(
    message: message,
    url: url,
    elicitation_id: elicitation_id,
    _meta: _meta,
    task: task
  )
  request.assert_valid!

  send_jsonrpc_request("elicitation/create", params: request.to_params)
end

#send_url_elicitation_required_error(request_id, message:, elicitations:) ⇒ Object

Build a URLElicitationRequiredError response (-32042). Used when a request cannot proceed until an elicitation is completed.

Parameters:

  • request_id (String, Integer)

    The JSON-RPC request ID to respond to

  • message (String)

    Human-readable error message

  • elicitations (Array<Hash>)

    Required URL mode elicitations

Raises:

  • (ArgumentError)


78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/action_mcp/server/elicitation.rb', line 78

def send_url_elicitation_required_error(request_id, message:, elicitations:)
  require_client_elicitation_support!(:url)
  raise ArgumentError, "message must be a string" unless message.is_a?(String)
  raise ArgumentError, "elicitations must be an array" unless elicitations.is_a?(Array)

  normalized_elicitations = elicitations.map do |elicitation|
    unless elicitation.respond_to?(:to_h) && elicitation.to_h.is_a?(Hash)
      raise ArgumentError, "Each elicitation must be an object"
    end

    attributes = elicitation.to_h.with_indifferent_access
    raise ArgumentError, "Each elicitation must have mode: 'url'" unless attributes[:mode] == "url"
    raise ArgumentError, "Each elicitation must have an elicitationId" unless attributes[:elicitationId].present?

    request = UrlElicitationRequest.new(
      message: attributes[:message],
      url: attributes[:url],
      elicitation_id: attributes[:elicitationId],
      _meta: attributes[:_meta],
      task: attributes[:task]
    )
    request.assert_valid!

    extensions = attributes.except(:mode, :message, :url, :elicitationId, :_meta, :task)
    request.to_params.merge(extensions)
  end

  error = {
    code: URL_ELICITATION_REQUIRED_CODE,
    message: message,
    data: { elicitations: normalized_elicitations }
  }

  send_jsonrpc_response(request_id, error: error)
end