# `ConduitMcp.Client.Request.MRTR`
[🔗](https://github.com/nyo16/conduit_mcp/blob/v0.11.0/lib/conduit_mcp/client/request/mrtr.ex#L1)

The client side of modern (`2026-07-28`) multi round-trip requests.

A `tools/call`, `resources/read` or `prompts/get` may come back with
`resultType: "input_required"`: the server needs answers (elicitation,
sampling, roots) before it can finish. `run/3` drives that loop. It does no
I/O itself: sending a request, allocating ids and answering input requests
are injected functions, so it runs in the caller and is testable without a
server.

## One round

  1. Take a fresh JSON-RPC id from `:next_id` (ids are never reused) and
     call `:send` with the method and params.
  2. A result with no `resultType`, or `"complete"`, ends the loop. So does a
     type listed in `:result_types` (for example `"task"` when the tasks
     extension is in use). Any other value is invalid: a `:protocol` error.
  3. For `"input_required"`: check every input request first (method known
     and declared in `:capabilities`; an undeclared one is a `:protocol`
     error and **no** handler runs), then call `:handle_input` for each,
     in key order, and retry with the **same method and params** plus
     `inputResponses` and the `requestState` echoed byte-exact. A missing
     `requestState` is omitted on the retry, never sent empty; a missing
     `inputRequests` means retry at once with `requestState` alone.

Only the original params are reused: `inputResponses` and `requestState`
from a previous round never leak into the next. `requestState` is held as
a `ConduitMcp.Client.Secret` between rounds and is never put in an error.

## Limits (MR-4)

All three give `%ConduitMcp.Client.Error{kind: :mrtr_round_limit}`, with the
cap that tripped as the reason:

  * `:max_rounds` (default 8) - requests sent, retries included; an
    `input_required` answer to the last allowed round is the error.
  * `:max_input_requests` (default 16) - input requests in one round.
  * `:max_request_state_bytes` (default 65_536) - size of `requestState`.

## Handler failures

`:handle_input` returning `{:error, reason}` or a non-map, raising,
throwing or exiting aborts the loop with `kind: :input_handler`; the reason
is `{:handler_error, key}`, `{:invalid_response, key}` or `{:raised, key}`.

# `input_fun`

```elixir
@type input_fun() :: (String.t(), map() -&gt; {:ok, map()} | {:error, term()})
```

Answers one input request: `(key, %{"method" => _, "params" => _})`.

# `option`

```elixir
@type option() ::
  {:send, send_fun()}
  | {:next_id, (-&gt; term())}
  | {:handle_input, input_fun()}
  | {:capabilities, map()}
  | {:result_types, [String.t()]}
  | {:max_rounds, pos_integer()}
  | {:max_input_requests, pos_integer()}
  | {:max_request_state_bytes, pos_integer()}
```

# `send_fun`

```elixir
@type send_fun() :: (term(), String.t(), map() -&gt;
                 {:ok, term()} | {:error, ConduitMcp.Client.Error.t()})
```

Sends one request: `(id, method, params)`.

# `run`

```elixir
@spec run(String.t(), map(), [option()]) ::
  {:ok, term(), %{rounds: pos_integer()}}
  | {:error, ConduitMcp.Client.Error.t()}
```

Runs `method` with `params` to completion.

Returns `{:ok, result, %{rounds: n}}` with the final string-keyed result
map and the number of requests sent, or `{:error, %Error{}}`; an error from
`:send` is returned unchanged.

## Options

  * `:send` (required) - `t:send_fun/0`.
  * `:next_id` (required) - zero-arity function returning a fresh id.
  * `:handle_input` (required) - `t:input_fun/0`.
  * `:capabilities` - the `clientCapabilities` this request declared
    (default `%{}`, which refuses every input request).
  * `:result_types` - extra `resultType` values to accept as final
    (default `[]`).
  * `:max_rounds`, `:max_input_requests`, `:max_request_state_bytes` -
    see the moduledoc.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
