Skip to content

Commit b3e700c

Browse files
committed
Merge feat/mcp-bridging: MCP server bridging with hardened reconciliation
Bridges Claude Code MCP server definitions into Codex config.toml. Supports stdio and HTTP transports, multi-project global ownership, degraded discovery preservation, credential detection, and exclusions. Iterative review (9 rounds, 14 fixes) hardened: scope-gated config reads, disk presence verification, type validation at discovery and translation, symlink containment for project configs, uninstall error handling, and first-time adoption of existing bridge-managed entries.
2 parents 679065b + 388cb60 commit b3e700c

33 files changed

Lines changed: 5449 additions & 128 deletions

AGENTS.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ claude-code-codex-bridge/
2626
│ └── workflows/
2727
├── docs/
2828
│ ├── agent-skills-standard.md
29-
│ └── codex-cli-reference.md
29+
│ ├── claude-code-mcp-reference.md
30+
│ ├── codex-cli-reference.md
31+
│ └── mcp-bridge-mapping.md
3032
├── src/
3133
│ └── cc_codex_bridge/
3234
│ ├── __main__.py
@@ -119,8 +121,10 @@ The bridge translates between two ecosystems. Authoritative reference documents
119121

120122
- `docs/agent-skills-standard.md` — the open Agent Skills standard (from [agentskills.io](https://agentskills.io/)): skill directory structure, `SKILL.md` format, frontmatter fields, progressive disclosure, client implementation contract, and script conventions.
121123
- `docs/codex-cli-reference.md` — Codex CLI specifics (from [developers.openai.com/codex](https://developers.openai.com/codex/) and the [Codex source](https://github.com/openai/codex)): instructions discovery (`AGENTS.md`), skill discovery hierarchy, agent role configuration, agent file auto-discovery, and Claude Code vs Codex comparison.
124+
- `docs/claude-code-mcp-reference.md` — Claude Code MCP server configuration format: `~/.claude.json` structure, per-project scoping, `.mcp.json` project-shared format, stdio/HTTP/SSE transports, and `headersHelper`/`oauth` fields.
125+
- `docs/mcp-bridge-mapping.md` — MCP bridge mapping rules: how each CC MCP field maps to Codex `config.toml` format, transport-specific translation, bearer token extraction, and unsupported feature handling.
122126

123-
Consult these before making design decisions that depend on how Codex discovers skills, loads instructions, or defines agent roles.
127+
Consult these before making design decisions that depend on how Codex discovers skills, loads instructions, defines agent roles, or configures MCP servers.
124128

125129
## Canonical Architecture
126130

@@ -150,7 +154,7 @@ Also check `DESIGN.md` for the canonical description of the current implemented
150154
- Review `.claude/docs/analysis/` for prior investigations and technical findings.
151155
- Review spec documents in `.claude/docs/plans/`, such as `*-spec.md`, for the intended contract and constraints.
152156
- Review implementation plans in `.claude/docs/plans/`, such as `*-implementation-plan.md`, for sequencing, scope, and expected milestones.
153-
- Consult `docs/agent-skills-standard.md` and `docs/codex-cli-reference.md` for the authoritative domain references that the bridge targets. These supersede earlier research notes in `.claude/docs/research/`.
157+
- Consult `docs/agent-skills-standard.md`, `docs/codex-cli-reference.md`, `docs/claude-code-mcp-reference.md`, and `docs/mcp-bridge-mapping.md` for the authoritative domain references that the bridge targets. These supersede earlier research notes in `.claude/docs/research/`.
154158
- Update `DESIGN.md` whenever the implemented architecture changes materially.
155159
- Update the relevant analysis, spec, or plan docs when the command surface, architecture, or implementation direction changes materially.
156160

CHANGELOG.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,45 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
66

77
## [Unreleased]
88

9+
### Added
10+
11+
- MCP server bridging: Claude Code MCP server definitions are now discovered,
12+
translated, and written to Codex's `config.toml`. Supports stdio and HTTP
13+
transports. Global servers write to `~/.codex/config.toml`, project servers
14+
to `.codex/config.toml`. Ownership tracked with drift detection.
15+
- MCP servers visible in `status` and `reconcile` output.
16+
- MCP server exclusion support via `bridge.toml` `mcp_servers` list,
17+
`--exclude-mcp-server` CLI flag, and `config exclude add mcp_server:<name>`.
18+
- Clean and uninstall remove bridge-owned MCP entries from config.toml files.
19+
- New runtime dependency: `tomlkit` for round-trip TOML editing.
20+
- New reference docs: `docs/claude-code-mcp-reference.md` and
21+
`docs/mcp-bridge-mapping.md`.
22+
- Degraded MCP discovery: corrupt `~/.claude.json` or `.mcp.json` files no
23+
longer trigger removal of previously-bridged MCP entries. The bridge warns
24+
and preserves existing state until the file is fixed.
25+
26+
### Fixed
27+
28+
- MCP reconcile now verifies on-disk presence of bridge-owned entries in
29+
`config.toml`, not just stored content hashes — externally deleted entries
30+
are restored on the next run.
31+
- MCP planning scope-gates `config.toml` reads: a corrupt global config no
32+
longer blocks project-only MCP sync, and vice versa.
33+
- First-time adoption of an existing bridge-managed global MCP server now
34+
updates `config.toml` when the definition changed, instead of silently
35+
skipping the entry.
36+
- Unreadable MCP config files (`PermissionError`, I/O failures) and non-dict
37+
JSON roots now trigger degraded discovery instead of being treated as empty.
38+
- MCP type validation: `command` and `url` must be strings at discovery;
39+
`args` must be a list, `env` must be a dict, and header values must be
40+
strings at translation — non-conforming values are skipped instead of
41+
crashing.
42+
- `config exclude add/remove` CLI now accepts `mcp_server` as an entity kind.
43+
- `uninstall_all` catches `ValueError` and `OSError` during MCP config
44+
cleanup, preventing one corrupt project from aborting the entire uninstall.
45+
- Project `.codex/config.toml` paths are now containment-checked against the
46+
project root, preventing writes through symlinked `.codex/` directories.
47+
948
## [1.1.0] - 2026-03-28
1049

1150
### Added

DESIGN.md

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ These are authoritative inputs:
4242
- project-level Claude skills from `.claude/skills/`
4343
- project-level Claude agents from `.claude/agents/`
4444
- user-level global instructions from `~/.claude/CLAUDE.md`
45+
- MCP server definitions from `~/.claude.json` (user-global and per-project scopes)
46+
- MCP server definitions from `<project>/.mcp.json` (project-shared scope)
4547

4648
### Generated outputs
4749

@@ -56,6 +58,8 @@ These are derived artifacts and must not become hand-maintained sources:
5658
- `~/.cc-codex-bridge/registry.json`
5759
- `~/.cc-codex-bridge/plugins/<marketplace>-<plugin>/`
5860
- `~/.cc-codex-bridge/logs/YYYY-MM-DD.jsonl` (daily activity logs)
61+
- `~/.codex/config.toml` `[mcp_servers.*]` entries (bridge-owned only)
62+
- `<project>/.codex/config.toml` `[mcp_servers.*]` entries (bridge-owned only)
5963

6064
### Authority rule
6165

@@ -79,7 +83,10 @@ If a behavior is described differently in multiple docs:
7983
- translation of Claude agents into self-contained Codex agent `.toml` files
8084
- translation of Claude skills into self-contained Codex skills
8185
- translation of Claude commands into native Codex prompt files with plugin resource vendoring
82-
- safe reconcile of generated project files, generated Codex agent files, and generated Codex skill directories
86+
- discovery of MCP server definitions from `~/.claude.json` and `.mcp.json`
87+
- translation of MCP server configs (stdio and HTTP) into Codex `config.toml` format
88+
- surgical editing of `config.toml` files with `tomlkit` (round-trip preserving)
89+
- safe reconcile of generated project files, generated Codex agent files, generated Codex skill directories, and MCP server config entries
8390
- state tracking for generator-owned outputs
8491
- project-level artifact cleanup via `clean`
8592
- machine-level full artifact removal via `uninstall`
@@ -108,8 +115,9 @@ The runtime is a deterministic pipeline:
108115
4. choose the highest semantic version for each `<marketplace>/<plugin>` and filter to only enabled plugins
109116
5. discover user-level skills, agents, and global instructions from `~/.claude/` (or `--claude-home`)
110117
6. discover project-level skills and agents from `.claude/`
118+
6b. discover MCP server definitions from `~/.claude.json` (user-global and per-project) and `.mcp.json` (project-shared)
111119
7. load optional `.codex/bridge.toml` exclusions and merge any CLI exclusion flags
112-
8. filter discovered plugins/skills/agents by the effective exclusion set
120+
8. filter discovered plugins/skills/agents/MCP servers by the effective exclusion set
113121
9. translate plugin agents into `GeneratedAgentFile` objects
114122
10. translate standalone user and project agents into `GeneratedAgentFile` objects
115123
11. translate plugin and user skills into `GeneratedSkill` trees (global registry)
@@ -118,9 +126,11 @@ The runtime is a deterministic pipeline:
118126
14. translate standalone user commands into `GeneratedPrompt` objects (global prompts)
119127
15. translate project commands into `GeneratedPrompt` objects (global prompts) with `--<project-dirname>` filename suffix
120128
16. merge all agents, render project-local agent `.toml` files to `.codex/agents/`, and collect global agents for `~/.codex/agents/`
121-
17. decide whether `CLAUDE.md` can be created or preserved as an `@AGENTS.md` shim
122-
18. build a full desired state for project files, Codex skill directories, global agent files, and global instructions
123-
19. inspect/preview or reconcile that desired state with ownership and rollback protections
129+
17. translate discovered MCP servers into `GeneratedMcpServer` objects with Codex-compatible TOML tables
130+
18. decide whether `CLAUDE.md` can be created or preserved as an `@AGENTS.md` shim
131+
19. build a full desired state for project files, Codex skill directories, global agent files, global instructions, and MCP server entries
132+
20. inspect/preview or reconcile that desired state with ownership and rollback protections
133+
21. for MCP servers: surgically edit `~/.codex/config.toml` (global) and `<project>/.codex/config.toml` (project) using `tomlkit`, preserving user-authored content
124134

125135
The reconcile pipeline is shared by `status` and `reconcile`.
126136

@@ -200,13 +210,15 @@ The project state file (at `~/.cc-codex-bridge/projects/<hash>/state.json`) reco
200210
- bridge home path
201211
- managed project files as a path-to-content-hash mapping (including `CLAUDE.md`, `AGENTS.md`, and agent `.toml` files under `.codex/agents/`)
202212
- managed project skill directory names (tracked separately for directory-snapshot comparison)
203-
- state version (currently 9; v8 state files are migrated on read, empty migrated hashes are treated as preserve-only until a trusted hash is recorded, and the next reconcile backfills hashes for preserved or retained managed files from on-disk content when possible)
213+
- managed MCP server names as a name-to-content-hash mapping (for project-scope MCP servers in `.codex/config.toml`)
214+
- state version (currently 11; v8–v10 state files are migrated on read)
204215

205216
The global registry records:
206217

207218
- generated skill install directory names with content hashes and owning project roots
208219
- generated agent `.toml` filenames with content hashes and owning project roots
209220
- vendored plugin resource directory names with content hashes and owning project roots
221+
- bridge-owned global MCP server names with content hashes and owning project roots
210222
- a sorted list of all reconciled project roots (the `projects` list)
211223

212224
### Safety rules
@@ -243,6 +255,13 @@ The global registry records:
243255
- stale managed outputs are removed when no longer desired
244256
- cleanup uses the codex_home recorded in bridge state, not the caller-supplied value, to ensure registry operations target the correct global state
245257
- project-local generated skill directories are removed as whole directories, not individual files, consistent with global skill directory ownership
258+
- MCP entries in `config.toml` are ownership-aware: only bridge-owned entries (tracked in state or registry) are modified; user-authored entries with the same name are never touched or adopted
259+
- user-authored MCP entries that collide with bridge-discovered servers are skipped during apply and excluded from state/registry tracking — `clean` will not remove them
260+
- when MCP discovery is degraded (config file exists but contains malformed JSON), stale-entry removal is suppressed — previously-bridged entries survive until the file is fixed
261+
262+
#### Known limitation: multi-project global MCP update on first reconcile
263+
264+
When project B first encounters a global MCP server already created by project A with different content, B adds itself as a co-owner in the registry but cannot update `config.toml` because the entry is not in B's `owned` set. The registry records B's desired hash while the disk retains A's content. This self-heals on B's second reconcile (B is then in `previously_owned_global`) or on A's next reconcile. This is accepted because MCP servers overwhelmingly come from plugins with identical configs across projects — the scenario of two projects wanting different content for the same global MCP server name is not realistic in practice. Do not attempt to fix this without a concrete real-world use case that demonstrates the need.
246265

247266
## 7. Discovery Architecture
248267

@@ -286,6 +305,22 @@ Discovery lives in `src/cc_codex_bridge/discover.py`.
286305
- project-level commands are discovered from `<project>/.claude/commands/` as `*.md` files
287306
- command discovery follows the same pattern as agent discovery (top-level `.md` files in a directory)
288307

308+
### MCP server discovery
309+
310+
`src/cc_codex_bridge/discover_mcp.py` reads MCP server definitions from Claude Code configuration files.
311+
312+
Sources (in precedence order, highest first):
313+
314+
1. **Project-local**: `~/.claude.json``projects.<project_root>.mcpServers` → scope `project`
315+
2. **Project-shared**: `<project>/.mcp.json``mcpServers` → scope `project`
316+
3. **User-global**: `~/.claude.json` → top-level `mcpServers` → scope `global`
317+
318+
When the same server name appears at multiple scopes, highest precedence wins. SSE transport servers are skipped (unsupported in Codex). Plugin-provided MCP servers (using `${CLAUDE_PLUGIN_ROOT}`) are not discovered — standalone equivalents cover them.
319+
320+
Transport detection: `command` field → stdio; `type: "http"` or `url` field → HTTP.
321+
322+
When a source file exists but contains malformed JSON, discovery is *degraded*: servers from other valid sources are still discovered, but the `mcp_discovery_degraded` flag is set on the `DiscoveryResult`. The reconcile pipeline uses this flag to suppress stale-entry removal — previously-bridged MCP entries survive until the corrupt file is fixed and a subsequent reconcile runs normally.
323+
289324
### Plugin enablement
290325

291326
- the `claude` CLI is a hard runtime dependency for the bridge
@@ -570,6 +605,28 @@ Agent references (`plugin:agent-name`) are not rewritten because agent invocatio
570605

571606
User-level and plugin skills are installed to the global Codex skill registry at `~/.codex/skills/`. Project-level skills are installed to project-local `.codex/skills/` directories and tracked as managed project skill directory names in the bridge state. Both global and project skills use exact directory-snapshot comparison for change detection.
572607

608+
### 8.6 MCP server translation
609+
610+
`src/cc_codex_bridge/translate_mcp.py` converts `DiscoveredMcpServer` objects into `GeneratedMcpServer` objects.
611+
612+
stdio mapping: `command``command`, `args``args`, `env``env` (omitted when empty). The CC `type` field is stripped (Codex infers transport from field presence).
613+
614+
HTTP mapping: `url``url`, `headers``http_headers`. Special case: `Authorization: "Bearer ${VAR}"` headers are extracted into `bearer_token_env_var` and the header is removed.
615+
616+
Diagnostics (warnings, not errors): `headersHelper` (no Codex equivalent), `oauth` (user must run `codex mcp login`).
617+
618+
### 8.7 MCP TOML editing
619+
620+
`src/cc_codex_bridge/toml_config.py` provides surgical editing of Codex `config.toml` files using `tomlkit` for round-trip preservation of comments and formatting.
621+
622+
Key functions:
623+
- `read_codex_config(path)` → parse or return empty doc
624+
- `write_codex_config(path, doc)` → atomic write (temp + rename); removes empty files
625+
- `apply_mcp_changes(doc, desired, owned)` → add/update/remove bridge-owned `[mcp_servers.*]` entries
626+
- `hash_mcp_server_table(table_dict)` → deterministic `sha256:` content hash
627+
628+
The editing is ownership-aware: only entries tracked in the bridge registry or state are modified. User-authored MCP entries are never touched.
629+
573630
## 9. Reconcile Architecture
574631

575632
Reconcile lives in `src/cc_codex_bridge/reconcile.py`.
@@ -910,7 +967,7 @@ Current runtime module responsibilities:
910967
- `config_check.py`
911968
- config validation: TOML well-formedness, scan path resolution, unknown key detection, global-only key rejection for project configs
912969
- `config_exclude_commands.py`
913-
- config exclude subcommand handlers: discovery-backed add/remove/list for plugin/skill/agent/command exclusions
970+
- config exclude subcommand handlers: discovery-backed add/remove/list for plugin/skill/agent/command/mcp_server exclusions
914971
- `config_scan_commands.py`
915972
- config scan subcommand handlers: glob-validated add/remove/list for scan paths
916973
- `config_scope.py`
@@ -1009,6 +1066,9 @@ These are current implemented simplifications, not necessarily permanent design
10091066
- exclusion ids are exact-match identifiers, not wildcard/glob patterns
10101067
- commands are translated to native Codex prompt files (`~/.codex/prompts/`) rather than Codex skills, avoiding namespace collisions with the skill directory entirely
10111068
- LaunchAgent scheduling with automatic `launchctl bootstrap/bootout` is supported; watcher mode is not
1069+
- MCP planning assumes `mcp_servers` is a TOML table when the document parses successfully — a hand-crafted scalar value (`mcp_servers = "oops"`) would pass TOML validation but crash during apply, leaving the registry inconsistent
1070+
- MCP translation does not remap Claude `${VAR}` env-var references in non-Authorization fields — Codex may treat these as literal strings rather than expanding them at runtime; the bridge copies the syntax verbatim and relies on the host shell or Codex runtime to resolve them
1071+
- MCP server names must match `[A-Za-z0-9_-]`; servers with dots, spaces, or other characters are skipped with a diagnostic — this is stricter than TOML's quoted-key support but required for registry key safety and Codex `mcp__<server>__<tool>` naming
10121072

10131073
Any change to these constraints should update this file.
10141074

0 commit comments

Comments
 (0)