Skip to content

Tools & approval

Agents act on the world through tools. muaz ships a small set of built-in tools, and registers MCP tools when MCP servers are connected. Every tool call is gated by an approval policy so the agent can’t run anything destructive without your say-so.

ToolDescription
read_fileRead a text file, with optional line offset/limit; large files are paginated
list_dirList the entries of a directory
searchRecursive regex search over file contents, with an optional filename glob
edit_fileReplace an exact, unique string in an existing file (old_stringnew_string)
write_fileWrite or overwrite a whole file, creating it (and parent dirs) if missing
patch_fileApply a unified diff patch to an existing file
shellExecute a shell command via sh -c; returns combined stdout/stderr, with a timeout
fetch_webpageFetch an http(s) URL and return its readable content — readability extraction to Markdown by default, or plain text
web_searchSearch the web (DuckDuckGo by default; Brave or self-hosted SearXNG configurable) and return ranked results as Markdown

The implementations live in kova-sdk (muaz’s agent SDK); web_search is muaz’s own. An agent can drop any built-in by setting it to off in its tools: block. MCP tools are opt-in per agent: a registered MCP server is attached only when the agent lists it under tools.servers.

The tools are sandboxed independently of the approval policy:

  • Filesystem tools (read_file, list_dir, search, edit_file, write_file, patch_file) are confined to the agent’s workspace root (workspace_root, defaulting to the directory muaz started in). Paths that escape it — and anything under ~/.muaz/ — are rejected, so the model can’t rewrite its own config or read your secrets.
  • shell runs in the workspace root and is killed after shell_timeout_secs.
  • Web tools (fetch_webpage, web_search) refuse private/internal addresses by default (SSRF protection) and honour the web_tools guardrails — HTTPS-only, URL allow/deny globs, response/size caps. See Configuration.

Every tool has one access level, and that single axis governs both whether a tool is available and whether it prompts:

LevelMeaning
offNot available — removed from the agent’s toolset entirely
askAvailable; every call needs approval
autoAvailable; runs without prompting

Levels are set globally in config.yaml under tools: and overridden per-agent under the agent’s own tools: block. There is no separate include/exclude list and no separate approve/deny/prompt policy — off/ask/auto is the whole model.

# config.yaml — global defaults
tools:
default: ask # fallback for any tool with no more specific setting
built_in:
read_file: auto # read-only tools are workspace-confined, so auto
list_dir: auto
search: auto
edit_file: ask
write_file: ask
patch_file: ask
shell: ask
fetch_webpage: ask
web_search: ask
blocked: [] # force these Off everywhere — agents cannot re-enable

A tool value is either a bare level (shell: ask) or a level with allow_when matchers that auto-approve specific calls while the tool otherwise stays at ask:

tools:
built_in:
shell:
level: ask
allow_when:
- "program=git" # auto-approve any `git …` command
- "dir=/srv/app" # …or a command whose path is under /srv/app
- "npm run *" # …or a glob over the primary argument
MatcherMatches
program=gitshell calls whose command’s first token is exactly git
dir=/srv/appfile/shell calls whose path is /srv/app or inside it
npm run *glob over the primary argument (command, file path, or url)

The structured program= and dir= forms are more precise than a raw glob — prefer them.

Global config only covers built-in tools. An agent attaches an MCP server by listing it under tools.servers, with its own default and per-tool levels:

tools:
servers:
github:
default: ask
tools:
get_issue: auto # read-only calls run; everything else asks

For a given tool call the most specific setting wins, in this order:

  1. global tools.blockedoff (always wins; agents cannot override)
  2. agent per-tool (tools.built_in.<tool> or tools.servers.<server>.tools.<tool>)
  3. agent per-server default (tools.servers.<server>.default)
  4. agent tools.default
  5. global tools.built_in.<tool>
  6. global tools.default

A session grant or a matching allow_when lets an otherwise-ask call through without re-prompting.

When a call needs your decision, muaz shows a plain-language summary (the exact command, URL, or path — not raw JSON) and a menu:

  • Approve once — run this single call
  • Approve for session — stop asking for matching calls this session; when the call has several scope levels you pick one (e.g. just this command, any git command, or every shell call)
  • Deny — refuse this call
  • Deny with a reason — refuse and pass a short note back to the agent so it can adapt (e.g. “use the staging database instead”)

Pass muaz chat --remember-approvals to persist “Approve for session” grants so a resumed session doesn’t re-prompt. Use /tools inside the REPL to see all registered tools and their current access levels.

An ask call reached in a non-interactive context (a pipeline step or a delegated subagent) has no one to prompt, so it is denied — give such agents the access levels they need up front (auto, or ask with allow_when).