Tool permission patterns for --allowed-tools / --disallowed-tools.
The Claude CLI accepts three pattern shapes in its tool lists:
- Bare name:
Bash,Read,Write. - Name with argument glob:
Bash(git log:*),Write(src/*.ex). - MCP pattern:
mcp__server__toolormcp__server__*.
This struct models all three. The typed constructors (tool/1,
tool_with_args/2, all/1, mcp/2) always produce well-formed
output. parse/1 validates the shape of a raw string and returns a
t:pattern_error/0 on malformed input.
For back-compat, the loose constructors (and the string clauses on
ClaudeWrapper.Query.allowed_tool/2 / disallowed_tool/2) accept any
string and store it verbatim, so callers passing raw CLI strings keep
working without changes. Use parse/1 directly when you want to catch
typos before the CLI invocation.
This is the Elixir port of the Rust crate's ToolPattern /
PatternError (../claude-wrapper/src/tool_pattern.rs). Validation
is shape-level only: tool names are not checked against any allowlist
because the CLI's tool inventory evolves independently.
Example
iex> ClaudeWrapper.ToolPattern.tool_with_args("Bash", "git log:*") |> to_string()
"Bash(git log:*)"
iex> ClaudeWrapper.ToolPattern.all("Write") |> to_string()
"Write(*)"
iex> ClaudeWrapper.ToolPattern.mcp("my-server", "*") |> to_string()
"mcp__my-server__*"
Summary
Types
The specific validation problem behind a parse/1 failure, carried in
the :reason field of the returned ClaudeWrapper.Error.t/0 (kind
:invalid_tool_pattern).
Functions
Shorthand for tool_with_args/2 with * as the argument pattern --
"any args to this tool."
An MCP pattern: mcp__{server}__{tool}. Pass "*" as the tool to
match any tool from the server.
Parse and validate a raw CLI-format pattern string.
Render the pattern as the string it will become in the CLI arg.
A bare tool name, e.g. tool("Bash") -> Bash.
A tool with an argument glob, rendered Name(args).
Types
@type problem() :: :empty | :missing_name | {:unbalanced_parens, String.t()} | {:illegal_char, String.t()}
The specific validation problem behind a parse/1 failure, carried in
the :reason field of the returned ClaudeWrapper.Error.t/0 (kind
:invalid_tool_pattern).
:empty-- input was empty or all whitespace.:missing_name-- the tool-name part was empty (e.g.(args)with nothing before the().{:unbalanced_parens, original}-- parentheses were unbalanced or appeared out of order. Carries the trimmed input.{:illegal_char, original}-- contained a comma (splits the argv) or a control char (can break the shell). Carries the trimmed input.
@type t() :: %ClaudeWrapper.ToolPattern{value: String.t()}
Functions
Shorthand for tool_with_args/2 with * as the argument pattern --
"any args to this tool."
iex> ClaudeWrapper.ToolPattern.all("Write") |> to_string()
"Write(*)"
An MCP pattern: mcp__{server}__{tool}. Pass "*" as the tool to
match any tool from the server.
iex> ClaudeWrapper.ToolPattern.mcp("my-server", "do_thing") |> to_string()
"mcp__my-server__do_thing"
iex> ClaudeWrapper.ToolPattern.mcp("my-server", "*") |> to_string()
"mcp__my-server__*"
@spec parse(String.t()) :: {:ok, t()} | {:error, ClaudeWrapper.Error.t()}
Parse and validate a raw CLI-format pattern string.
Validation is shape-level only (non-empty, balanced parens, no comma
or control chars). Tool names are not checked against any allowlist
because the CLI's tool inventory evolves independently. Surrounding
whitespace is trimmed before validation, so leading/trailing control
characters do not trip {:illegal_char, _}.
Returns {:ok, t} or {:error, %ClaudeWrapper.Error{kind: :invalid_tool_pattern}} whose :reason is the specific
problem/0.
iex> ClaudeWrapper.ToolPattern.parse("Bash(git log:*)")
{:ok, %ClaudeWrapper.ToolPattern{value: "Bash(git log:*)"}}
iex> ClaudeWrapper.ToolPattern.parse("")
{:error, %ClaudeWrapper.Error{kind: :invalid_tool_pattern, reason: :empty}}
iex> ClaudeWrapper.ToolPattern.parse("(args)")
{:error, %ClaudeWrapper.Error{kind: :invalid_tool_pattern, reason: :missing_name}}
Render the pattern as the string it will become in the CLI arg.
The String.Chars protocol is also implemented, so the kernel
to_string/1 and string interpolation work too.
iex> ClaudeWrapper.ToolPattern.to_string(ClaudeWrapper.ToolPattern.tool("Read"))
"Read"
A bare tool name, e.g. tool("Bash") -> Bash.
No validation beyond trimming whitespace; the CLI is the source of
truth for which tool names exist. Use parse/1 when you want shape
validation.
iex> ClaudeWrapper.ToolPattern.tool(" Bash ") |> to_string()
"Bash"
A tool with an argument glob, rendered Name(args).
iex> ClaudeWrapper.ToolPattern.tool_with_args("Bash", "git log:*") |> to_string()
"Bash(git log:*)"