[DRAFT] # PR: Support Repository-based MCP Plugins & Options in Codex CLI Generator#460
[DRAFT] # PR: Support Repository-based MCP Plugins & Options in Codex CLI Generator#460silviojr wants to merge 1 commit into
Conversation
ee331f1 to
b2f7472
Compare
b2f7472 to
4858b08
Compare
IsmailMehdi
left a comment
There was a problem hiding this comment.
Leaving the three blocker-level items inline. Full review posted separately (subprocess env / TOML escaping / implicit skills-config behavior change). Non-blocking items are in that comment, not repeated here.
| # Install the plugin via Codex CLI so it changes status from 'not installed' to 'installed, enabled' | ||
| cmd = ["npm", "exec", "--yes", self.codex_cli_version, "--", "plugin", "add", f"{plugin_name}@{marketplace_name}"] | ||
| try: | ||
| result = subprocess.run(cmd, env=self.env, check=False, capture_output=True, text=True) |
There was a problem hiding this comment.
Blocker. env=self.env will make this fail to find npm.
subprocess.run(env=...) replaces the process environment; it does not merge. self.env is only populated with HOME, CODEX_HOME, and (optionally) OPENAI_API_KEY — no PATH. So npm isn't resolvable and this exits 127 / FileNotFoundError, and the plugin never transitions to installed, enabled (which is the whole point of adding this subprocess).
The other subprocess call sites in this file already do the right thing — _setup_skills and _install_mcp_servers_from_repo both build:
setup_env = os.environ.copy()
setup_env.update(self.env)and pass that in. Use the same pattern here (either recompute locally or take setup_env as a parameter from the callers that already have it).
| lines.append("") | ||
|
|
||
| for plugin_id, options in self.enabled_plugins.items(): | ||
| lines.append(f'[plugins."{plugin_id}"]') |
There was a problem hiding this comment.
Should fix before merge. plugin_id isn't TOML-escaped, which produces invalid TOML if plugin_name or marketplace_name ever contains a " or \. It also opens a small TOML-injection surface on user-authored configs (e.g. a plugin_name of foo"] evil="bar would break out of the section).
_toml_key (line 622) already handles this exact case — it json.dumps keys that aren't bare-key-safe, and @ isn't bare-key-safe, so it round-trips dak@evalbench-local-marketplace to "dak@evalbench-local-marketplace" unchanged. Just reuse it:
lines.append(f"[plugins.{self._toml_key(plugin_id)}]")| plugin_name = os.path.basename(os.path.abspath(repo_dir)) | ||
| marketplace_name = skill_config.get("marketplace_name", "evalbench-local-marketplace") | ||
| plugin_id = f"{plugin_name}@{marketplace_name}" | ||
| self.enabled_plugins.setdefault(plugin_id, {}).update(skill_config.get("config", {}) or {}) |
There was a problem hiding this comment.
Should fix before merge — or at minimum verify. This is a silent behavior change for every existing skills: config that uses action: install_from_repo.
Before this PR, cloning a skill repo only registered it in the marketplace and copied SKILL folders — nothing was ever added to config.toml. After this PR, every such repo also gets a [plugins."<name>@evalbench-local-marketplace"] section with enabled = true written to config.toml (via self.enabled_plugins).
Whether that breaks anything depends on how Codex CLI handles an enabled = true on a plugin with no runtime component. Two ways forward:
- Confirm against an existing skills-only config that Codex is tolerant, and add a one-line comment here explaining why the skills and MCP paths deliberately share the enable-in-config behavior.
- If it's not safe / not intended, scope the
enabled_plugins.setdefault(...)call to_install_mcp_servers_from_repoonly and leave_setup_skillsdoing what it did before.
Either is fine; the current state (silent change with no comment) is the risky option.
This PR extends the Codex CLI generator to support cloning plugin repositories containing bundled MCP servers (such as the Data Agent Kit), auto-enabling them in
config.toml, and passing configuration parameters (userConfig) to them.1. Refactored Configuration Serialization
config.tomlwas written immediately on initialization with only inlinemcp_serversconfiguration. This prevented adding plugins cloned dynamically during setup.CodexCliGeneratorto collect configurations (inline servers, repository-based servers, and cloned skills/plugins) into internal state dictionaries (self.inline_mcp_serversandself.enabled_plugins) first, and deferred writing theconfig.tomlfile to the end of the_setupmethod.2. Supported List-Based
mcp_serversRepositoriesClaudeCodeGeneratorto maintain cross-agent testing consistency._install_mcp_servers_from_repowhich clones the plugin repository, registers it inmarketplace.json, and records its ID and parameters in the enabled plugins list.3. Enabled Plugins in
config.tomlmarketplace.jsonmust be explicitly marked as enabled inconfig.tomlto be loaded by Codex CLI.[plugins."<plugin-id>"]sections withenabled = trueandoptionsinsideconfig.toml. Theplugin_idis constructed using theplugin-name@marketplace-namepattern.4. Supported Plugin Options & Variables (
userConfig)${user_config.PROJECT_ID}).configblocks from the YAML settings, passed them toself.enabled_plugins, and serialized them as inline TOML tables underoptions = { ... }.Testing & Verification
1. Test Scenario Setup
We executed the evaluation pipeline using the
Bullseye_PCAscenario:Specifies cloning and loading the
dakplugin from its repository:Requests creating a notebook and running a sample BigQuery query.
2. Sandbox Verification
After executing the setup sequence, we inspected the sandboxed directory
.venv/fake_home_codexand verified:.agents/plugins/marketplace.jsonwas correctly created, pointing to the local cloned repository with the relative path starting with./(e.g."path": "./.codex/plugins/data-agent-kit-starter-pack").codex plugin listin the sandbox confirmed the plugin status was successfully transitioned to:dak@evalbench-local-marketplace | installed, enableddeveloping-with-bigquery,notebook-guidance, etc.) were successfully copied to.codex/skills/.3. Execution Verification
We executed the run and examined the outputs in
results/aadf29ef-3145-4d8e-aca1-9e9bd09b58a9:notebook__create_notebook(succeeded)notebook__list_cells(succeeded)notebook__insert_cell(succeeded, twice)