# `ExMCP.Protocol.RequestTracker`
[🔗](https://github.com/azmaveth/ex_mcp/blob/v1.0.0/lib/ex_mcp/protocol/request_tracker.ex#L1)

Manages tracking of pending requests and cancellations for MCP servers.

This module provides functionality to:
- Track pending requests with their associated GenServer from references
- Track cancelled request IDs
- Handle request completion and cancellation
- Query pending request status

The tracker maintains two data structures:
- `pending_requests`: Map of request_id => from (GenServer reference)
- `cancelled_requests`: MapSet of cancelled request_ids

# `from`

```elixir
@type from() :: GenServer.from()
```

# `request_id`

```elixir
@type request_id() :: String.t() | integer()
```

# `state`

```elixir
@type state() :: map()
```

# `cancel_request`

```elixir
@spec cancel_request(request_id(), state()) :: state()
```

Marks a request as cancelled.

Adds the request_id to the cancelled_requests set.

## Examples

    iex> state = RequestTracker.init()
    iex> new_state = RequestTracker.cancel_request("req-123", state)
    iex> RequestTracker.cancelled?("req-123", new_state)
    true

# `cancelled?`

```elixir
@spec cancelled?(request_id(), state()) :: boolean()
```

Checks if a request has been cancelled.

## Examples

    iex> state = %{cancelled_requests: MapSet.new(["req-123"])}
    iex> RequestTracker.cancelled?("req-123", state)
    true
    
    iex> RequestTracker.cancelled?("req-456", state)
    false

# `complete_request`

```elixir
@spec complete_request(request_id(), state()) :: state()
```

Completes a pending request by removing it from tracking.

## Examples

    iex> state = %{pending_requests: %{"req-123" => {self(), :tag}}, cancelled_requests: MapSet.new()}
    iex> new_state = RequestTracker.complete_request("req-123", state)
    iex> Map.has_key?(new_state.pending_requests, "req-123")
    false

# `get_pending_request`

```elixir
@spec get_pending_request(request_id(), state()) :: {:ok, from()} | :error
```

Gets the from reference for a pending request.

Returns {:ok, from} if found, :error if not found.

## Examples

    iex> from = {self(), :tag}
    iex> state = %{pending_requests: %{"req-123" => from}}
    iex> RequestTracker.get_pending_request("req-123", state)
    {:ok, from}
    
    iex> RequestTracker.get_pending_request("req-456", state)
    :error

# `get_pending_request_ids`

```elixir
@spec get_pending_request_ids(state()) :: [request_id()]
```

Gets all pending request IDs.

## Examples

    iex> state = %{pending_requests: %{"req-123" => {self(), :tag}, "req-456" => {self(), :tag2}}}
    iex> RequestTracker.get_pending_request_ids(state) |> Enum.sort()
    ["req-123", "req-456"]

# `handle_cancellation`

```elixir
@spec handle_cancellation(request_id(), state()) ::
  {:reply, from(), state()} | {:noreply, state()}
```

Handles a cancellation notification.

If the request is still pending:
- Marks it as cancelled
- Removes it from pending
- Returns {:reply, from} to send cancellation reply

If the request is not pending:
- Just marks it as cancelled
- Returns {:noreply, state}

## Examples

    iex> from = {self(), :tag}
    iex> state = RequestTracker.init() |> RequestTracker.track_request("req-123", from)
    iex> {action, data, new_state} = RequestTracker.handle_cancellation("req-123", state)
    iex> action
    :reply
    iex> data
    from
    iex> RequestTracker.cancelled?("req-123", new_state)
    true

# `init`

```elixir
@spec init() :: map()
```

Initializes request tracker state.

Returns a map with empty pending_requests and cancelled_requests.

## Examples

    iex> RequestTracker.init()
    %{pending_requests: %{}, cancelled_requests: MapSet.new()}

# `init_state`

```elixir
@spec init_state(map()) :: map()
```

Merges request tracker state into existing state map.

## Examples

    iex> RequestTracker.init_state(%{foo: "bar"})
    %{foo: "bar", pending_requests: %{}, cancelled_requests: MapSet.new()}

# `process_if_not_cancelled`

```elixir
@spec process_if_not_cancelled(request_id(), from(), state()) ::
  {:ok, state()} | {:cancelled, state()}
```

Processes a request with cancellation checking.

This is a convenience function that:
1. Checks if the request is already cancelled
2. If not, tracks it as pending
3. Returns appropriate response

## Examples

    iex> state = RequestTracker.init() |> RequestTracker.cancel_request("req-123")
    iex> RequestTracker.process_if_not_cancelled("req-123", {self(), :tag}, state)
    {:cancelled, state}
    
    iex> state = RequestTracker.init()
    iex> {:ok, new_state} = RequestTracker.process_if_not_cancelled("req-456", {self(), :tag}, state)
    iex> Map.has_key?(new_state.pending_requests, "req-456")
    true

# `track_request`

```elixir
@spec track_request(request_id(), from(), state()) :: state()
```

Tracks a new pending request.

Adds the request_id and from reference to pending_requests.

## Examples

    iex> state = RequestTracker.init()
    iex> new_state = RequestTracker.track_request("req-123", {self(), :tag}, state)
    iex> Map.has_key?(new_state.pending_requests, "req-123")
    true

---

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