Anubis.Server.McpParam (anubis_mcp v2.2.0)

Copy Markdown

Tool parameters mirrored into Mcp-Param-{Name} HTTP headers, which the 2026-07-28 Streamable HTTP transport defines.

A tool opts a parameter in with an x-mcp-header property in its inputSchema; with the component DSL, field :region, :string, mcp_header: "Region". A client then sends the argument's value in the Mcp-Param-Region header, and the server rejects the call with -32020 (HeaderMismatch, HTTP 400) when a header disagrees with the body, is missing for a value the body carries, or holds characters a header value cannot.

An annotation must name a header token, unique regardless of case, on a string, integer or boolean parameter reachable from the schema root through properties alone. A schema that breaks any of these raises ArgumentError when its JSON Schema is built.

Headers mirror wire arguments before defaults or transformations. A routing selector that must be visible to intermediaries should be required and keep the same meaning during validation; authorization remains the application's responsibility. See the transport guide.

Summary

Functions

Puts the x-mcp-header annotations of a component schema into the JSON Schema built from it.

Decodes a header value that may carry the =?base64?…?= sentinel.

Encodes a value for a Mcp-Name-style header, the inverse of decode_header_value/1.

Checks the Mcp-Param-* headers of a tools/call against its arguments.

Functions

annotate(json_schema, schema)

@spec annotate(map(), map()) :: map()

Puts the x-mcp-header annotations of a component schema into the JSON Schema built from it.

schema is the expanded component schema, whose fields carry mcp_header: in their metadata; json_schema is what it was encoded to. Raises ArgumentError for an annotation the specification does not allow.

decode_header_value(value)

@spec decode_header_value(String.t()) :: {:ok, String.t()} | :error

Decodes a header value that may carry the =?base64?…?= sentinel.

Spaces and tabs around the value are not part of it (RFC 9110, section 5.5) and are dropped first. A value without the sentinel, or with only its prefix, is returned as written. A sentinel around invalid Base64 is :error.

Examples

iex> Anubis.Server.McpParam.decode_header_value("us-west1")
{:ok, "us-west1"}

iex> Anubis.Server.McpParam.decode_header_value("=?base64?SGVsbG8=?=")
{:ok, "Hello"}

iex> Anubis.Server.McpParam.decode_header_value("=?base64?SGVsbG8?=")
:error

iex> Anubis.Server.McpParam.decode_header_value("  test_tool    ")
{:ok, "test_tool"}

encode_header_value(value)

@spec encode_header_value(String.t()) :: String.t()

Encodes a value for a Mcp-Name-style header, the inverse of decode_header_value/1.

A value that is already a safe header value (visible ASCII only, no leading or trailing space or tab, not starting with the base64 sentinel) is returned as written; anything else is wrapped in the =?base64?...?= sentinel so the server decodes it back to the exact body value.

Examples

iex> Anubis.Server.McpParam.encode_header_value("us-west1")
"us-west1"

iex> Anubis.Server.McpParam.encode_header_value("Héllo")
"=?base64?" <> Base.encode64("Héllo") <> "?="

iex> Anubis.Server.McpParam.decode_header_value(Anubis.Server.McpParam.encode_header_value(" padded "))
{:ok, " padded "}

validate(input_schema, arguments, headers)

@spec validate(map() | nil, map() | nil, %{required(String.t()) => String.t()}) ::
  :ok | {:error, Anubis.MCP.Error.t()}

Checks the Mcp-Param-* headers of a tools/call against its arguments.

input_schema is the tool's JSON Schema, arguments the call's arguments and headers the request's headers, with lowercase names. Arguments are the values received on the wire, before server defaults or transformations. Absent or null values require no header. Returns :ok when they match, otherwise {:error, error} with protocol code -32020.

Mirrored integers must be within the IEEE 754 safe range, ±(2^53 - 1).