# `ConduitMcp.Client.ToolFilter`
[🔗](https://github.com/nyo16/conduit_mcp/blob/v0.11.0/lib/conduit_mcp/client/tool_filter.ex#L1)

Tool visibility: which tools an application (and its LLM) can see at all.

Visibility is separate from authorization (`ConduitMcp.Client.Policy`): a
hidden tool is left out of tool lists and adapter output, and calling it
fails with `{:error, %ConduitMcp.Client.Error{kind: :not_found}}` (PO-7),
so it cannot be reached by guessing its name.

    filter =
      ConduitMcp.Client.ToolFilter.compile(
        include: ["github/*", "fs/read_*"],
        exclude: ["*/delete_*", fn tool, ctx -> hidden_for?(ctx.assigns.user, tool) end]
      )

    ConduitMcp.Client.ToolFilter.visible?(filter, tool, ctx)

## Patterns

Every pattern matches the structured target `{server_id, tool_name}`, never
a namespaced string, so `server "a_b"` + `tool "c"` and `server "a"` +
`tool "b_c"` can never be confused:

  * `"server/tool"` - `*` is a glob within one segment (it matches any run
    of characters, including none, in that segment only). A bare `"tool"`
    (no `/`) means any server. `"*"` matches every tool.
  * `{server, %Regex{}}` - the regex is matched against the tool name;
    `server` is a segment glob as above (`"*"` for any server).
  * a 2-arity function `fn %ConduitMcp.Client.Tool{}, ctx -> boolean end`.
    It is called in the caller. A function that raises, exits, throws or
    returns a non-boolean counts as matching for `:exclude` (hidden) and
    as not matching for `:include` (fail closed).

## Semantics

  * `:exclude` always wins: a tool matching any exclude pattern is hidden.
  * An empty `:include` (the default) means every tool not excluded is
    visible; otherwise a tool must match at least one include pattern.
  * Exclude patterns match **loosely** (Unicode lower-case, with `-`, `.`
    and `_` equivalent), so `exclude: ["*/delete_repo"]` also hides
    `Delete-Repo`. Include patterns match **exactly** (case-sensitive), the
    same asymmetry `ConduitMcp.Client.Policy` uses for deny and allow
    (PO-5). Loose matching applies to both segments, and a regex is tried
    against both the tool name and its folded form.

## Static and dynamic filtering

String and regex patterns depend only on the target, so they can be applied
once when a server's tool list is refreshed: `static_visible?/2`. Function
patterns depend on the call context (`ctx.assigns`), so they run at read
time in the caller: `visible?/3`. `static_visible?/2` never hides a tool
that `visible?/3` could show, so refresh-time filtering followed by
read-time filtering gives the same result as `visible?/3` alone.

# `pattern`

```elixir
@type pattern() ::
  String.t()
  | {String.t(), Regex.t()}
  | (ConduitMcp.Client.Tool.t(), map() -&gt; boolean())
```

A pattern as written in `compile/1` options.

# `t`

```elixir
@type t() :: %ConduitMcp.Client.ToolFilter{exclude: [matcher()], include: [matcher()]}
```

A compiled filter. Treat as opaque.

# `compile`

```elixir
@spec compile(keyword()) :: t()
```

Compiles `include:` and `exclude:` pattern lists (both default to `[]`).

Raises `ArgumentError` naming the option and the entry for an unknown
option, a non-list value, an unrecognized pattern, an empty segment, a
server segment containing `:`, or a function of the wrong arity.

# `static_visible?`

```elixir
@spec static_visible?(t(), ConduitMcp.Client.Tool.t()) :: boolean()
```

Whether `tool` survives the static (string and regex) patterns alone. Use
when a tool list is refreshed. Function patterns are not called: an
include list that has a function pattern keeps every tool it does not
statically include, for `visible?/3` to decide at read time.

# `visible?`

```elixir
@spec visible?(t(), ConduitMcp.Client.Tool.t(), map()) :: boolean()
```

Whether `tool` is visible under every pattern, including function patterns
(called with `ctx`). Use at read time, in the caller.

---

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