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
-
#fulfil_input_request(key, request, result) ⇒ Hash
The handler's result.
-
#fulfil_input_requests(input_requests, result) ⇒ Hash
Fulfil every input request through the handler registered for its method.
-
#undeclared_sampling_tool_use?(method, params) ⇒ Boolean
SEP-1577 (sampling tool calling): a server MUST NOT send
toolsortoolChoiceto a client that did not declare the sampling.tools sub-capability.
Instance Method Details
#fulfil_input_request(key, request, result) ⇒ Hash
Returns the handler's result.
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.}") 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]) = 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()}", 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.
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.
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 |