# Advanced configuration



This page covers the parts of `~/.cluster/config.toml` that go beyond a
single key: nested tables you edit by hand or through a dedicated command,
and where the CLI keeps its state.
For the flat keys and `cluster config set`, see
[Config basics](/docs/build/config/basic).

## Model providers [#model-providers]

The `cluster` provider is built in: it talks to `api_url` with your Cluster
sign-in. You can add one [Ollama](/docs/build/ollama) provider as a keyless
alternative and choose which one a new session starts on:

```toml
default_model_provider = "gpuserver"
default_model = "qwen3.5:9b"

[model_providers.gpuserver]
kind = "ollama"
base_url = "http://gpuserver:11434"
```

`base_url` is the host root; Cluster Build appends `/v1`. At most one
provider table is allowed, `kind` must be `"ollama"`, and
`default_model_provider` must name either `cluster` or that table. `/model`
lists Cluster models and every reachable configured provider, so the
default only sets the startup selection.

## MCP servers [#mcp-servers]

Connect the assistant to [Model Context Protocol](https://modelcontextprotocol.io)
servers with one `[mcp.servers.<name>]` table each. The name becomes a log
file name and part of every tool id (`mcp__<server>__<tool>`), so keep it to
letters, digits, `-`, and `_`, and avoid a double underscore.

```toml
[mcp.servers.example]
transport = "stdio"              # or "streamable_http"
command = "npx"                  # stdio: executable to launch
args = ["@example/mcp-server"]
read_only_tools = []             # tool names plan mode may still call
enabled = true                   # false keeps it listed but never connects
startup_timeout_sec = 5          # handshake timeout
tool_timeout_sec = 120           # per tool-call timeout
```

The transport-specific fields — `env_vars` and `env` for stdio, `url` and
the auth fields for streamable HTTP — are covered below, and the
[sample config](/docs/build/config/sample) shows every field in one place.

MCP tools run under the same [workspace trust](/docs/build/interactive#workspace-trust)
decision as native tools — there is no per-call confirmation. Add a server
only when you trust it, and list its safe operations in `read_only_tools` so
plan mode can still call them.

### stdio servers [#stdio-servers]

Cluster Build starts `command` as a child process when the server is needed.
The executable must be on `PATH` in the shell where you start `cluster`;
Python servers commonly use `uvx`, Node servers `npx`. For a local checkout,
point `command` and `args` at the built executable, such as
`command = "node"` and `args = ["/path/to/server/dist/index.js"]`.

Pass credentials with `env_vars` — a list of environment variable *names*.
Cluster Build reads each value from its own environment at startup and
forwards it to the child under the same name, so only the names live in
`config.toml`. Use the nested `env` table for non-secret literals (or, for
backwards compatibility, `${env:VAR}` templates). If the same key appears in
both, the explicit `env` value wins.

```toml
[mcp.servers.ups]
transport = "stdio"
command = "npx"
args = ["-y", "ups-mcp"]
startup_timeout_sec = 30
tool_timeout_sec = 120
env_vars = ["UPS_CLIENT_ID", "UPS_CLIENT_SECRET", "UPS_ACCOUNT_NUMBER"]

[mcp.servers.ups.env]
UPS_ENVIRONMENT = "production" # or "sandbox"
```

A stdio child receives only a small default allowlist (such as `PATH`,
`HOME`, and `SHELL`) plus `env_vars` and `env` — it does **not** inherit
arbitrary variables from your shell. If an `env_vars` entry is unset or
empty, that server is marked unavailable until you fix the environment and
restart.

```bash
export UPS_CLIENT_ID=...
export UPS_CLIENT_SECRET=...
export UPS_ACCOUNT_NUMBER=...
cluster mcp test ups
```

### Streamable HTTP servers [#streamable-http-servers]

For a `streamable_http` server behind authentication, supply a bearer token
with `bearer_token_env_var` (the name of an environment variable holding the
token) and/or env-backed headers with `env_http_headers` (header name to
variable name). As with `env_vars`, only the names are stored; values are
read at startup.

```toml
[mcp.servers.tasks]
transport = "streamable_http"
url = "https://tasks.example/mcp"
bearer_token_env_var = "TASKS_MCP_TOKEN"

[mcp.servers.tasks.env_http_headers]
X-Api-Key = "TASKS_API_KEY"
```

The older `auth_token` and `headers` fields still work and accept
`${env:VAR}` templates, but the name-only fields are preferred. All of these
are streamable-HTTP-only — setting them on a `stdio` server is an error. Set
at most one bearer source (`bearer_token_env_var`, `auth_token`, or
`oauth_audience`), and don't combine bearer auth with an explicit
`Authorization` header. `cluster doctor` warns when a server stores a likely
secret inline and points you at the name-only fields, without printing the
value.

### Per-user OAuth [#per-user-oauth]

For a server whose tools gate on the caller's identity, the CLI can manage a
per-user OAuth token instead of a static credential. Set `oauth_audience`,
`oauth_client`, and a non-empty `oauth_scopes` array (all required together,
streamable-HTTP-only, and mutually exclusive with the bearer fields), then
log in once:

```bash
cluster mcp login <server>    # browser PKCE login; stores a per-user token
cluster mcp logout <server>   # remove the stored token
```

The token lives at `~/.cluster/mcp-auth/<server>.json`, separate from your
main `cluster login`, and is refreshed and injected automatically. Until you
log in, the server connects unauthenticated and fails its handshake. If the
token expires mid-session and the server answers 401, the CLI refreshes it
once, rebuilds the connection, and retries.

### Disabling and inspecting servers [#disabling-and-inspecting-servers]

Set `enabled = false` to keep a server in your config but stop connecting to
it. It stays listed in `/mcp` and `cluster mcp list` but registers no tools.

```bash
cluster mcp list            # every configured server; an unresolvable one shows as `crashed` with its error
cluster mcp test <server>   # connect to one server; exits non-zero if it can't resolve or connect
```

A single server whose config can't resolve no longer takes down the CLI: it
is listed as `crashed` while every healthy server and your native tools keep
working. Inside a session, `/mcp` opens a live manager to reconnect, enable
or disable, authenticate, and inspect a server's tools without restarting.

## Built-in browser [#built-in-browser]

A Playwright-backed browser provider is enabled out of the box and appears
in `/mcp` alongside your own servers. Turn it off, or replace it by
declaring any browser-capability MCP server of your own:

```toml
[browser]
enabled = false
```

`cluster config set browser_enabled false` writes the same thing. Browser
tools ask before accessing each new HTTP(S) origin and remember accepted
origins only for the current session.

## Computer tool [#computer-tool]

The native `computer` desktop tool is opt-in and only advertised on
supported graphical sessions (Linux GNOME Wayland, macOS 26+):

```toml
[computer]
enabled = true
unattended = false   # true suppresses the per-process capture and control prompts
```

See [Computer use](/docs/build/computer-use) for setup.

## Tools [#tools]

```toml
[tools.request_user_input]
default_mode = false   # true lets the assistant ask clarifying questions outside plan mode
```

The `request_user_input` tool is always available in plan mode.

## Skills [#skills]

Skills are reusable instructions the assistant can pull in on demand. Drop a
`SKILL.md` (YAML frontmatter plus a Markdown body) into its own directory
under `~/.cluster/skills/`:

```
~/.cluster/skills/
  my-skill/
    SKILL.md
```

Cluster Build discovers skills at startup and loads a skill's body when it is
relevant. Manage discovery with a `[skills]` table:

```toml
[skills]
enabled = true        # master switch
disabled = []         # skill directory names to skip
```

Inspect them with `cluster skills list` / `cluster skills show <name>`, or
`/skills` in a session.

## Custom commands [#custom-commands]

Custom commands are local prompt shortcuts — explicit macros you invoke
yourself, rather than instructions the model pulls in on its own. Drop a
Markdown file at `~/.cluster/commands/<name>.md`:

```markdown
---
description: Review a requested scope
---

Review $ARGUMENTS carefully and report concrete findings.
```

`~/.cluster/commands/review.md` then shows up in the slash menu as `/review`
with a `[Command]` badge. Running `/review src/lib.rs` — interactively or
with `cluster exec` — replaces every `$ARGUMENTS` in the body before sending
the request. Frontmatter is optional; without `description`, the first
non-empty body line is used. Only immediate `.md` files are loaded, and a
built-in slash command always wins a name collision.

## Workspace trust [#workspace-trust]

Saved [workspace trust](/docs/build/interactive#workspace-trust) decisions
live in `config.toml`, one `[projects."<canonical-root>"]` table per
directory. Manage them with `cluster trust` rather than editing by hand:

```bash
cluster trust add [path]      # trust a workspace (defaults to the current directory)
cluster trust remove [path]
cluster trust list
```

```toml
[projects."/Users/me/src/app"]
trust_level = "trusted"

[projects."/Users/me/src/app/vendor"]
trust_level = "untrusted"   # suppresses the prompt and keeps execution disabled
```

The root is the repository root, so trusting any subdirectory trusts the
whole repository; git linked worktrees map to their main checkout. The
nearest configured ancestor wins, so an untrusted child can sit under a
trusted parent. Cluster refuses to persist trust for the filesystem root or
your home directory.

## Project instructions [#project-instructions]

When `include_project_instructions` is on (the default), Cluster Build sends
instruction files with every request as non-persisted context. It looks for
the first of `AGENTS.override.md`, `AGENTS.md`, or `CLAUDE.md` in each of
these places, in order:

1. `~/.cluster/AGENTS.md` — your global instructions.
2. The repository root.
3. Each directory between the root and your current working directory.

Outside a git repository only the working directory is searched. Discovery
is bounded: files are truncated once the total budget is spent, and the
model sees a truncation marker when that happens.

## Config and state locations [#config-and-state-locations]

Everything Cluster Build stores lives under `~/.cluster/`:

| Path            | Contents                                                                |
| --------------- | ----------------------------------------------------------------------- |
| `config.toml`   | All settings on this page and [Config basics](/docs/build/config/basic) |
| `auth.json`     | Your sign-in token (created by `cluster login`)                         |
| `AGENTS.md`     | Global project instructions                                             |
| `sessions/`     | Saved conversations (`cluster sessions ls`)                             |
| `subagents/`    | Transcripts of local child agents, grouped by root session              |
| `tool_outputs/` | Spooled bodies of large tool results                                    |
| `memories/`     | Cross-session memory (`MEMORY.md`, per-project files, raw extracts)     |
| `skills/`       | Your custom skills                                                      |
| `commands/`     | Your custom slash commands                                              |
| `themes/`       | User-provided syntax themes                                             |
| `mcp-auth/`     | Per-server MCP OAuth tokens                                             |
| `logs/`         | Rolling `cluster.log.<date>` files and `mcp/<server>.log`               |
| `update/`       | Auto-update state and lock                                              |
| `cron/`         | Per-project automation state                                            |
| `history.jsonl` | Composer input history                                                  |

Config writes use `config.lock` next to the file for cross-process locking.
The directory and its files are created with owner-only permissions.

## Next steps [#next-steps]

* **[Environment variables](/docs/build/config/environment-variables)** —
  one-run overrides, proxies, and logging.
* **[Sample config.toml](/docs/build/config/sample)** — every table above in
  one annotated file.
