# `Snodo.Sampling`
[🔗](https://github.com/joshrotenberg/snodo/blob/v0.4.1/lib/snodo/sampling.ex#L1)

Builds and validates embedded `sampling/createMessage` requests for MRTR.

SEP-2577 deprecates server-initiated sampling in MCP 2026-07-28. The method
is still defined by the protocol schema and scored by the official
conformance runner, so a 2026-07-28 handler may return one as an input
request through `Snodo.Result.input_required/1`, next to elicitation. Prefer
elicitation for new designs; sampling stays available for clients that
declare the `sampling` capability.

`create_message/2` returns a bare input request, without a JSON-RPC
envelope. Messages are `Snodo.Prompt.message/2` maps whose content is one
text, image, audio, tool-use, or tool-result block, or a list of those
blocks. Use `response/3` on a retried request to read only the named
`CreateMessageResult`. It does not bind a response to a user or persist
state: applications must do that using authenticated context and verified,
request-specific state.

The dialect refuses the request with `-32021` when the client has not
declared `sampling`, `sampling.tools` (needed by `:tools` and
`:tool_choice`), or `sampling.context` (needed by an `:include_context`
other than `"none"`). Sampled content is model output: treat it as
untrusted input, never as an instruction or a verified fact.

# `request`

```elixir
@type request() :: %{required(String.t()) =&gt; term()}
```

# `response_result`

```elixir
@type response_result() :: :missing | {:ok, map()} | {:error, Snodo.Error.t()}
```

# `create_message`

```elixir
@spec create_message([map()], keyword()) :: request()
```

Builds a sampling input request, raising `ArgumentError` when invalid.

`messages` is a non-empty list of sampling messages. Options:

  * `:max_tokens` - required, a positive integer.
  * `:system_prompt` - a string.
  * `:model_preferences` - a map with optional `"hints"` (a list of
    `%{"name" => string}` maps) and `"costPriority"`, `"speedPriority"`,
    and `"intelligencePriority"` numbers from 0 to 1.
  * `:temperature` - a number.
  * `:stop_sequences` - a list of strings.
  * `:include_context` - `"none"`, `"thisServer"`, or `"allServers"`. The
    last two are deprecated by SEP-2596 and need `sampling.context`.
  * `:metadata` - a JSON object passed through to the client's provider.
  * `:tools` - a list of tool definitions in wire shape; needs
    `sampling.tools`.
  * `:tool_choice` - `%{"mode" => "auto" | "none" | "required"}`; needs
    `sampling.tools`.

# `response`

```elixir
@spec response(Snodo.Context.t() | map(), String.t(), request()) :: response_result()
```

Reads and validates the named response, ignoring unrelated response IDs.

A valid response is a `CreateMessageResult`: a `"role"`, one sampling
content block or a list of them as `"content"`, a `"model"` string, and an
optional `"stopReason"`. Invalid responses return a generic invalid-params
error without the submitted data. Additional JSON fields are preserved and
ignored. This helper does not authenticate content or trust client-echoed
request state.

# `supported?`

```elixir
@spec supported?(term(), term()) :: boolean()
```

Checks the request against this request's client capabilities.

Requires a `sampling` object, plus `sampling.tools` when the request carries
`tools` or `toolChoice` and `sampling.context` when `includeContext` is not
`"none"`.

# `valid_response?`

```elixir
@spec valid_response?(term()) :: boolean()
```

Checks that `response` is a `CreateMessageResult`.

That is a `"role"` of `"user"` or `"assistant"`, one sampling content block
or a non-empty list of them as `"content"`, a `"model"` string, and an
optional `"stopReason"` string. Additional JSON fields are allowed.
`Snodo.Client` applies it to what a sampling handler returns.

# `validate_request`

```elixir
@spec validate_request(term()) :: :ok | {:error, String.t()}
```

Validates one bare sampling input request.

---

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