diff --git a/content/agents/codex.mdx b/content/agents/codex.mdx index 8e558b4..adf3e85 100644 --- a/content/agents/codex.mdx +++ b/content/agents/codex.mdx @@ -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` | @@ -26,6 +26,8 @@ skills: ## MCP example +Codex reads MCP servers from `~/.codex/config.toml` under `[mcp_servers.]` 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 @@ -36,6 +38,10 @@ mcps: args: [mcp-server-filesystem, ~/projects] ``` + + 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. + + ## Related diff --git a/content/agents/overview.mdx b/content/agents/overview.mdx index 1adb963..343d28d 100644 --- a/content/agents/overview.mdx +++ b/content/agents/overview.mdx @@ -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` | diff --git a/content/cli/agents.mdx b/content/cli/agents.mdx index eafebe2..0b13d2a 100644 --- a/content/cli/agents.mdx +++ b/content/cli/agents.mdx @@ -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 … diff --git a/content/concepts/agents-and-renderers.mdx b/content/concepts/agents-and-renderers.mdx index d34cd13..debecaa 100644 --- a/content/concepts/agents-and-renderers.mdx +++ b/content/concepts/agents-and-renderers.mdx @@ -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 … ``` @@ -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.]` (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. diff --git a/content/concepts/mcp-servers.mdx b/content/concepts/mcp-servers.mdx index bfdbbeb..7a11b70 100644 --- a/content/concepts/mcp-servers.mdx +++ b/content/concepts/mcp-servers.mdx @@ -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.]` 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 @@ -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: @@ -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.]` (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:` diff --git a/content/configure/mcp-servers.mdx b/content/configure/mcp-servers.mdx index ef74193..9752386 100644 --- a/content/configure/mcp-servers.mdx +++ b/content/configure/mcp-servers.mdx @@ -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 @@ -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.]` 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: @@ -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` | diff --git a/content/how-gaal-works.mdx b/content/how-gaal-works.mdx index 3ba33a1..7f13ecd 100644 --- a/content/how-gaal-works.mdx +++ b/content/how-gaal-works.mdx @@ -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. diff --git a/content/schema/mcps.mdx b/content/schema/mcps.mdx index 8a9181a..08f3ca0 100644 --- a/content/schema/mcps.mdx +++ b/content/schema/mcps.mdx @@ -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 @@ -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.]` table for TOML files (Codex). ### `agents` @@ -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` @@ -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.]` 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.