# `Raxol.MCP.Client.Reservation`
[🔗](https://github.com/DROOdotFOO/raxol/blob/v2.8.0/lib/raxol/mcp/client/reservation.ex#L1)

Single-use handles for a priced `tools/call` (ADR-0037 decision 7).

The transport refuses a priced tool whose call carries no spend-gate
handle. That refusal was `Map.get(request, :reservation)` truthiness, which
is not a check: `Raxol.MCP.Client.call_tool/4` is public, so any caller
could pass `reservation: "anything"` and the transport could not tell a
live reservation from a settled one, a refused one, or a string someone
invented. The metering gate sits on the function that issues the request,
and a nominal gate there is the same hole as no gate at all.

So a handle is a value that must be MINTED and can be spent once. `mint/1`
writes 18 bytes of `:crypto.strong_rand_bytes/1` into the shared table
`Raxol.MCP.Client.Tables` owns; `consume/2` takes it out. A literal, a
replay of a handle already spent, and one past its TTL are all
indistinguishable from an absent handle, which is what
`Raxol.Agent.McpSpendHook` mints against and the transport refuses.

## What this does and does not defend

It defends the transport's own gate: a call arriving through the public API
with a forged, reused or stale handle is refused, and a handle the spend
hook minted is good for exactly one request. It does not pretend to defend
against arbitrary code on this node -- the table is `:public`, because the
minting side is `raxol_agent` and the consuming side is this package, and a
process that can write another process's ETS table can equally call this
transport directly. The authority being protected is the SPEND HOOK's: only
the hook that reserved against a budget has a handle to hand on.

## Why a TTL

A minted handle is not always spent. The transport refuses before issuing
for a full in-flight window (`:busy`), an open breaker, or an unknown
price, and each of those leaves the handle unconsumed. Every mint sweeps
what has expired, so the table is bounded by the handles minted inside one
TTL rather than by the lifetime of the node.

# `consume`

```elixir
@spec consume(:ets.table(), term()) :: :ok | :error
```

Spend `token`, or refuse it.

`:ok` exactly once per minted handle. `:error` for anything else: a handle
never minted, one already spent, one whose TTL has passed, or a term that
is not a handle at all.

# `mint`

```elixir
@spec mint() :: String.t()
```

Mint a single-use handle in the shared table, starting its owner if needed.

# `mint`

```elixir
@spec mint(:ets.table()) :: String.t()
```

Mint a single-use handle in `table`.

# `new`

```elixir
@spec new(atom()) :: :ets.table()
```

A table for minted handles. `Raxol.MCP.Client.Tables` owns the shared one.

---

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