Module: MCPClient::InputRoundTrips

Included in:
JsonRpcCommon
Defined in:
lib/mcp_client/input_round_trips.rb

Overview

Fulfilment of the input requests an input_required result carries (MCP 2026-07-28 multi-round tool requests). Mixed into JsonRpcCommon, whose host supplies the registered handlers.

Constant Summary collapse

INPUT_REQUEST_HANDLERS =

Input request methods and the transport callback that fulfils each.

{
  'elicitation/create' => :@elicitation_request_callback,
  'sampling/createMessage' => :@sampling_request_callback,
  'roots/list' => :@roots_list_request_callback
}.freeze

Instance Method Summary collapse

Instance Method Details

#fulfil_input_request(key, request, result) ⇒ Hash

Returns the handler's result.

Parameters:

  • key (String) —

    the server-assigned request key

  • request (Hash) —

    the input request ({ 'method' => ..., 'params' => ... })

  • result (Hash) —

    the InputRequiredResult (for error data)

Returns:

  • (Hash) —

    the handler's result

Raises:



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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
# File 'lib/mcp_client/input_round_trips.rb', line 49

def fulfil_input_request(key, request, result)
  shown_key = sanitize_log_text(key.to_s.inspect)
  unless request.is_a?(Hash) && request['method'].is_a?(String) &&
         (request['params'].nil? || request['params'].is_a?(Hash))
    raise MCPClient::Errors::InputRequiredError.new("Malformed input request #{shown_key} (method/params)",
                                                    data: result)
  end

  request_method = request['method']
  shown_method = sanitize_log_text(request_method.inspect)
  handler_ivar = INPUT_REQUEST_HANDLERS[request_method]
  unless handler_ivar
    raise MCPClient::Errors::InputRequiredError.new(
      "Unsupported input request method #{shown_method} for key #{shown_key}", data: result
    )
  end
  unless registered_callback?(handler_ivar)
    raise MCPClient::Errors::InputRequiredError.new(
      "Server requested #{shown_method} (key #{shown_key}) but no handler is registered for it " \
      '(the capability was not declared)', data: result
    )
  end

  # The handler this reaches is the same callback a legacy
  # server-initiated request would have used, so Roots and Sampling are
  # just as deprecated here (SEP-2577). The notice precedes the
  # sampling.tools refusal below, as it precedes the handler's own
  # -32602 on the server-initiated path: the deprecated values were on
  # the wire and a host with a handler asked for them, whichever
  # sub-capability the request then trips over.
  warn_input_request_deprecated(request_method, request['params'])
  if undeclared_sampling_tool_use?(request_method, request['params'])
    raise MCPClient::Errors::InputRequiredError.new(
      "Server requested tool-enabled #{shown_method} (key #{shown_key}) but the sampling.tools " \
      'capability was not declared', data: result
    )
  end

  begin
    response = instance_variable_get(handler_ivar).call(key, request['params'] || {})
  rescue StandardError => e
    # The exception text is host-internal; it stays in the local log.
    @logger.error("Handler for #{shown_method} (key #{shown_key}) raised: #{e.message}")
    raise MCPClient::Errors::InputRequiredError.new(
      "Handler for #{shown_method} (key #{shown_key}) failed", data: result
    )
  end
  unless response.is_a?(Hash)
    raise MCPClient::Errors::InputRequiredError.new(
      "Handler for #{shown_method} (key #{shown_key}) returned #{response.class}, expected a result object",
      data: result
    )
  end
  if (error = response['error'] || response[:error])
    message = error.is_a?(Hash) ? (error['message'] || error[:message]) : error
    raise MCPClient::Errors::InputRequiredError.new(
      "Handler for #{shown_method} (key #{shown_key}) failed: #{sanitize_log_text(message)}", data: result
    )
  end

  warn_input_request_answer_deprecated(request_method, response)
  response
end

#fulfil_input_requests(input_requests, result) ⇒ Hash

Fulfil every input request through the handler registered for its method. There is no per-key error channel in InputResponses, so any request this client cannot honour fails the whole round trip.

The answers produced before the failure travel with the error (Errors::InputRequiredError#answered_so_far): a request the host already answered has been put to a person, and a caller that can keep it — the tasks extension's poll loop — must not ask them again.

Parameters:

  • input_requests (Hash) —

    the InputRequests map

  • result (Hash) —

    the InputRequiredResult (for error data)

Returns:

  • (Hash) —

    the InputResponses map

Raises:



29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/mcp_client/input_round_trips.rb', line 29

def fulfil_input_requests(input_requests, result)
  unless input_requests.is_a?(Hash)
    raise MCPClient::Errors::InputRequiredError.new('Malformed InputRequiredResult: inputRequests is not an object',
                                                    data: result)
  end

  responses = {}
  input_requests.each do |key, request|
    responses[key] = fulfil_input_request(key, request, result)
  rescue MCPClient::Errors::InputRequiredError => e
    raise e.with_answered_so_far(responses)
  end
  responses
end

#undeclared_sampling_tool_use?(method, params) ⇒ Boolean

SEP-1577 (sampling tool calling): a server MUST NOT send tools or toolChoice to a client that did not declare the sampling.tools sub-capability. On a server-initiated request the client answers -32602; InputResponses has no per-request error channel, so on the multi round-trip path the whole round trip fails instead — the sampler is never invoked with a request this client never advertised support for.

Parameters:

  • method (String) —

    the input request method

  • params (Hash, nil) —

    the input request params

Returns:

  • (Boolean) —

    whether this is tool-enabled sampling without the declaration



122
123
124
125
126
# File 'lib/mcp_client/input_round_trips.rb', line 122

def undeclared_sampling_tool_use?(method, params)
  return false unless method == 'sampling/createMessage' && !sampling_tools_supported?

  params.is_a?(Hash) && (params.key?('tools') || params.key?('toolChoice'))
end