# `Raxol.MCP.Authorizer`
[🔗](https://github.com/DROOdotFOO/raxol/blob/v2.7.0/lib/raxol/mcp/authorizer.ex#L1)

The authorization seam in front of MCP `tools/call`.

An authorizer is a 3-arity function `(tool_name, arguments, context) -> decision`,
evaluated by `Raxol.MCP.Server` before a tool runs. `raxol_mcp` deliberately
owns only the seam, not a policy engine: richer ALLOW/ASK/DENY logic (e.g.
`Raxol.Agent.Authorization.Engine`) lives upstream and plugs in as a closure,
so the engine and this enforcement point share one vocabulary without
`raxol_mcp` depending on `raxol_agent` (which would be a dependency cycle).

## Decisions

  * `:allow` -- run the tool.
  * `{:ask, prompt}` -- the action needs interactive approval. What the server
    does with it depends on whether the client can be asked:
    - a client that advertised the `elicitation` capability at `initialize`
      (and has a subscribed transport) is sent a real `elicitation/create`
      request carrying `prompt`, and the call is parked until it answers;
    - anything else gets the machine-readable `authorization_required` result
      (deny-on-ASK), which is also what a decline, a cancel, an error, and a
      timeout resolve to. Only an explicit approval runs the tool.
    See `Raxol.MCP.Server`'s "Elicitation" section.
  * `{:deny, reason}` -- refuse, machine-readably.

A `nil` authorizer means allow: a stdio transport already inherits the OS
process boundary. The SSE transport, which exposes tools over the network, is
guarded separately -- see `Raxol.MCP.Deployment`.

## Two seams: tools and reads

`Raxol.MCP.Server` evaluates authorizers at two independent seams:

  * `:authorizer` -- guards `tools/call` (this module's original purpose).
    Receives the TOOL name.
  * `:read_authorizer` -- guards the read/metadata surfaces
    (`resources/read`, `resources/subscribe`, `resources/unsubscribe`,
    `resources/list`, `prompts/get`, `prompts/list`, `tools/list`,
    `completion/complete`). Receives the METHOD name in the tool-name
    position, e.g. `"resources/read"` with `%{"uri" => uri}` as the
    arguments.

They are deliberately separate: a tool allowlist knows nothing about
method names and would otherwise deny every read. On read surfaces an
`{:ask, _}` decision resolves to deny (no elicitation for reads) and the
prompt is not echoed to the client. Network deployments must configure
BOTH seams: a nil `:read_authorizer` serves model state to any connected
client, so the SSE boot guard (`Raxol.MCP.Deployment`) refuses to boot
without one, exactly as it refuses without a tool authorizer.

# `context`

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

# `decision`

```elixir
@type decision() :: :allow | {:ask, String.t()} | {:deny, term()}
```

# `t`

```elixir
@type t() :: (String.t(), map(), context() -&gt; decision())
```

# `allow_all`

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

Allow every call. The implicit behaviour when no authorizer is configured.

# `allowlist`

```elixir
@spec allowlist([String.t()]) :: t()
```

Allow only tools whose name is in `names`; deny the rest. This is the allowlist
exposure mode: pair it with a narrow set to expose a safe subset of the
auto-derived tool surface over an untrusted transport.

# `decide`

```elixir
@spec decide(t() | nil, String.t(), map(), context()) :: decision()
```

Evaluate an authorizer, treating `nil` as `:allow`.

# `deny_all`

```elixir
@spec deny_all(term()) :: t()
```

Deny every call with `reason`.

---

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