# `ExMCP.Reliability.Retry`
[🔗](https://github.com/azmaveth/ex_mcp/blob/v1.0.0/lib/ex_mcp/reliability/retry.ex#L1)

Retry logic with exponential backoff for MCP operations.

Provides configurable retry strategies to handle transient failures
in distributed systems. Supports exponential backoff with jitter
to prevent thundering herd problems.

## Usage

    # Simple retry with defaults
    Retry.with_retry(fn ->
      ExMCP.Client.call_tool(client, "tool", %{})
    end)

    # Custom configuration
    Retry.with_retry(
      fn -> risky_operation() end,
      max_attempts: 5,
      initial_delay: 100,
      max_delay: 10_000,
      backoff_factor: 2,
      jitter: true
    )

    # With custom retry condition
    Retry.with_retry(
      fn -> http_request() end,
      should_retry?: fn
        {:error, %{status: status}} when status in 500..599 -> true
        {:error, :timeout} -> true
        _ -> false
      end
    )

## Strategies

- **Exponential backoff**: Delay increases exponentially with each attempt
- **Jitter**: Random variation to prevent synchronized retries
- **Circuit breaker integration**: Can be combined with circuit breakers

# `retry_opts`

```elixir
@type retry_opts() :: [
  max_attempts: pos_integer(),
  initial_delay: pos_integer(),
  max_delay: pos_integer(),
  backoff_factor: number(),
  jitter: boolean(),
  should_retry?: (any() -&gt; boolean()),
  on_retry: (pos_integer(), any() -&gt; any())
]
```

# `calculate_delay`

```elixir
@spec calculate_delay(
  pos_integer(),
  keyword()
) :: pos_integer()
```

Calculates the delay for a given attempt using exponential backoff.

Useful for custom retry implementations.

# `mcp_defaults`

```elixir
@spec mcp_defaults(keyword()) :: keyword()
```

Retry configuration specifically for MCP operations.

Returns retry options optimized for MCP protocol operations.

# `merge_policies`

```elixir
@spec merge_policies(keyword(), keyword()) :: keyword()
```

Merges client default retry policy with operation-specific overrides.

Operation-specific values take precedence over client defaults.

## Examples

    iex> ExMCP.Reliability.Retry.merge_policies(
    ...>   [max_attempts: 5, initial_delay: 100],
    ...>   [max_attempts: 3]
    ...> )
    [max_attempts: 3, initial_delay: 100]

# `with_fallback`

```elixir
@spec with_fallback([function()], retry_opts()) ::
  {:ok, any()} | {:error, :all_failed}
```

Executes multiple operations with retry, stopping on first success.

Useful for fallback scenarios where you have multiple ways to achieve
the same result.

## Example

    Retry.with_fallback([
      fn -> primary_service_call() end,
      fn -> secondary_service_call() end,
      fn -> fallback_local_data() end
    ])

# `with_linear_retry`

```elixir
@spec with_linear_retry(
  function(),
  keyword()
) :: {:ok, any()} | {:error, any()}
```

Executes a function with linear retry (fixed delay).

## Options

- `:max_attempts` - Maximum number of attempts (default: 3)
- `:delay` - Fixed delay between attempts in ms (default: 1000)
- `:should_retry?` - Function to determine if retry should occur
- `:on_retry` - Callback function called before each retry

# `with_retry`

```elixir
@spec with_retry(function(), retry_opts()) :: {:ok, any()} | {:error, any()}
```

Executes a function with retry logic.

## Options

- `:max_attempts` - Maximum number of attempts (default: 3)
- `:initial_delay` - Initial delay in ms (default: 100)
- `:max_delay` - Maximum delay in ms (default: 5000)
- `:backoff_factor` - Multiplier for exponential backoff (default: 2)
- `:jitter` - Add randomization to delays (default: true)
- `:should_retry?` - Function to determine if retry should occur (default: retry on any error)
- `:on_retry` - Callback function called before each retry with (attempt, error)

## Returns

- `{:ok, result}` if operation succeeds
- `{:error, reason}` if all retries are exhausted, where `reason` is the
  last underlying error unchanged — callers see the same error shape with
  or without a retry policy

## Exceptions

Only transport-level exceptions (`ExMCP.Error.TransportError`) are treated
as retryable errors. All other exceptions are re-raised so bugs are not
silently converted into retry loops. Exits (e.g. from `GenServer.call/3`)
and throws are caught and treated as `{:error, {:exit, reason}}` /
`{:error, {:throw, value}}`.

# `wrap`

```elixir
@spec wrap(function(), retry_opts()) :: function()
```

Creates a retry-wrapped version of a function.

Useful for wrapping functions that should always be retried.

## Example

    retryable_call = Retry.wrap(fn -> unstable_api_call() end, max_attempts: 5)

    # Later...
    result = retryable_call.()

---

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