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;serveris 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
:excludealways 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), soexclude: ["*/delete_repo"]also hidesDelete-Repo. Include patterns match exactly (case-sensitive), the same asymmetryConduitMcp.Client.Policyuses 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.
Summary
Functions
Compiles include: and exclude: pattern lists (both default to []).
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.
Whether tool is visible under every pattern, including function patterns
(called with ctx). Use at read time, in the caller.
Types
Functions
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.
@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.
@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.