# `ConduitMcp.Cancellation`
[🔗](https://github.com/nyo16/conduit_mcp/blob/v0.10.1/lib/conduit_mcp/cancellation.ex#L1)

Cooperative request cancellation for long-running tools.

MCP clients can abort an in-flight request by sending
`notifications/cancelled` with the original request's id. Because
ConduitMCP is stateless and each HTTP request runs in its own Bandit
process, the notification (which arrives on a *different* request)
cannot directly preempt the in-flight handler. Instead, the handler
records the cancellation in a shared ETS table and tool code polls
this module to decide whether to abort.

## Tool integration

Inside a long-running tool, periodically check the conn's request id:

    def my_long_tool(conn, params) do
      ConduitMcp.Cancellation.cancelled?(conn)
      |> case do
        true -> {:error, %{"code" => ConduitMcp.Errors.request_cancelled(), "message" => "Request cancelled"}}
        false -> continue_work(...)
      end
    end

Or use the `Process.exit/2` pattern for hard abort. Tools that complete
in tens of milliseconds typically do not need cancellation at all — the
client's notification will arrive after the response has already been
sent.

## Lifecycle

- The handler stashes the request id into `conn.assigns[:mcp_request_id]`
  before dispatching to the server callback.
- On `notifications/cancelled` arrival, the cancellation flag is set
  with `cancel/2`.
- When a request completes (success or error), the handler calls
  `clear/1` to release the flag so the entry does not linger.
- For pathological cases (cancel arrives after completion has already
  cleared the entry, or the server crashes before clear runs),
  `cleanup/1` can be called periodically to prune stale entries by
  age. There is no janitor wired by default — cancellation entries
  are bounded by the rate of in-flight cancellations.

Emits `[:conduit_mcp, :request, :cancelled]` telemetry on cancellation
with metadata `%{request_id: id, reason: reason}`, and
`[:conduit_mcp, :cancellation, :cleanup]` with measurement
`%{removed: count}` on each `cleanup/1` pass.

# `cancel`

Marks a request id as cancelled with an optional reason.

# `cancelled?`

Returns `true` when the given request has been cancelled.

Accepts a request id directly, or a `Plug.Conn` whose `assigns` contains
`:mcp_request_id` (set by `ConduitMcp.Handler` before dispatch).

# `cleanup`

Removes cancellation entries older than `ttl_ms` milliseconds. Defensive —
the handler clears entries inline when a request completes; this is for
the rare case where a cancel arrived after a crash or after the response
was already sent.

Returns the number of entries removed.

Emits `[:conduit_mcp, :cancellation, :cleanup]` telemetry with measurement
`%{removed: count}` so eviction is observable (mirrors the session janitor).

# `clear`

Clears a request id from the cancellation set. Idempotent.

# `reason`

Returns the cancellation reason recorded for the request, or `nil`.

---

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