Anubis.Server.InputRequired (anubis_mcp v2.2.0)

Copy Markdown

A result asking the client for input before a request can complete: the InputRequiredResult of the multi round-trip requests pattern (MCP 2026-07-28).

A tools/call, prompts/get or resources/read handler returns one in place of an Anubis.Server.Response. The client answers each input request under its key and retries the original request; the handler then reads the answers with Anubis.Server.Frame.input_response/2 and, if it attached one, its state with Anubis.Server.Frame.request_state/1.

alias Anubis.Server.{Frame, InputRequired, Response}

@impl true
def execute(_params, frame) do
  case Frame.input_response(frame, "confirm") do
    %{"action" => "accept"} ->
      {:reply, Response.text(Response.tool(), "Sent."), frame}

    _not_yet ->
      input =
        InputRequired.new()
        |> InputRequired.elicit("confirm", "Send the message?", %{
          "type" => "object",
          "properties" => %{"ok" => %{"type" => "boolean"}}
        })

      {:reply, input, frame}
  end
end

A client may answer only some requests, or none, and may never retry; a handler asks again for what is missing rather than failing.

An input request needs the matching client capability (elicitation, sampling or roots), including its elicitation mode or sampling tool support. An unsupported request is refused with -32021 instead of being sent. Anubis.Server.Frame.client_supports?/2 checks top-level presence; handlers offering alternatives also check the required subfeatures in frame.context.client_capabilities. The result exists only in the stateless era; a handshake-era request that gets one fails with an internal error.

State is signed by Anubis.Server.RequestState, which needs config :anubis_mcp, :request_state_secret.

Summary

Functions

Asks the user, through a form, for content matching requested_schema.

Asks the client for its roots. The answer is a ListRootsResult.

Starts an empty result. It needs at least one input request or a state.

The client capabilities the input requests need, as a requiredCapabilities map, including elicitation modes and sampling tool support (for example, %{"elicitation" => %{"form" => %{}}}). Requirements from all requests are combined.

Asks the client's model for a completion. params are the sampling/createMessage parameters ("messages", "maxTokens", ...). Including "tools" or "toolChoice" requires sampling.tools support.

Attaches a state the client echoes on its retry, where Anubis.Server.Frame.request_state/1 returns it. Any term; it travels signed.

Types

t()

@type t() :: %Anubis.Server.InputRequired{
  input_requests: %{required(String.t()) => %{required(String.t()) => term()}},
  request_state: {:set, term()} | nil
}

Functions

elicit(input, key, message, requested_schema)

@spec elicit(t(), String.t(), String.t(), map()) :: t()

Asks the user, through a form, for content matching requested_schema.

The answer is an ElicitResult: %{"action" => "accept", "content" => ...}, or "decline" / "cancel" without content.

list_roots(input, key)

@spec list_roots(t(), String.t()) :: t()

Asks the client for its roots. The answer is a ListRootsResult.

new()

@spec new() :: t()

Starts an empty result. It needs at least one input request or a state.

required_capabilities(input_required)

@spec required_capabilities(t()) :: %{required(String.t()) => map()}

The client capabilities the input requests need, as a requiredCapabilities map, including elicitation modes and sampling tool support (for example, %{"elicitation" => %{"form" => %{}}}). Requirements from all requests are combined.

sample(input, key, params)

@spec sample(t(), String.t(), map()) :: t()

Asks the client's model for a completion. params are the sampling/createMessage parameters ("messages", "maxTokens", ...). Including "tools" or "toolChoice" requires sampling.tools support.

state(input, term)

@spec state(t(), term()) :: t()

Attaches a state the client echoes on its retry, where Anubis.Server.Frame.request_state/1 returns it. Any term; it travels signed.