# `Codex.MCP.Client`
[🔗](https://github.com/nshkrdotcom/codex_sdk/blob/v0.18.1/lib/codex/mcp/client.ex#L1)

MCP JSON-RPC client for stdio and streamable HTTP transports.

## Tool Name Qualification

When `list_tools/2` is called with `qualify?: true`, tool names are qualified with
the server name prefix in the format `mcp__<server>__<tool>`. This follows the
OpenAI tool name constraint by exposing only ASCII alphanumerics, `_`, and `-`
in the qualified key.

If the qualified name exceeds 64 characters, it is truncated and a SHA1 hash suffix
is appended to ensure uniqueness.

## Tool Invocation

The `call_tool/4` function invokes tools on the MCP server with support for:

  * **Retry Logic** - Configurable retries with exponential backoff
  * **Approval Integration** - Optional approval callbacks before invocation
  * **Timeout Control** - Per-call timeout settings
  * **Telemetry** - Events emitted for observability

## Telemetry Events

The following telemetry events are emitted during tool invocation:

  * `[:codex, :mcp, :tool_call, :start]` - When a tool call begins
    * Measurements: `%{system_time: integer()}`
    * Metadata: `%{tool: String.t(), arguments: map(), server_name: String.t() | nil}`

  * `[:codex, :mcp, :tool_call, :success]` - When a tool call succeeds
    * Measurements: `%{duration: integer()}`
    * Metadata: `%{tool: String.t(), arguments: map(), server_name: String.t() | nil, attempt: integer()}`

  * `[:codex, :mcp, :tool_call, :failure]` - When a tool call fails
    * Measurements: `%{duration: integer()}`
    * Metadata: `%{tool: String.t(), arguments: map(), server_name: String.t() | nil, reason: term(), attempt: integer()}`

# `capabilities`

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

# `t`

```elixir
@type t() :: %Codex.MCP.Client{
  capabilities: capabilities(),
  instructions: String.t() | nil,
  protocol_version: String.t() | nil,
  server_info: map() | nil,
  server_name: String.t() | nil,
  tool_cache: map(),
  transport: transport_ref()
}
```

# `transport_ref`

```elixir
@type transport_ref() :: {module(), term()}
```

# `call_tool`

```elixir
@spec call_tool(t(), String.t(), map() | nil, keyword()) ::
  {:ok, map()} | {:error, term()}
```

Invokes a tool on the MCP server.

## Options

  * `:retries` - Number of retry attempts (default: `3`)
  * `:backoff` - Backoff function `(attempt -> :ok)` (default: exponential backoff)
  * `:timeout_ms` - Request timeout in milliseconds (default: `60000`)
  * `:approval` - Approval callback function `(tool, args, context) -> :ok | {:deny, reason}`
  * `:context` - Tool context map passed to approval callback (default: `%{}`)

## Backoff

The default backoff uses exponential delays: 100ms, 200ms, 400ms, 800ms, ... up to 5000ms max.
Provide a custom function to override: `backoff: fn attempt -> Process.sleep(attempt * 100) end`

## Approval Callbacks

Approval callbacks are invoked before the first attempt. They can be:

  * A 3-arity function `(tool, args, context) -> result`
  * A 2-arity function `(tool, args) -> result`
  * A 1-arity function `(tool) -> result`

Where result is one of:

  * `:ok` or any truthy value - Approved
  * `:deny` or `false` - Denied
  * `{:deny, reason}` - Denied with reason

## Telemetry

Emits the following events:

  * `[:codex, :mcp, :tool_call, :start]` - When the call begins
  * `[:codex, :mcp, :tool_call, :success]` - On successful completion
  * `[:codex, :mcp, :tool_call, :failure]` - On failure (after all retries exhausted)

## Returns

  * `{:ok, result}` - Tool execution succeeded
  * `{:error, {:approval_denied, reason}}` - Approval callback denied the call
  * `{:error, reason}` - Tool execution failed after all retries

## Examples

    # Basic invocation with defaults
    {:ok, result} = Codex.MCP.Client.call_tool(client, "echo", %{"text" => "hello"})

    # With custom retry and backoff
    {:ok, result} = Codex.MCP.Client.call_tool(client, "fetch", %{"url" => url},
      retries: 5,
      backoff: fn attempt -> Process.sleep(attempt * 200) end,
      timeout_ms: 30_000
    )

    # With approval callback
    {:ok, result} = Codex.MCP.Client.call_tool(client, "write_file", args,
      approval: fn tool, args, _ctx ->
        if safe_tool?(tool, args), do: :ok, else: {:deny, "unsafe"}
      end
    )

# `capabilities`

```elixir
@spec capabilities(t()) :: capabilities()
```

Returns capabilities advertised by the MCP server.

# `handshake`

```elixir
@spec handshake(
  transport_ref(),
  keyword()
) :: {:ok, t()} | {:error, term()}
```

Backwards compatible alias for `initialize/2`.

# `initialize`

```elixir
@spec initialize(
  transport_ref(),
  keyword()
) :: {:ok, t()} | {:error, term()}
```

Performs MCP initialization against the given transport.

This sends `initialize` and then emits the `notifications/initialized` notification.

## Options

  * `:client` - Client name to send during initialization (default: `"codex-elixir"`)
  * `:client_title` - Optional client title for UI display
  * `:version` - Client version (default: `"0.0.0"`)
  * `:capabilities` - Client capability map (default: `%{}`)
  * `:protocol_version` - MCP protocol version (default: `2025-06-18`)
  * `:server_name` - Server name for tool name qualification (e.g., `"shell"`)
  * `:timeout_ms` - Timeout for initialization (default: `10000`)

# `list_prompts`

```elixir
@spec list_prompts(
  t(),
  keyword()
) :: {:ok, map()} | {:error, term()}
```

Lists available prompts via `prompts/list`.

## Options

  * `:cursor` - Pagination cursor (default: `nil`)
  * `:timeout_ms` - Request timeout (default: `30000`)

# `list_resources`

```elixir
@spec list_resources(
  t(),
  keyword()
) :: {:ok, map()} | {:error, term()}
```

Lists available resources via `resources/list`.

## Options

  * `:cursor` - Pagination cursor (default: `nil`)
  * `:timeout_ms` - Request timeout (default: `30000`)

# `list_tools`

```elixir
@spec list_tools(
  t(),
  keyword()
) :: {:ok, [map()], t()} | {:error, term()}
```

Lists available tools, applying allow/block filters and caching results unless `cache?: false`
is supplied.

## Options

  * `:cache?` - Whether to use cached results (default: `true`)
  * `:allow` - List of tool names to allow (allowlist filter)
  * `:deny` - List of tool names to deny (blocklist filter)
  * `:filter` - Custom filter function `(tool -> boolean)`
  * `:qualify?` - Whether to add qualified names with server prefix (default: `false`)
  * `:cursor` - Pagination cursor (default: `nil`)
  * `:timeout_ms` - Request timeout (default: `30000`)

## Returns

  * `{:ok, tools, updated_client}` on success
  * `{:error, reason}` on failure

# `qualify_tool_name`

```elixir
@spec qualify_tool_name(String.t(), String.t()) :: String.t()
```

Qualifies a tool name with the server prefix.

Returns the fully qualified name in the format `mcp__<server>__<tool>`.
Server and tool components are sanitized for the OpenAI-facing key by replacing
any non-ASCII-alphanumeric character other than `_` or `-` with `_`. If the qualified
name exceeds 64 characters, it is truncated and a SHA1 hash suffix derived from
the raw unsanitized qualified name is appended to ensure uniqueness.

## Examples

    iex> Codex.MCP.Client.qualify_tool_name("server1", "tool_a")
    "mcp__server1__tool_a"

    iex> long_tool = String.duplicate("a", 80)
    iex> result = Codex.MCP.Client.qualify_tool_name("srv", long_tool)
    iex> String.length(result)
    64

---

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