# Tools & Skills User Guide

GoClaw provides a powerful ecosystem of tools and skills that enable the agent to interact with your system, data, and external services.

## Tools (Function Calling)

Tools are capabilities that the agent can actively execute. GoClaw features a library of **over 100 built-in tools** (ranging from file operations and web automation to database and observability integrations). To prevent context window bloat and keep the LLM focused, GoClaw utilizes a **RAG Tool Filter** to dynamically select and load only the relevant tools for each user query.

### Core Tools

| Category | Tools | Description |
|----------|-------|-------------|
| **File Operations** | `file_read`, `file_write`, `file_edit`, `read_file_lines`, `file_search`, `glob_files` | Read, write, and search files within the workspace. `file_edit` and `apply_patch` are preferred for precise changes. |
| **Shell & System** | `shell_exec`, `shell_exec_multi`, `process`, `clipboard` | Execute commands (one, or several in sequence), manage background processes, and interact with the system clipboard. |
| **Git & GitLab** | `git`, `gitlab`, `gitlab_hook` | Perform git operations and interact with GitLab projects, issues, and merge requests; have a project's events run a SOP ([`gitlab_hook`](automation/gitlab-webhook.md#asking-the-agent-for-a-hook), an admin in a conversation). |
| **Web & Network** | `web_search`, `web_fetch`, `browser`, `http_request` | Search the web, fetch content, automate headless Chrome, and make API requests. All of them go through the [outbound HTTP policy](#outbound-http). |
| **Database** | `db_query`, `mongo_query`, `db_write`, `db_analyze`, `db_enrich` | Query SQL (PostgreSQL, MySQL, ClickHouse) and MongoDB databases (read-only); `db_write` inserts, updates and deletes rows on datasources with `allow_write: true`. `db_analyze` reads a datasource's schema and caches it (the tables first, then one table's columns); `db_enrich` adds business notes — what a column means, what its values stand for — to that cached schema. |
| **Memory** | `memory_search`, `memory_get`, `memory_write`, `archive_search`, `archive_get` | Search and manage long-term agent memory and session archives. |
| **Knowledge** | `wiki_read`, `wiki_write`, `doc_index`, `review_learned`, `notes_distill` | Read and write the organisation wiki shared by all agents, and read the pages copied from [Wiki.js](wikijs.md); index a web page, wiki page or Markdown file in the workspace into searchable memory (admins); review learned facts held back as unverified (admins); clear resolved error notes and merge duplicate notes. |
| **Communication** | `message`, `notify`, `email` | Send messages to Discord/Teams, trigger notifications, and manage emails. |
| **Data & Utilities** | `json_transform`, `regex`, `text_utils`, `calc`, `datetime`, `spreadsheet`, `diff` | Process data, perform math, handle dates, and compare text. |
| **Agent & Sessions** | `agents_list`, `subagents`, `sessions_list`, `sessions_history`, `sessions_spawn`, `webhooks` | Manage multiple agents and conversation sessions; read the [webhook routes](configuration.md#webhook-routes) and what each received (`webhooks`, admins). |
| **SOP & Administration** | `sop_list`, `sop_get`, `sop_save`, `sop_run`, `sop_versions`, `sop_rollback`, `sop_review`, `sop_approve`, `sop_pin_channel`, `cron`, `update_core_instruction`, `capability_request` | Manage [SOPs](automation/sop.md) (versions, rollback, review date, approving a step that waits for approval, the channel a SOP's notices go to) and scheduled jobs; set the agent's core instruction; record a missing tool, view or permission for IT to provide. |
| **Infrastructure & Code** | `grafana_alerts`, `grafana_alert_rule`, `graylog_query`, `graylog_alert`, `uptrace_query`, `uptrace_monitor`, `zabbix_query`, `zabbix_alert`, `wazuh_query`, `wazuh_alert`, `netbox`, `parse_unl`, `sonarqube`, `sonarqube_hook`, `codereview_pull_repo`, `project_blast_radius` | The [observability tools](observability/index.md) and [NetBox](observability/netbox.md); read EVE-NG `.unl` topology files; query SonarQube (`tools.sonarqube`, or several instances under `tools.sonarqubes`); clone a repository, index its code structure and list every function that calls a given one, directly or through others. |
| **Media & Docs** | `image`, `pdf`, `document`, `diagram_render`, `generate_pdf`, `generate_html`, `voice_input`, `voice_output` | Analyze images; extract text from PDF, `.docx` and `.pptx` files; render a Mermaid diagram or raw HTML to a PNG with headless Chrome; generate PDF and HTML reports; transcribe an audio file and turn text into speech. |
| **Output Navigation** | `read_output` | Navigate large tool results (over `tools.max_result_chars`, default 8,000 characters) kept in memory for an hour — search, slice, or paginate without re-running the tool. |
| **Tool Discovery** | `search_tools` | Find a tool the agent may use but that was not selected for this request, and make it callable from the next step. A matching tool the caller's role may not use (an admin-only one, for a user) is never added: the search says it needs an administrator. |

### Configuration & Security

#### Shell Execution
Shell access is controlled via an allowlist in `config.yaml`:

```yaml
tools:
  shell:
    allow: ["git *", "npm *", "ls"]    # Allowed command prefixes
    deny: ["npm publish"]              # Refused even when allow matches
```

- With an empty `allow` (the default), `shell_exec` and `shell_exec_multi` refuse every
  command. `["*"]` allows any command.
- An entry allows every command that starts with it, unless a `deny` entry matches first.
- An allowed command is still refused when it contains a shell metacharacter:
  `;` `&` `|` `$` `>` `<` `` ` `` `\` `!` `(` `)` `{` `}` `[` `]` `=` `#` `^` `%` `@` or a
  line break. Pipes, redirection, chaining and variables are therefore never run, and an
  option is written `--flag value`, not `--flag=value`.
- A built-in deny list applies whatever the config says: destructive file-system commands,
  shutdown and reboot, service management, SSH, system package installs, user management,
  `ansible` and the deployment `make` targets.
- An agent's own `tools.shell.allow` or `deny` (in its `agents.list` entry) replaces the global
  list for that agent; the two are not merged.
- Commands run in the agent's workspace directory.

#### Outbound HTTP

Every tool that calls a URL goes through one outbound policy, `tools.http`: `http_request`,
`web_fetch`, `web_search`, the browser, MCP servers over HTTP/SSE, `gitlab`, `sonarqube`, the
observability tools (Grafana, Graylog, Uptrace, Zabbix, Wazuh, NetBox), and the tools that read a
file from a URL (`pdf`, `document`, `image`, `spreadsheet`).

The policy denies by default: with no `tools.http` settings, every request is refused.

An integration's own host is the exception. `gitlab`, `sonarqube` and the observability tools
may reach the host of the `url` (or Uptrace `dsn`) configured for the instance they call, private
address included, without an `allow` entry: it is the address you configured, not one the model
chose. That host is opened to that tool only — `http_request` and the browser still need it on
`allow` — and `deny` still refuses it. Any other host such a call reaches, a redirect elsewhere
say, is checked as usual. The Integrations page's **Test** runs under the same rule.

```yaml
tools:
  http:
    default_policy: deny       # the default; see below
    allow:
      - "api.search.brave.com" # web_search with Brave
      - "*.example.com"        # example.com and every subdomain
      - "wiki.internal"        # an internal host: its private address is allowed too
      - "10.0.0.17"            # a URL whose host is written as this IP
      - "10.0.0.0/24"          # URLs whose host is an IP in this range
    deny:
      - "169.254.169.254"      # cloud metadata endpoint
```

- `deny` is checked first and always wins.
- When `allow` has entries, only matching hosts are reached and `default_policy` makes no
  difference. When `allow` is empty, `default_policy: deny` refuses every host
  (`domain "…" is blocked: no domains in allow list (default_policy=deny)`), and
  `default_policy: allow` lets any public host through.
- An entry is an exact host name, `*.example.com` (that domain and its subdomains), `*`
  (every host), or an IP address or CIDR range. On `allow`, an IP or CIDR entry matches only a
  URL whose host is written as an IP address; a host name that resolves into the range still
  needs its own entry. On `deny`, it also refuses every host name that resolves into the range
  (`address … is blocked by policy`), even a name on `allow`.
- Private addresses — loopback, `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`,
  `100.64.0.0/10`, link-local (including `169.254.169.254`) and IPv6 unique-local — are reached
  only through an `allow` entry that names them: the host name, or, for a URL written with an
  IP, that IP or a CIDR range around it. With `default_policy: allow` and an empty `allow`, every
  host that resolves to a private address is refused (`access to private IP … is blocked (SSRF
  protection)`).
- A wildcard entry lets every host name it matches reach private addresses too: `*.example.com`
  admits any name under `example.com` that resolves to a LAN address, and `*` turns the
  private-address check off for every host. Allow internal hosts by their exact name, and keep
  wildcards for public domains.
- The browser and `notify` (sending to a webhook URL) follow the same rules, a host name on
  `allow` included. The requests a page makes (redirects, scripts, images) are checked against
  the policy of the agent using the browser: its own `tools.http` when it has one, else the
  global one.
- An agent's own `tools.http` (in its `agents.list` entry) replaces the global setting for that
  agent, field by field: its `allow` replaces the global `allow` rather than adding to it, and
  the same holds for `deny` and `default_policy`.

```yaml
agents:
  list:
    - id: ops
      tools:
        http:
          allow: ["wiki.internal", "api.github.com"]   # this agent reaches only these
```

The policy is read on every request, so a change applies without a restart. To add a host from
the command line:

```bash
goclaw config append tools.http.allow=api.example.com
```

##### Hosts per run

One agent run (the answer to one message, a SOP step, a cron job) may reach at most
`tools.http.max_hosts_per_run` different hosts, 100 unless set. Every request a tool makes
counts: `http_request`, `web_fetch`, `web_search`, `notify` and the integration tools. A
subagent counts toward the run that started it. Once the run has reached the limit, a request
to a new host is refused (`host "…" refused: this run has already reached 100 different
hosts`), hosts it already reached keep working, and the gateway log says `[http-policy] a run
reached tools.http.max_hosts_per_run`. The browser's page loads are not counted.

An assistant's turn reaches a few hosts, a research turn a few dozen; a run that keeps going
from one host to the next is working through a list of addresses. Raise the limit for an agent
that genuinely needs more, in its own `tools.http`; `0` turns it off.

```yaml
tools:
  http:
    max_hosts_per_run: 100
```

When the gateway is reachable from other machines (`gateway.bind` is `lan`, `all` or a
non-loopback address, or an ingress tunnel is set) and `tools.shell.allow` is `["*"]` or
`tools.http.default_policy` is `allow` with an empty `allow` list, globally or for one agent,
the gateway logs a `[config] WARNING` naming the setting at start-up and on every config change
that alters it. Anyone who can message such an agent can have it run any command or reach any
host for them.

#### Git Operations
Read-only operations are always allowed. Write operations must be explicitly enabled:

```yaml
tools:
  git:
    allow_write: true
```

An agent's own `tools.git.allow_write` (in its `agents.list` entry) overrides the global
setting, in either direction. Even with writes enabled, the `git` tool refuses:

- `push --force` / `push -f`, `reset --hard`, `clean -fd`, and deleting `main` or `master`;
- options that run a program or write a file (`--upload-pack`, `--exec`, `--output`), and the
  global options that change git's configuration or repository (`-c`, `-C`, `--git-dir`,
  `--work-tree`);
- a push to a GitLab host whose `tools.gitlab` / `tools.gitlabs` entry has `allow_write: false`;
- subcommands it does not know.

#### Database Connections
Configure datasources in `config.yaml`:

```yaml
datasources:
  analytics:
    driver: clickhouse
    dsn: "clickhouse://localhost:9000/analytics"
  crm:
    driver: postgres
    dsn: "${secret:CRM_DSN}"
    allow_write: true   # opens this datasource to db_write and metrics_store writes
```

`db_query` runs `SELECT`, `SHOW`, `DESCRIBE`, `EXPLAIN` and `WITH` queries only, and
refuses one that hides a write (a data-modifying `WITH`, `EXPLAIN ANALYZE` of a write,
`SELECT … INTO`). It returns at most `tools.max_db_query_rows` rows (default 10,000).

The database enforces the same rules, so they hold if goclaw's own checks miss
something and when goclaw stops waiting:

| Engine | Time limit (about 29s, under `db_query`'s 30s) | Read-only |
|---|---|---|
| PostgreSQL | `statement_timeout` in the query's transaction; `client_connection_check_interval` (14+) ends a query whose gateway died | A read-only transaction |
| MySQL | `max_execution_time` | `transaction_read_only=1` on the read's connection, put back after the read; a connection that cannot be put back is closed. A read-only transaction alone still runs `CREATE`, `DROP` and other DDL |
| MariaDB | `max_statement_time` | as MySQL |
| ClickHouse | `max_execution_time` (with `timeout_overflow_mode=throw`), where the account lets goclaw set it; otherwise the profile's own | `readonly=1`, where the account allows it: writes, `SET`, and `url()`, `file()`, `remote()`, `dictionary()` or `format()` read as a table are refused. `DESCRIBE TABLE url(…)` still connects out, so goclaw's checks refuse those calls everywhere. Read a dictionary with `dictGet()` |

A DBA's tighter limit on the account is kept: goclaw sets the lower of the
two. That covers PostgreSQL's `statement_timeout` and
`idle_in_transaction_session_timeout` (`ALTER ROLE … SET`), and MySQL's and
MariaDB's session time limit. On PostgreSQL, a pool whose DSN asked for the
simple protocol runs an approved saved query on it, so its values keep the
types they had.

A query the database stops at its time limit comes back with a message telling
the agent to narrow it. A query cannot raise its own limit: `SET`, `set_config`,
MySQL's `SET_VAR` and `MAX_EXECUTION_TIME` hints and ClickHouse's `SETTINGS`
clause are refused before the query is sent.

What the account allows decides what ClickHouse is sent, and goclaw logs
one `[db_limits]` line per datasource saying what that is:

- **`readonly=1` profile.** It can change no setting unless its profile
  marks one `CHANGEABLE_IN_READONLY`; `db_query` sends only those. Without
  that, the time limit is the profile's own `max_execution_time`: set one
  there, or mark it `CHANGEABLE_IN_READONLY`.
- **`readonly=2` account.** It gets only the time limit. It lets a query run
  `url()`, `file()` and `remote()` and change its own time limit, so prefer
  `readonly=1`.
- **`readonly=0` account whose profile fixes `readonly`.** It gets no
  read-only on the server.
- **Bounded `max_execution_time`.** A limit outside the account's bounds is
  brought within them.

Over ClickHouse's HTTP interface, goclaw also sends
`cancel_http_readonly_queries_on_client_close` where the account allows it.
Otherwise a query whose client gave up kept running there.

ClickHouse turns a deadline into a setting of its own, so every query goclaw
sends ClickHouse has none: health checks, `db_write`, `metrics_store` and
org-directory lookups included.

goclaw learns what each server allows when the datasource is first used.
When it cannot, for example because the server is busy, `db_query` makes
do for a minute and then tries again; Postgres still gets
`statement_timeout`. A PostgreSQL DSN that asks for the simple protocol
(`default_query_exec_mode=simple_protocol`, common with PgBouncer) is opened
on the extended protocol without named prepared statements, which works
through PgBouncer.

Every datasource is read-only unless it sets `allow_write: true`. On those,
`db_write` can insert rows, and update or delete them by key (`where` takes
equality, `null` and lists — no operators). Only admins and admin-authored
SOP/cron runs are offered the tool; ordinary users, webhook-triggered runs and
delegated SOP runs are refused. One call changes at most
`tools.max_db_write_rows` rows (default 100): an update or delete that would
change more is rolled back and nothing is written. ClickHouse datasources
accept insert only; MongoDB is not supported.

### GitLab Integration Setup

To use the `gitlab` tool, you need a Personal Access Token (PAT):

1. Go to **User Settings → Access Tokens** in GitLab.
2. Create a token with `api` and `read_repository` scopes.
3. Configure it in `config.yaml` using an environment variable for security:

```yaml
tools:
  gitlab:
    url: https://gitlab.com
    token: "${env:GITLAB_TOKEN}"
    allow_write: false
```

More GitLab instances go under `tools.gitlabs`, by name; the agent picks one with the
tool's `instance` argument, and without it uses `tools.gitlab`:

```yaml
tools:
  gitlabs:
    internal:
      url: https://gitlab.example.com
      token: "${secret:GITLAB_INTERNAL_TOKEN}"
      auth_type: oauth      # "pat" (default) sends PRIVATE-TOKEN; "oauth" sends Authorization: Bearer
      allow_write: true
      description: "Internal services"   # helps the agent choose the instance
```

An agent with its own `tools.gitlab` (in its `agents.list` entry) uses that instance for
every call. The GitLab host needs no entry on `tools.http.allow` (see
[Outbound HTTP](#outbound-http)).

---

## Tool Testing & Benchmarking (CLI)

You can call any registered tool directly from the command line — **without starting the gateway or involving an LLM**. This is useful for testing configurations, debugging tool output, and measuring latency.

### List all tools

```bash
goclaw tools list
```

### Execute a tool directly

```bash
# Basic call
goclaw tools exec datetime action=now

# Multiple args (auto-typed)
goclaw tools exec gitlab action=mrs_list project=group/repo page=2

# SQL query
goclaw tools exec db_query datasource=analytics query="SELECT count(*) FROM users"
```

Arguments use `key=value` syntax with automatic type detection:

| Value | Parsed as |
|---|---|
| `page=2` | number (as in a model's tool call) |
| `active=true` | `bool` |
| `name=hello` | `string` |
| `"id='12345'"` | `string` (quoted, so it is not read as a number) |
| `'filters={"status":"open"}'` | JSON object |

The shell removes one level of quotes before GoClaw sees the argument: wrap a value that must
stay a string in a second pair of quotes, and quote JSON and anything with `*`, `?` or spaces.
The [tool reference](tool-exec-reference.md) lists each tool's arguments.

Output shows the **full result** (never truncated), execution time, char count, and line count.

### Benchmark a tool

```bash
goclaw tools bench datetime action=now --runs=20
goclaw tools bench memory_search query=test --runs=10
```

```
Bench: datetime — 20 runs
  Min:    1ms
  Max:    8ms
  Avg:    3ms
  P95:    6ms
  Errors: 0 / 20
Last output (chars: 25):
2026-03-27T11:50:47+08:00
```

---

## Skills (Context Injection)

Skills are Markdown files that provide specialized knowledge or best practices to the agent. Unlike tools, skills are injected into the agent's system prompt as reference material.

### Managing Skills via CLI

| Command | Description |
|---------|-------------|
| `skills list` | List all available skills |
| `skills install <source>` | Install a skill from a registry, URL, GitHub ref, or local path |
| `skills show <name>` | View the content of a specific skill |
| `skills remove <name>` | Uninstall a user-provided skill |

### Built-in Skills
GoClaw comes with built-in skills, including:
- `coding`: Best practices for file operations and testing.
- `git`: Guidelines for commits and branching.
- `mcp`: How to utilize the Model Context Protocol.
- `observability`: SRE patterns for monitoring and incident response.
- `skill-creator`: A meta-skill to help you write your own skills.

### Custom Skills
You can add your own skills by placing `.md` files (or `<name>/SKILL.md` folders) in:
- Workspace: `~/.goclaw/workspace/skills/` (another agent's own workspace for that agent)
- User-level: `~/.goclaw/skills/`

A skill with the same name as an earlier one replaces it, in this order: built-in, the
agent's plugins, the workspace, the user-level directory.

---

## Model Context Protocol (MCP)

GoClaw supports the Model Context Protocol, allowing the agent to connect to external MCP servers to gain additional tools and resources. Use the `mcp` tool to interact with configured servers.

### Registering a server from the Web UI

The **MCP** tab registers servers without hand-editing `config.yaml`, and — the
part a config file cannot do — proves a definition works before it is saved.

1. **Register server** opens the editor. Pick the kind:
   - **HTTP / SSE** — a URL such as `http://127.0.0.1:3001/mcp/`. Leave transport
     on `auto` unless the server only speaks one of them.
   - **Local command (stdio)** — a command such as `npx` with arguments. It runs
     on the gateway host, as the gateway user.
2. **Test connection** dials the draft *as typed*, before anything is written. A
   success reports the server's name, version, protocol, capabilities and every
   tool it advertises.
3. **Try a tool** appears after a successful test: pick a tool, supply arguments
   as JSON, and run it. This calls the real server, so treat it as you would any
   other live call.
4. **Save** writes the definition to `mcp.servers.<name>` in `config.yaml`. The
   config watcher picks it up, and any cached session for that name is closed so
   the agent uses the new definition immediately — no restart.

Saved servers are listed with a **Test** button, so an operator can check a
server that used to work without opening the editor.

### Credentials

The list and the editor show header and environment values as they are written in
`config.yaml`. A token pasted as plain text is therefore shown to everyone who can
open the dashboard, and it sits unencrypted in the file.

Store the token in the vault and write a reference instead — the reference is what
the editor shows, and the gateway resolves it when it connects:

```
Authorization: Bearer ${secret:MY_MCP_TOKEN}
```

`${env:VAR}` and `${file:/path}` (the file's first line) work the same way. See the
secret reference syntax in `config.yaml.example`.

### In `config.yaml`

A server saved from the Web UI lands under `mcp.servers`; you can also write it by hand:

```yaml
mcp:
  servers:
    docs:
      url: "http://127.0.0.1:3001/mcp/"
      transport: auto                 # auto (Streamable HTTP, then SSE) | streamable | sse
      headers:
        Authorization: "Bearer ${secret:MY_MCP_TOKEN}"
      description: "Internal documentation search"   # helps the agent pick this server
    files:
      command: npx                    # stdio: runs on the gateway host
      args: ["-y", "@modelcontextprotocol/server-filesystem", "/data"]
      env:
        LOG_LEVEL: info
```

The agent reaches them through the `mcp` tool: `list_servers`, `list_tools`, and
`call_tool` with `server`, `tool` and an `arguments` object, plus the resource and prompt
actions (`list_resources`, `read_resource`, `list_prompts`, `get_prompt`, …). From the
command line:

```bash
goclaw tools exec mcp action=call_tool server=docs tool=search 'arguments={"query":"hello"}'
```

### If the test fails

The most common failure is not a broken server. GoClaw denies outbound HTTP to
hosts that are not allowlisted, and private addresses on top of that — which is
exactly where MCP servers usually live:

```
Outbound HTTP refused "127.0.0.1" … add "127.0.0.1" to tools.http.allow
```

Add the host to `tools.http.allow` in config and test again (see
[Outbound HTTP](#outbound-http) for how the list matches). The editor turns the
other frequent failures — a 401, a wrong path, a command that is not installed,
a port with nothing behind it — into the same kind of specific advice.
