ClaudeWrapper.ToolPattern (ClaudeWrapper v0.8.0)

Copy Markdown View Source

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__tool or mcp__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).

t()

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

problem()

@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.

t()

@type t() :: %ClaudeWrapper.ToolPattern{value: String.t()}

Functions

all(name)

@spec all(String.t()) :: t()

Shorthand for tool_with_args/2 with * as the argument pattern -- "any args to this tool."

iex> ClaudeWrapper.ToolPattern.all("Write") |> to_string()
"Write(*)"

mcp(server, tool)

@spec mcp(String.t(), String.t()) :: t()

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__*"

parse(input)

@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}}

to_string(tool_pattern)

@spec to_string(t()) :: String.t()

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"

tool(name)

@spec tool(String.t()) :: t()

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"

tool_with_args(name, args)

@spec tool_with_args(String.t(), String.t()) :: t()

A tool with an argument glob, rendered Name(args).

iex> ClaudeWrapper.ToolPattern.tool_with_args("Bash", "git log:*") |> to_string()
"Bash(git log:*)"