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

Validated builder for MCP tool definitions.

`Raxol.MCP.Registry.register_tools/2` accepts a list of maps with
`:name`, `:description`, `:inputSchema`, and `:callback` keys. The shape
is documented as a `tool_def` type but isn't validated until call
time -- typos like `:input_schema` instead of `:inputSchema` silently
miss the registration; tools without callbacks blow up only when an
agent tries to invoke them.

This module catches those mistakes at the seam:

    tool =
      Raxol.MCP.ToolDef.new!("symphony_list_runs",
        description: "Returns a snapshot of active Symphony runs.",
        input_schema: %{type: "object", properties: %{}},
        callback: fn _args -> %{ok: true} end
      )

    Raxol.MCP.Registry.register_tools(registry, [tool])

`new/2` returns `{:ok, def}` or `{:error, reasons}`. `new!/2` raises
on validation failure.

Both spellings of the schema key are accepted (`:inputSchema` and
`:input_schema`); the canonical key on the returned map is
`:inputSchema` to match the MCP spec.

# `t`

```elixir
@type t() :: Raxol.MCP.Registry.tool_def()
```

# `validation_error`

```elixir
@type validation_error() ::
  :missing_name
  | :missing_description
  | :missing_callback
  | :invalid_callback_arity
  | :missing_input_schema
  | :invalid_input_schema
```

# `new`

```elixir
@spec new(
  String.t(),
  keyword()
) :: {:ok, t()} | {:error, [validation_error()]}
```

Builds a validated tool definition.

Required:

- `name` -- string, non-empty.
- `:description` -- string, non-empty.
- `:callback` -- 1-arity function (receives the arguments map).
- `:input_schema` (or `:inputSchema`) -- map with at minimum `:type` /
  `"type"` set to `"object"`.

Returns `{:ok, def}` (a map matching `Raxol.MCP.Registry.tool_def()`)
or `{:error, [validation_error()]}`.

# `new!`

```elixir
@spec new!(
  String.t(),
  keyword()
) :: t()
```

Like `new/2` but raises `ArgumentError` on validation failure.

# `sensitive?`

```elixir
@spec sensitive?(map()) :: boolean()
```

Whether `tool` declares itself sensitive -- destructive, or otherwise
something that must not run unattended (moving money is the motivating case).

Recognized, any of which is enough:

  * `annotations.destructiveHint == true` -- the MCP-standard hint;
  * `annotations.sensitive == true` -- for a tool that is not destructive in
    the MCP sense but still must be gated.

This predicate is deliberately **one-directional: an annotation can only ever
ADD an obligation, never remove one.** A tool claiming `destructiveHint:
false` or `readOnlyHint: true` gets no exemption from anything -- it is a
self-report, and the harness already rules elsewhere (U21-R3) that a
self-reported `mutating: false` cannot remove a call from the mutation set.
Declaring yourself dangerous is credible; declaring yourself safe is not.

# `validate`

```elixir
@spec validate(map()) :: :ok | {:error, [validation_error()]}
```

Validates an existing tool map (e.g., one built directly without `new/2`).

Useful when adopting `ToolDef` incrementally -- run validation on a list
of hand-built tools at registration time and fail fast.

---

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