# `ConduitMcp.Protocol.Modern`
[🔗](https://github.com/nyo16/conduit_mcp/blob/v0.11.0/lib/conduit_mcp/protocol/modern.ex#L1)

Shapes a neutral handler result (`t:ConduitMcp.Handler.result/0`) into the
JSON-RPC 2.0 response map of the modern (`2026-07-28`) protocol era, and
maps modern errors to their HTTP status.

## Results

Every successful result gets:

  * `"resultType" => "complete"`;
  * `_meta["io.modelcontextprotocol/serverInfo"]` — the server's name and
    version. Request-scoped `io.modelcontextprotocol/*` keys a method
    echoed from the request's `_meta` (protocol version, client
    capabilities, client info, log level) are removed from the result's
    `_meta`; the result's other keys are kept. `tools/call` never echoes
    the request's `_meta` on this era, so these are the tool's own;
  * on the six cacheable results — `server/discover`, `tools/list`,
    `prompts/list`, `resources/list`, `resources/templates/list` and
    `resources/read` — `"ttlMs"` and `"cacheScope"`.

Cache hints default to the application environment:

    config :conduit_mcp, cache_ttl_ms: 60_000, cache_scope: :private

(`:private` or `:public`; defaults `60_000` and `:private`). A caller can
override them per response with the `:cache` option of `to_response/4`.
`{:ok, result}` is always a complete result.

## Input required

`{:input_required, input_requests, request_state}` — produced by the
handler once it has gated the input requests and sealed the callback's
state into the `request_state` token — becomes an `InputRequiredResult`:
`"resultType" => "input_required"`, `"inputRequests"`, `"requestState"`
and the `serverInfo` `_meta`, but never `ttlMs`/`cacheScope`, on any
method (an interim result must not be cached).

## Tasks

`{:task, task}` — a task created for a `tools/call` by the
`io.modelcontextprotocol/tasks` extension (`ConduitMcp.Ext.Tasks`) —
becomes a `CreateTaskResult`: the `Task` fields with
`"resultType" => "task"` and the `serverInfo` `_meta`, never
`ttlMs`/`cacheScope`.

## Errors

Error responses keep the legacy shape. One code is rewritten: `-32002`
(resource not found in `2025-11-25` and earlier, and the code
`handle_read_resource/2` callbacks return) becomes `-32602`, because
`2026-07-28` forbids emitting `-32002`.

## HTTP status

`http_status/1` maps a response (or bare code) produced while running a
method: `-32020`/`-32021`/`-32022` ⇒ 400, `-32601` ⇒ 404, everything else
(results, and errors such as an unknown tool's `-32602`) ⇒ 200. A
rejection raised *before* the method runs — any `ConduitMcp.Era.classify/2`
error, including its `-32602` for invalid `_meta`, and any
`ConduitMcp.Plugs.HeaderValidation` error — is always 400.

# `cache`

```elixir
@type cache() :: %{ttl_ms: non_neg_integer(), scope: :private | :public}
```

Cache hints: a TTL in milliseconds and a scope.

# `option`

```elixir
@type option() :: {:server_info, map()} | {:cache, cache() | nil}
```

Options for `to_response/4`:

  * `:server_info` — `%{"name" => ..., "version" => ...}`, put into the
    result's `_meta`; omitted when absent.
  * `:cache` — a `t:cache/0` overriding the configured default for this
    response (cacheable methods only); `nil` means the default.

# `cacheable_methods`

```elixir
@spec cacheable_methods() :: [String.t()]
```

The methods whose complete results carry `ttlMs` and `cacheScope`.

# `default_cache`

```elixir
@spec default_cache() :: cache()
```

The configured default cache hints (`:cache_ttl_ms`, `:cache_scope`).

# `http_status`

```elixir
@spec http_status(map() | integer()) :: 200 | 400 | 404
```

The HTTP status for a modern response, or for an error code, produced while
running a method (see the moduledoc for rejections raised before it).

# `resource_not_found`

```elixir
@spec resource_not_found() :: integer()
```

The modern era's "resource not found" code, `-32602`.

# `to_response`

```elixir
@spec to_response(
  ConduitMcp.Protocol.json_rpc_id() | nil,
  String.t() | nil,
  ConduitMcp.Handler.result(),
  [option()]
) :: map()
```

Builds the modern JSON-RPC response to `method` request `id`.

## Examples

    iex> ConduitMcp.Protocol.Modern.to_response(1, "tools/call", {:ok, %{"content" => []}})
    %{"jsonrpc" => "2.0", "id" => 1, "result" => %{"content" => [], "resultType" => "complete"}}

    iex> ConduitMcp.Protocol.Modern.to_response(1, "resources/read", {:error, -32002, "Resource not found", nil})
    %{"jsonrpc" => "2.0", "id" => 1, "error" => %{"code" => -32602, "message" => "Resource not found"}}

---

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