# `ExMCP.Server.ResultNormalizer`
[🔗](https://github.com/azmaveth/ex_mcp/blob/v1.0.0/lib/ex_mcp/server/result_normalizer.ex#L1)

Shared result and error normalization for every server dispatch path.

`ExMCP.Server.Dispatch` (handler-process transports and stdio),
the HTTP message processor, and `ExMCP.Protocol.RequestProcessor` (DSL
servers) all convert handler return
values into JSON-RPC results. Keeping that conversion here guarantees the
transports agree on tool-result shape, key stringification and, most
importantly, on what is safe to send back to a client.

## Client-facing error messages

`error_message/2` never runs `inspect/1` over an arbitrary term into a
response. Handler-authored detail (a binary reason, or a map/struct with a
`message` field) is preserved because MCP servers are expected to explain
themselves; anything else is logged with `Logger.error/1` and replaced by a
generic message so internal structs, pids, or file paths cannot leak.

# `error_code`

```elixir
@spec error_code(term(), integer()) :: integer()
```

Returns the JSON-RPC error code a handler error reason should map to.

Cursor complaints map to invalid params (`-32602`); everything else uses
`default`. Codes embedded in the reason are deliberately *not* honoured:
handlers have historically returned `%{"code" => ...}` maps whose codes do
not match the transport-level meaning of the failure.

# `error_message`

```elixir
@spec error_message(String.t(), term()) :: String.t()
```

Builds a client-safe error message from a handler error reason.

Detail that the handler clearly authored (a binary, an atom, or a
`:message` / `"message"` field) is kept. Everything else is logged and
omitted from the response.

# `paginated`

```elixir
@spec paginated(String.t(), list(), String.t() | nil) :: map()
```

Builds a paginated list result such as `%{"tools" => [...], "nextCursor" => ...}`.

# `prepare_tools_list`

```elixir
@spec prepare_tools_list([map()]) :: [map()]
```

Prepares the complete source collection for a modern `tools/list` page.

Custom handlers that paginate tools must call this function before slicing
a page or calculating an opaque cursor. It stringifies protocol keys,
excludes invalid `x-mcp-header` definitions, removes ExMCP-only execution
metadata, and applies the deterministic modern ordering.

The normal result path applies the same operation defensively, but at that
point it cannot repair a cursor that a custom handler calculated from an
unfiltered collection.

# `protocol_result`

```elixir
@spec protocol_result(map(), map(), keyword()) :: map()
```

Applies the result envelope required by the request's protocol era.

Legacy results are returned unchanged. Modern results receive a
`resultType` discriminator and result metadata identifying the server.
Handler-supplied `input_required` (or extension) result types are preserved.

# `stringify_keys`

```elixir
@spec stringify_keys(term()) :: term()
```

Recursively converts atom keys to strings.

Known MCP protocol fields such as `:input_schema`, `:mime_type` and
`:is_error` are mapped to their lower-camel-case wire names so raw Handler
implementations may use idiomatic Elixir keys.

# `tool_error_result`

```elixir
@spec tool_error_result(term()) :: map()
```

Builds an MCP tool result that reports a failure through `isError`.

# `tool_result`

```elixir
@spec tool_result(
  term(),
  keyword()
) :: map()
```

Normalizes a `handle_call_tool/3` result into an MCP `tools/call` result.

Accepts a bare content list, a binary (wrapped as a text content item), or a
map that already carries `content`.

## Options

  * `:wrap_bare_map` - when `true`, a map that carries no `content` key is
    wrapped as `%{"content" => map}` instead of being used as the result
    verbatim. The handler-process transports (`ExMCP.Server.Dispatch`) have
    always done this; the HTTP path has not, and both behaviours are relied
    on by existing servers.

# `validate_result_capabilities`

```elixir
@spec validate_result_capabilities(map(), map()) ::
  :ok | {:error, ExMCP.Error.ProtocolError.t()}
```

Validates that an extension result was enabled by per-request capabilities.

---

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