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.
Built-in tools
Section titled “Built-in tools”| Tool | Description |
|---|---|
read_file | Read a text file, with optional line offset/limit; large files are paginated |
list_dir | List the entries of a directory |
search | Recursive regex search over file contents, with an optional filename glob |
edit_file | Replace an exact, unique string in an existing file (old_string → new_string) |
write_file | Write or overwrite a whole file, creating it (and parent dirs) if missing |
patch_file | Apply a unified diff patch to an existing file |
shell | Execute a shell command via sh -c; returns combined stdout/stderr, with a timeout |
fetch_webpage | Fetch an http(s) URL and return its readable content — readability extraction to Markdown by default, or plain text |
web_search | Search 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.
Guardrails
Section titled “Guardrails”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. shellruns in the workspace root and is killed aftershell_timeout_secs.- Web tools (
fetch_webpage,web_search) refuse private/internal addresses by default (SSRF protection) and honour theweb_toolsguardrails — HTTPS-only, URL allow/deny globs, response/size caps. See Configuration.
Tool access
Section titled “Tool access”Every tool has one access level, and that single axis governs both whether a tool is available and whether it prompts:
| Level | Meaning |
|---|---|
off | Not available — removed from the agent’s toolset entirely |
ask | Available; every call needs approval |
auto | Available; 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 defaultstools: 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-enableA 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 argumentallow_when matcher forms
Section titled “allow_when matcher forms”| Matcher | Matches |
|---|---|
program=git | shell calls whose command’s first token is exactly git |
dir=/srv/app | file/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.
MCP servers are opt-in per agent
Section titled “MCP servers are opt-in per agent”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 asksResolution order
Section titled “Resolution order”For a given tool call the most specific setting wins, in this order:
- global
tools.blocked→ off (always wins; agents cannot override) - agent per-tool (
tools.built_in.<tool>ortools.servers.<server>.tools.<tool>) - agent per-server default (
tools.servers.<server>.default) - agent
tools.default - global
tools.built_in.<tool> - global
tools.default
A session grant or a matching allow_when lets an otherwise-ask call through
without re-prompting.
At the interactive prompt
Section titled “At the interactive prompt”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
gitcommand, or everyshellcall) - 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).