Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion content/agents/codex.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ description: "OpenAI Codex CLI coding agent."
|----------|------|
| Project skills | `.agents/skills` (generic fallback) |
| Global skills | `~/.codex/skills` |
| MCP config | `~/.codex/mcp.json` |
| MCP config | `~/.codex/config.toml` |
| Audit search (project) | `.agents/skills`, `.github/skills`, `.claude/skills` |
| Audit search (global) | `~/.codex/skills`, `~/.agents/skills`, `~/.copilot/skills` |

Expand All @@ -26,6 +26,8 @@ skills:

## MCP example

Codex reads MCP servers from `~/.codex/config.toml` under `[mcp_servers.<name>]` tables. gaal detects the `.toml` extension on the registry-resolved path and writes TOML, preserving unrelated keys (`model`, `sandbox`, `[analytics]`, …) on every write.

```yaml gaal.yaml
mcps:
- name: filesystem
Expand All @@ -36,6 +38,10 @@ mcps:
args: [mcp-server-filesystem, ~/projects]
```

<Note>
If you have an existing `~/.codex/mcp.json` from an older gaal release, Codex never read it. `gaal sync` prints a one-shot warning when it sees the file; it's safe to delete by hand.
</Note>

## Related

<CardGroup cols={2}>
Expand Down
2 changes: 1 addition & 1 deletion content/agents/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ gaal ships with a built-in registry of 21 coding agents plus a vendor-neutral `g
|-------|----------------|---------------|------------|
| Claude Code | `.claude/skills` | `~/.claude/skills` | `~/.config/claude/claude_desktop_config.json` |
| Cursor | `.agents/skills` (generic) | `~/.cursor/skills` | `~/.cursor/mcp.json` |
| Codex | `.agents/skills` (generic) | `~/.codex/skills` | `~/.codex/mcp.json` |
| Codex | `.agents/skills` (generic) | `~/.codex/skills` | `~/.codex/config.toml` |
| GitHub Copilot | `.github/skills` | `~/.copilot/skills` | `~/.vscode/settings.json` |
| Windsurf | `.windsurf/skills` | `~/.codeium/windsurf/skills` | `~/.codeium/windsurf/mcp_settings.json` |
| Continue | `.continue/skills` | `~/.continue/skills` | `~/.continue/config.json` |
Expand Down
2 changes: 1 addition & 1 deletion content/cli/agents.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ $ gaal agents
NAME INSTALLED PROJECT_SKILLS GLOBAL_SKILLS MCP_CONFIG
claude-code yes .claude/skills ~/.claude/skills ~/.config/claude/claude_desktop_config.json
cursor yes .agents/skills ~/.cursor/skills ~/.cursor/mcp.json
codex yes .agents/skills ~/.codex/skills ~/.codex/mcp.json
codex yes .agents/skills ~/.codex/skills ~/.codex/config.toml
github-copilot no .github/skills ~/.copilot/skills ~/.vscode/settings.json
windsurf no .windsurf/skills ~/.codeium/windsurf/skills ~/.codeium/windsurf/mcp_settings.json
Expand Down
4 changes: 2 additions & 2 deletions content/concepts/agents-and-renderers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ $ gaal agents
NAME INSTALLED PROJECT_SKILLS GLOBAL_SKILLS MCP_CONFIG
claude-code yes .claude/skills ~/.claude/skills ~/.config/claude/claude_desktop_config.json
cursor yes .agents/skills ~/.cursor/skills ~/.cursor/mcp.json
codex yes .agents/skills ~/.codex/skills ~/.codex/mcp.json
codex yes .agents/skills ~/.codex/skills ~/.codex/config.toml
github-copilot no .github/skills ~/.copilot/skills ~/.vscode/settings.json
```
Expand All @@ -45,7 +45,7 @@ Agents marked installed are the ones that win when you use the `agents: ["*"]` w
Once gaal knows *what* to install (the plan from `gaal sync`) and *where* (the registry), the per-agent renderer does the actual write:

- For skills, the renderer copies the source files into the agent's skills directory and adapts file names if the agent expects a different layout.
- For MCP servers, the renderer reads the target JSON, upserts the `mcpServers` entry, and writes the file back atomically.
- For MCP servers, the renderer reads the target file, upserts the entry under `mcpServers` (JSON) or `[mcp_servers.<name>]` (TOML, used by Codex), and writes the file back atomically.

Renderers are responsible for the *guarantees*, they never clobber, they always write atomically, and they fail loudly when the target is malformed.

Expand Down
18 changes: 9 additions & 9 deletions content/concepts/mcp-servers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ title: "MCP servers"
description: "Upsert MCP server entries into agent configs without clobbering them."
---

