# `ConduitMcp.Headers`
[🔗](https://github.com/nyo16/conduit_mcp/blob/v0.11.0/lib/conduit_mcp/headers.ex#L1)

HTTP request-metadata header codec for the MCP 2026-07-28 Streamable HTTP
transport.

The transport mirrors selected JSON-RPC body fields into HTTP headers so
intermediaries can route requests without parsing the body:

  * `Mcp-Method` — the request `method`, on every request.
  * `Mcp-Name` — `params.name` for `tools/call` and `prompts/get`,
    `params.uri` for `resources/read`, and `params.taskId` for the tasks
    extension's `tasks/get`, `tasks/update` and `tasks/cancel`
    ([ext-tasks routing headers](https://github.com/modelcontextprotocol/ext-tasks/blob/main/specification/2026-07-28/tasks.md#streamable-http-routing-headers)).
  * `Mcp-Param-{Name}` — tool arguments whose `inputSchema` property carries
    an `"x-mcp-header": "{Name}"` annotation.

This module is pure: it builds those headers for a client, validates
`x-mcp-header` annotations, and lets a server decode header values and
compare them to body values. Header names it returns are lowercase, matching
how Plug normalizes request header names.

## Value encoding

Strings are sent as-is, integers as decimal, booleans as `true`/`false`.
A string that is not plain header-safe ASCII — it contains a byte outside
`0x20..0x7E` (non-ASCII, control characters including tab and DEL), has a
leading or trailing space, or starts with `=?` and ends with `?=` (so it
could be mistaken for the sentinel) — is sent as
`=?base64?{standard padded Base64 of its UTF-8 bytes}?=`.

Integer comparison is numeric and strict: a header matches an integer body
value only when it is a canonical decimal integer (optional `-`, no leading
zeros other than `"0"` itself, no `"-0"`) within the JavaScript safe range
`-(2^53 - 1)..(2^53 - 1)`. Body values `42` and `42.0` both match `"42"`.

# `header_param`

```elixir
@type header_param() :: %{header: String.t(), path: [String.t()], type: String.t()}
```

An `x-mcp-header` annotation reachable from the schema root.

# `decode_value`

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

Decodes a received header value.

A value that starts with `=?base64?` and ends with `?=` must carry valid,
padded standard Base64 of valid UTF-8 and decodes to that string; anything
else in that shape returns `:error`. Every other value is returned unchanged.

# `encode_value`

```elixir
@spec encode_value(String.t() | integer() | boolean()) :: String.t()
```

Encodes a string, integer, or boolean as a header value.

Strings that are not plain header-safe ASCII are wrapped in the
`=?base64?...?=` sentinel; see the module documentation.

# `header_params`

```elixir
@spec header_params(map()) :: [header_param()]
```

Lists the `x-mcp-header` annotations reachable from the root of an
`inputSchema` through chains of `properties` keys only.

Each entry has the declared `header` Name as written, the property-key
`path` from the root, and the property's declared `type`. Annotations whose
Name or `type` is not a string are left out; `validate_x_mcp_header/1`
reports those. Entries are ordered by path.

# `method_header`

```elixir
@spec method_header(String.t()) :: {String.t(), String.t()}
```

Returns the `Mcp-Method` header for a request method.

# `name_header`

```elixir
@spec name_header(String.t(), map()) :: {String.t(), String.t()} | nil
```

Returns the `Mcp-Name` header for a request, or `nil` when none applies.

Only `tools/call` and `prompts/get` (from `params["name"]`),
`resources/read` (from `params["uri"]`) and the tasks extension's
`tasks/get|update|cancel` (from `params["taskId"]`) carry the header, and
only when that param is a string. The value is encoded with
`encode_value/1`.

# `name_header_required?`

```elixir
@spec name_header_required?(String.t()) :: boolean()
```

Returns whether requests with `method` must carry an `Mcp-Name` header.

# `name_param`

```elixir
@spec name_param(String.t()) :: String.t() | nil
```

The `params` key `Mcp-Name` mirrors for `method` (`"name"`, `"uri"` or
`"taskId"`), or `nil` when the method carries no `Mcp-Name`.

# `param_headers`

```elixir
@spec param_headers(map(), map()) :: [{String.t(), String.t()}]
```

Builds the `Mcp-Param-{Name}` headers for a tool call.

`tool_or_schema` is a tool definition (with `"inputSchema"`) or an
`inputSchema` itself; `args` are the string-keyed call arguments. A header
is produced for each annotation from `header_params/1` whose argument value
at `path` is a string, integer, boolean, or integral float (`42.0` encodes
as `"42"`). Absent and `nil` values, and values of any other type, produce
no header. Header names are `"mcp-param-"` followed by the lowercased Name.

# `validate_x_mcp_header`

```elixir
@spec validate_x_mcp_header(map()) :: :ok | {:error, [String.t()]}
```

Validates every `x-mcp-header` annotation in an `inputSchema`.

The whole schema is searched, including `items`, composition keywords and
`$defs`. Each annotation must:

  * sit on a property reachable from the root through `properties` keys only;
  * be on a property whose `"type"` is `"string"`, `"integer"` or `"boolean"`;
  * be a non-empty RFC 9110 token (which excludes CR and LF);
  * be unique, compared case-insensitively, within the schema.

Errors name the annotation and its JSON Pointer location, never argument
values.

# `value_matches?`

```elixir
@spec value_matches?(String.t(), term()) :: boolean()
```

Compares a decoded header value to the corresponding body value.

Strings compare exactly and booleans against `"true"`/`"false"`. Integers
and floats compare numerically against a canonical decimal integer header
within the JavaScript safe range (`"42"` matches `42` and `42.0`; `"042"`,
`"-0"`, `"+1"` and `"42.0"` match nothing). Any other body value, including
`nil`, never matches.

---

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