GoClaw provides a powerful ecosystem of tools and skills that enable the agent to interact with your system, data, and external services.
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.
| 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, 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. |
| 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; 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 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 (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 and NetBox; 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. |
Shell access is controlled via an allowlist in config.yaml:
tools:
shell:
allow: ["git *", "npm *", "ls"] # Allowed command prefixes
deny: ["npm publish"] # Refused even when allow matches
allow (the default), shell_exec and shell_exec_multi refuse every
command. ["*"] allows any command.deny entry matches first.; & | $ > < ` \ ! ( ) { } [ ] = # ^ % @ or a
line break. Pipes, redirection, chaining and variables are therefore never run, and an
option is written --flag value, not --flag=value.ansible and the deployment make targets.tools.shell.allow or deny (in its agents.list entry) replaces the global
list for that agent; the two are not merged.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.
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.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.*.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.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)).*.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.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.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.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:
goclaw config append tools.http.allow=api.example.com
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.
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.
Read-only operations are always allowed. Write operations must be explicitly enabled:
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;--upload-pack, --exec, --output), and the
global options that change git's configuration or repository (-c, -C, --git-dir,
--work-tree);tools.gitlab / tools.gitlabs entry has allow_write: false;Configure datasources in config.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.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.
To use the gitlab tool, you need a Personal Access Token (PAT):
api and read_repository scopes.config.yaml using an environment variable for security: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:
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).
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.
goclaw tools list
# 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 lists each tool's arguments.
Output shows the full result (never truncated), execution time, char count, and line count.
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 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.
| 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 |
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.You can add your own skills by placing .md files (or <name>/SKILL.md folders) in:
~/.goclaw/workspace/skills/ (another agent's own workspace for that agent)~/.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.
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.
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.
http://127.0.0.1:3001/mcp/. Leave transport
on auto unless the server only speaks one of them.npx with arguments. It runs
on the gateway host, as the gateway user.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.
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.
config.yamlA server saved from the Web UI lands under mcp.servers; you can also write it by hand:
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:
goclaw tools exec mcp action=call_tool server=docs tool=search 'arguments={"query":"hello"}'
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 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.