An **MCP server** is a process that speaks the [Model Context Protocol](https://modelcontextprotocol.io). Coding agents that support MCP read a JSON config that lists the servers to launch and how to reach them.
An **MCP server** is a process that speaks the [Model Context Protocol](https://modelcontextprotocol.io). Coding agents that support MCP read a config file (JSON for most, TOML for Codex) that lists the servers to launch and how to reach them.

gaal manages those entries declaratively: you describe each server once in `gaal.yaml` and gaal upserts it into the right JSON file for every agent that should see it. The destination path is resolved automatically from the [agent registry](/agents/overview) — you no longer have to spell it out.
gaal manages those entries declaratively: you describe each server once in `gaal.yaml` and gaal upserts it into the right config file for every agent that should see it. The destination path is resolved automatically from the [agent registry](/agents/overview), and the format is picked from its extension (`.json` or `.toml`) — you no longer have to spell either out.

## The upsert guarantee

Every agent's MCP config lives inside a larger config file. `claude_desktop_config.json` carries other Claude Desktop settings; `~/.vscode/settings.json` carries every VS Code preference. You can't safely overwrite the whole file.
Every agent's MCP config lives inside a larger config file. `claude_desktop_config.json` carries other Claude Desktop settings; `~/.vscode/settings.json` carries every VS Code preference; `~/.codex/config.toml` carries Codex's `model`, `sandbox`, and `[analytics]` settings. You can't safely overwrite the whole file.

gaal reads the target JSON, modifies the `mcpServers` object in place, and writes it back. Other top-level keys are preserved exactly. That's the upsert guarantee: gaal never clobbers a config it didn't author.
gaal reads the target, modifies the MCP servers section in place (`mcpServers` in JSON, `[mcp_servers.<name>]` tables in TOML), and writes it back. Other top-level keys are preserved exactly. That's the upsert guarantee: gaal never clobbers a config it didn't author.

## Targeting agents, not paths

Expand Down Expand Up @@ -68,7 +68,7 @@ mcps:

### Remote source

For configs maintained elsewhere, point at a URL that returns an `mcpServers` document. gaal downloads the JSON and merges the entry whose key matches `name` into each target.
For configs maintained elsewhere, point at a URL that returns an `mcpServers` document. gaal downloads the JSON and merges the entry whose key matches `name` into each target (translating to TOML when the resolved target is a `.toml` file).

```yaml
mcps:
Expand Down Expand Up @@ -116,15 +116,15 @@ The bridge handles authentication redirects and session resumption on your behal

## Behaviour during sync

- The target JSON is updated in place; the named entry is added or refreshed under `mcpServers`.
- Other top-level keys in the target file are preserved.
- Other entries inside `mcpServers` that gaal didn't author are preserved.
- The target file is updated in place; the named entry is added or refreshed under `mcpServers` (JSON) or `[mcp_servers.<name>]` (TOML).
- Other top-level keys in the target file are preserved. TOML targets round-trip through a generic map, so unrelated tables (`[analytics]`, `[features]`, …) survive every write; comments are not preserved.
- Other MCP entries that gaal didn't author are preserved.
- Removing an `mcps[]` entry from `gaal.yaml` does **not** remove it from the target file unless you pass `--prune`.
- If the target's parent directory is missing, the entry is skipped — gaal never creates an agent's config directory as a side effect.

## Behaviour with `--prune`

`gaal sync --prune` removes any `mcpServers` entries from gaal-managed targets that aren't in `gaal.yaml`. It only touches keys gaal could plausibly own; your other settings and other servers stay untouched. See [Pruning](/workflows/pruning) for the exact algorithm.
`gaal sync --prune` removes any MCP server entries from gaal-managed targets that aren't in `gaal.yaml`. It only touches keys gaal could plausibly own; your other settings and other servers stay untouched. See [Pruning](/workflows/pruning) for the exact algorithm.

## Migrating from `target:`

Expand Down
30 changes: 29 additions & 1 deletion content/configure/mcp-servers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ mcps:
args: [mcp-server-filesystem, ~/projects]
```

For Codex, gaal writes the entry as a `[mcp_servers.filesystem]` TOML table; for the others it lands under `mcpServers` in JSON. Format dispatch is automatic.

Or use the wildcard to install in every agent registered on this machine that exposes a global MCP config:

```yaml
Expand Down Expand Up @@ -148,6 +150,32 @@ mcps:

gaal upserts under the `mcpServers` key while leaving every other VS Code setting in place.

## Codex (TOML)

Codex reads MCP servers from `~/.codex/config.toml` under `[mcp_servers.<name>]` tables. gaal detects the `.toml` extension on the registry-resolved path and writes TOML automatically:

```yaml
mcps:
- name: context7
agents: [codex]
global: true
inline:
command: npx
args: ["-y", "@upstash/context7-mcp@latest"]
```

Becomes:

```toml
[mcp_servers.context7]
command = 'npx'
args = ['-y', '@upstash/context7-mcp@latest']
```

Unrelated top-level keys (`model`, `sandbox`, `[analytics]`, `[features]`, …) are preserved on every write. Comments are not preserved, so prefer external documentation over inline TOML comments in this file.

If you have an existing `~/.codex/mcp.json` from an older gaal release, Codex never read it. `gaal sync` prints a one-shot warning when it sees the file; it's safe to delete by hand.

## Common targets

The destination file is resolved from the agent registry. For reference:
Expand All @@ -156,7 +184,7 @@ The destination file is resolved from the agent registry. For reference:
|-------|----------------------------------|
| Claude Code / Claude Desktop | `~/.config/claude/claude_desktop_config.json` |
| Cursor | `~/.cursor/mcp.json` |
| Codex | `~/.codex/mcp.json` |
| Codex | `~/.codex/config.toml` |
| Windsurf | `~/.codeium/windsurf/mcp_settings.json` |
| Continue | `~/.continue/config.json` |
| VS Code (Copilot, Cline, Roo) | `~/.vscode/settings.json` |
Expand Down
2 changes: 1 addition & 1 deletion content/how-gaal-works.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ gaal is a small, opinionated state engine. Once you understand the four moving p
Every gaal command runs the same four-stage pipeline.

1. **gaal.yaml, the source of truth.** You write it (or generate it with `gaal init`) and commit it to your dotfiles. Everything downstream reads from this file.
2. **Audit, read the filesystem.** gaal scans the local filesystem to discover what's already there: every installed agent, every skill on disk, every MCP entry in every agent's JSON config. Audit writes nothing.
2. **Audit, read the filesystem.** gaal scans the local filesystem to discover what's already there: every installed agent, every skill on disk, every MCP entry in every agent's config (JSON for most, TOML for Codex). Audit writes nothing.
3. **Plan, diff desired vs actual.** gaal compares the desired state from `gaal.yaml` against the actual state from the audit and computes the work to do. `gaal sync --dry-run` stops at this stage and prints the plan.
4. **Sync, apply through renderers.** The plan executes through per-agent **renderers**, small adapters that know each agent's native file layout and config format. They write to `.claude/skills/*`, `.cursor/mcp.json`, `~/.codex/skills/*`, and so on.

Expand Down
12 changes: 6 additions & 6 deletions content/schema/mcps.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: "mcps"
description: "MCP server entries upserted into agent JSON configs."
description: "MCP server entries upserted into agent config files."
---

```yaml
Expand Down Expand Up @@ -33,7 +33,7 @@ No. Omit `mcps:` if you don't manage any.
|-------|------|----------|
| `name` | string | yes |

The identifier the entry is upserted under inside the target's `mcpServers` object.
The identifier the entry is upserted under: as a key inside the target's `mcpServers` object for JSON files, or as the `[mcp_servers.<name>]` table for TOML files (Codex).

### `agents`

Expand All @@ -45,7 +45,7 @@ Either a list of registry names (`[claude-code, cursor, codex]`) or the wildcard

`["*"]` resolves at sync time to every registered agent that has a non-empty MCP config for the requested scope. Agents whose registry entry has no MCP config for that scope are skipped silently.

The destination JSON file is then looked up in the [agent registry](/agents/overview) — `global_mcp_config_file` for `global: true`, `project_mcp_config_file` for `global: false`.
The destination file is then looked up in the [agent registry](/agents/overview) — `global_mcp_config_file` for `global: true`, `project_mcp_config_file` for `global: false`. The format is picked from the resolved file's extension: `.json` for most agents, `.toml` for Codex.

### `global`

Expand Down Expand Up @@ -142,9 +142,9 @@ mcps:

## Behaviour

- gaal upserts under the `mcpServers` object of every resolved target file.
- Other top-level keys in the target are preserved.
- Other entries inside `mcpServers` that gaal didn't author are preserved.
- gaal upserts under the `mcpServers` object of every resolved JSON target, or under `[mcp_servers.<name>]` tables for TOML targets (Codex).
- Other top-level keys in the target are preserved. TOML targets round-trip through a generic map, so unrelated tables (`[analytics]`, `[features]`, …) survive every write; comments are not preserved.
- Other MCP entries that gaal didn't author are preserved.
- Removing an entry from `gaal.yaml` does **not** remove it from the target unless you pass `--prune`.
- gaal does **not** expand `${VAR}` references in `args:` or `env:`. The agent process resolves them when launching the server.
- When the resolved target's parent directory is missing, gaal silently skips the entry — it never creates an agent's config directory as a side effect.
Expand Down