#3201·devenv

Codex CLI integration: generate .codex/config.toml (MCP servers, hooks) like claude.code and opencode

Author: onnimonniCreated Sep 21, 2026Updated Sep 21, 2026

Motivation

devenv ships claude.code and opencode modules that turn Nix options into the agent's project files (.mcp.json, .claude/settings.json, opencode.json). There is no equivalent for OpenAI Codex CLI, so a project that supports both agents keeps a generated .mcp.json and a hand-written .codex/config.toml with the same MCP server, and they drift.

Concrete case: a Phoenix project where claude.code.mcpServers.tidewave.url is derived from the port devenv allocates for the phoenix process, while .codex/config.toml still hard-codes it:

toml
[mcp_servers.tidewave]
url = "http://127.0.0.1:4000/tidewave/mcp"

What Codex reads

  • .codex/config.toml in the project (walked from cwd to the project root, applied when the project is trusted). MCP servers live under [mcp_servers.<name>] with either command/args/env (stdio) or url/http_headers (streamable HTTP), plus enabled, required, startup_timeout_sec, tool_timeout_sec.
  • .codex/hooks.json in the same folder for hooks (Stop, tool-use events; the JSON payload on stdin includes cwd).
  • AGENTS.md for instructions, and prompt/skill directories under .codex/ and .agents/.

Proposal

A codex module in src/modules/integrations/ following the claude.code shape:

nix
{
  codex = {
    enable = true;
    mcpServers.tidewave = {
      type = "http";
      url = "http://127.0.0.1:${toString config.processes.phoenix.ports.http.value}/tidewave/mcp";
    };
    hooks.git-hooks-run.enable = true;  # default when git-hooks.enable, as in claude.code
    settings = { };                      # freeform, merged into config.toml
  };
}

generating .codex/config.toml (and .codex/hooks.json when hooks are set) through files, so they are store symlinks refreshed on shell entry like .pre-commit-config.yaml.

The mcpServers submodule in claude.nix (type, command, args, env, url, headers) already matches Codex's fields one to one, so it could be shared between the two modules rather than duplicated. The git-hooks-run hook would map to a Codex Stop or post-tool hook running prek run.

Happy to test a branch against a real project (Phoenix + Tidewave MCP over HTTP, devenv 2.3.1 on macOS).