Skip to content

feat(codex): add project-scoped MCP and user target support#803

Open
Nickolaus wants to merge 2 commits intomicrosoft:mainfrom
Nickolaus:feature/codex-project-scoped-mcp
Open

feat(codex): add project-scoped MCP and user target support#803
Nickolaus wants to merge 2 commits intomicrosoft:mainfrom
Nickolaus:feature/codex-project-scoped-mcp

Conversation

@Nickolaus
Copy link
Copy Markdown

Description

Make Codex MCP config project-scoped for repo installs by writing to .codex/config.toml instead of ~/.codex/config.toml, and only configure Codex MCP when Codex is an active project target.

Also add Codex user-scope support for installed primitives so agents, hooks, and skills follow the existing target and scope model used by the other supported tools.

Fixes #502

Type of change

  • Bug fix
  • New feature
  • Documentation
  • Maintenance / refactor

Testing

  • Tested locally
  • All existing tests pass
  • Added tests for new functionality (if applicable)

@Nickolaus Nickolaus marked this pull request as ready for review April 21, 2026 08:04
Copilot AI review requested due to automatic review settings April 21, 2026 08:04
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates APM's Codex integration to support project-scoped MCP configuration (writing to .codex/config.toml for project installs) and adds Codex participation in the existing user-scope target/scope model.

Changes:

  • Make Codex MCP config scope-aware (project .codex/config.toml vs user ~/.codex/config.toml) and avoid configuring Codex MCP unless Codex is an active project target.
  • Add scope-aware project_root/user_scope plumbing through MCP client creation and MCP install/uninstall flows.
  • Expand unit/integration tests and update Starlight docs to reflect the new Codex MCP scoping behavior.

Reviewed changes

Copilot reviewed 23 out of 23 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/apm_cli/integration/mcp_integrator.py Adds scope parameters and filters Codex MCP configuration to active project targets.
src/apm_cli/adapters/client/base.py Introduces project_root/user_scope context and placeholder normalization helper.
src/apm_cli/adapters/client/codex.py Resolves Codex config path by scope and normalizes project placeholders in args.
src/apm_cli/factory.py Passes project_root/user_scope into client adapter construction.
src/apm_cli/core/safe_installer.py Creates adapters with scope context for conflict-aware installs.
src/apm_cli/core/operations.py Threads scope context through configure/install/uninstall helpers.
src/apm_cli/registry/operations.py Checks installed server IDs using scope-aware adapter config resolution.
src/apm_cli/integration/targets.py Marks Codex as partially user-scope supported.
src/apm_cli/commands/install.py Passes scope context into MCP install + stale cleanup.
src/apm_cli/commands/uninstall/cli.py / engine.py Passes scope context into MCP stale cleanup on uninstall.
src/apm_cli/adapters/client/{vscode,cursor,opencode,copilot}.py Switches workspace-root resolution to self.project_root.
tests/unit/test_transitive_mcp.py Adds Codex project-scoped MCP behavior tests (install + stale cleanup).
tests/unit/test_mcp_client_factory.py Validates Codex config-path scoping + placeholder normalization.
tests/unit/integration/test_scope_*.py Updates/extends target resolution and install/uninstall scope tests for Codex.
tests/unit/integration/test_hook_integrator.py Verifies Codex hooks merge target respects scope-resolved root dir.
docs/src/content/docs/integrations/ide-tool-integration.md Documents Codex MCP config paths and project-target gating.
docs/src/content/docs/reference/cli-commands.md Clarifies global vs project Codex configuration behavior.
Comments suppressed due to low confidence (4)

src/apm_cli/registry/operations.py:36

  • The method signature now accepts project_root and user_scope, but the docstring's Args section doesn't document them. Please update the docstring to describe how scope/root affect which config files are inspected when checking installation status.
    def check_servers_needing_installation(self, target_runtimes: List[str], server_references: List[str], project_root=None, user_scope: bool = False) -> List[str]:
        """Check which MCP servers actually need installation across target runtimes.
        
        This method checks the actual MCP configuration files to see which servers
        are already installed by comparing server IDs (UUIDs), not names.
        
        Args:
            target_runtimes: List of target runtimes to check
            server_references: List of MCP server references (names or IDs)
            

src/apm_cli/core/operations.py:13

  • The function signature now includes project_root and user_scope, but the docstring doesn't document these parameters. Please update the Args section so callers understand which config scope/path will be written.
def configure_client(client_type, config_updates, project_root=None, user_scope=False):
    """Configure an MCP client.
    
    Args:
        client_type (str): Type of client to configure.
        config_updates (dict): Configuration updates to apply.
    

src/apm_cli/core/operations.py:40

  • The function signature now includes project_root and user_scope, but these parameters are missing from the docstring Args section. Document them so it's clear which project/user config is being targeted during installation.
def install_package(client_type, package_name, version=None, shared_env_vars=None, server_info_cache=None, shared_runtime_vars=None, project_root=None, user_scope=False):
    """Install an MCP package for a specific client type.
    
    Args:
        client_type (str): Type of client to configure.
        package_name (str): Name of the package to install.
        version (str, optional): Version of the package to install.
        shared_env_vars (dict, optional): Pre-collected environment variables to use.
        server_info_cache (dict, optional): Pre-fetched server info to avoid duplicate registry calls.
        shared_runtime_vars (dict, optional): Pre-collected runtime variables to use.
    

src/apm_cli/core/operations.py:86

  • The function signature now includes project_root and user_scope, but the docstring Args section doesn't mention them. Please document how they affect which config file is edited during uninstall.
def uninstall_package(client_type, package_name, project_root=None, user_scope=False):
    """Uninstall an MCP package.
    
    Args:
        client_type (str): Type of client to configure.
        package_name (str): Name of the package to uninstall.
    

Comment thread src/apm_cli/adapters/client/base.py
@Nickolaus Nickolaus force-pushed the feature/codex-project-scoped-mcp branch from 1070d52 to 0d0328b Compare April 21, 2026 08:16
@Nickolaus Nickolaus requested a review from Copilot April 21, 2026 08:16
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 2 comments.

Comment thread src/apm_cli/integration/mcp_integrator.py Outdated
Comment thread src/apm_cli/registry/operations.py Outdated
@Nickolaus Nickolaus force-pushed the feature/codex-project-scoped-mcp branch from 0d0328b to 8df3621 Compare April 21, 2026 08:30
@Nickolaus Nickolaus requested a review from Copilot April 21, 2026 08:31
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.

Comment thread src/apm_cli/integration/mcp_integrator.py
Comment thread src/apm_cli/adapters/client/vscode.py Outdated
@Nickolaus Nickolaus force-pushed the feature/codex-project-scoped-mcp branch from 8df3621 to b31b2af Compare April 21, 2026 08:43
@Nickolaus Nickolaus requested a review from Copilot April 21, 2026 08:43
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated 2 comments.

Comment thread src/apm_cli/integration/mcp_integrator.py
Comment thread src/apm_cli/adapters/client/opencode.py
@Nickolaus Nickolaus force-pushed the feature/codex-project-scoped-mcp branch from b31b2af to 84b5115 Compare April 21, 2026 09:01
@Nickolaus Nickolaus requested a review from Copilot April 21, 2026 09:03
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/apm_cli/registry/operations.py:116

  • _get_installed_server_ids() currently extracts IDs only for runtimes 'copilot', 'codex', and 'vscode'. MCPIntegrator.install() can pass 'cursor' (and potentially other runtimes) into check_servers_needing_installation(), and Cursor uses the same mcpServers schema as Copilot (including id). Without a 'cursor' branch here, Cursor will always appear to have zero installed servers, causing unnecessary reinstalls. Consider handling 'cursor' by reading IDs from config.get("mcpServers", {}) like the Copilot branch (and similarly address any other MCP-compatible runtimes you intend to support).
                )
                config = client.get_current_config()
                
                if isinstance(config, dict):
                    if runtime == 'copilot':

Comment thread src/apm_cli/registry/operations.py
@Nickolaus Nickolaus force-pushed the feature/codex-project-scoped-mcp branch from 84b5115 to 4863999 Compare April 21, 2026 09:30
@Nickolaus Nickolaus requested a review from Copilot April 21, 2026 09:38
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated 1 comment.

Comment thread docs/src/content/docs/integrations/ide-tool-integration.md Outdated
@Nickolaus Nickolaus requested a review from Copilot April 21, 2026 10:07
@Nickolaus Nickolaus force-pushed the feature/codex-project-scoped-mcp branch from 4863999 to 729180a Compare April 21, 2026 10:09
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 29 out of 29 changed files in this pull request and generated no new comments.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 29 out of 29 changed files in this pull request and generated 1 comment.

Comment thread src/apm_cli/registry/operations.py Outdated
@Nickolaus Nickolaus force-pushed the feature/codex-project-scoped-mcp branch from 729180a to f5178f5 Compare April 21, 2026 10:42
@Nickolaus Nickolaus requested a review from Copilot April 21, 2026 10:42
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 29 out of 29 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

src/apm_cli/registry/operations.py:105

  • _get_installed_server_ids() now takes project_root and user_scope, but the docstring does not describe what these parameters do (or when to pass them). Please update the docstring so callers understand which scope/path is being inspected when determining installed server IDs.
        """Get all installed server IDs across target runtimes.
        
        Args:
            target_runtimes: List of runtimes to check
            

@Nickolaus Nickolaus force-pushed the feature/codex-project-scoped-mcp branch from f5178f5 to e7c3558 Compare April 21, 2026 10:55
@Nickolaus Nickolaus requested a review from Copilot April 21, 2026 10:56
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 29 out of 29 changed files in this pull request and generated no new comments.

Copy link
Copy Markdown
Collaborator

@sergio-sisternes-epam sergio-sisternes-epam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your efforts towards improving our Codex support @Nickolaus

Consolidated review -- project-scoped Codex MCP

Strong direction. Project-scoped .codex/config.toml is the right evolution
for Codex support, and this PR does the hard plumbing work to thread scope
awareness through the adapter/factory/integrator chain cleanly. The
_get_installed_server_ids() pre-computation is a genuine performance win,
the em-dash to ASCII fix is correct, and the test coverage for scope
resolution is solid. Thank you for the iterations.

Two items need fixing before merge. Both are small.


BLOCKING

B1. TOML parse error silently destroys user config (see inline on codex.py)

In codex.py, get_current_config() catches TomlDecodeError and returns
{}. When update_config() then merges into that empty dict, it writes
back to disk -- silently deleting the user's entire existing config. A single
typo in config.toml + apm install = data loss.

B2. Silent Codex gating -- no output when excluded from targets (see inline on mcp_integrator.py)

When Codex is installed but not in active_targets(), it's silently removed
from target_runtimes. Every other filter path (--exclude, scope filtering,
"no runtimes installed") tells the user what happened. This is the only
silent one. If Codex was the only detected runtime, install() returns 0
with zero output and no way to diagnose why.


SHOULD-FIX (recommended in this PR, small effort)

S1. scope silently overrides user_scope in install() (see inline on
mcp_integrator.py). Two params controlling one behaviour is a maintenance
trap. At minimum, add a comment at the override site explaining the precedence.

S2. Log which Codex config path was resolved (project .codex/config.toml
vs user ~/.codex/config.toml) at verbose level. Currently the user cannot
tell which file was written.

S3. The doc table in ide-tool-integration.md crams two paths into one
cell for Codex. Split into two rows or add a Scope column for readability.

S4. Add a doc note about .gitignore for .codex/: if MCP server configs
use inline credentials instead of env-var references, users should gitignore
the directory.


FOLLOW-UP (separate PRs -- happy to file issues)

  • Add regression test ensuring all MCPClientAdapter subclasses set
    project_root/user_scope after construction (inheritance guard).
  • Move normalize_project_arg() to CodexClientAdapter (only consumer).
  • Unify remove_stale() to use adapter-resolved paths for all runtimes
    (currently uses adapter for Codex but hardcoded paths for VS Code/Cursor).
  • Freeze project_root at construction time (avoid dynamic os.getcwd()
    fallback in the property).
  • Path containment validation on project_root before mkdir(parents=True).
  • Update skill resource files for Codex scope awareness.

Once B1 and B2 are addressed, happy to approve. The SHOULD-FIX items are
small enough to land in the same push. Thank you for driving Codex support
forward -- this is a big step for the project.


Review produced with input from the APM Expert Review Panel: Python Architect, CLI Logging Expert, DevX UX Expert, Supply Chain Security Expert, with CEO synthesis.

Copy link
Copy Markdown
Collaborator

@sergio-sisternes-epam sergio-sisternes-epam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline comments for B1, B2, and S1 above.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[B1] TOML parse error silently wipes user config

When get_current_config() catches TomlDecodeError and returns {},
update_config() merges the new server into that empty dict and writes it
back -- destroying the user's entire existing config.

Suggested fix: warn and return None on decode failure; have
update_config() bail out (or require --force) when the existing
config could not be parsed:

except toml.TomlDecodeError as exc:
    from ..utils.console import _rich_warning
    _rich_warning(
        f"Could not parse {config_path}: {exc} -- "
        "skipping config write to avoid data loss",
        symbol="warning",
    )
    return None

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

)
active = {t.name for t in active_targets(root, config_target)}
if "codex" not in active:
target_runtimes = [r for r in target_runtimes if r != "codex"]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[B2] Silent Codex gating -- add a log line here

This is the only runtime-filter path with no user output. Every other
filter (--exclude, scope, "no runtimes installed") logs something.

Suggested fix:

if "codex" not in active:
    target_runtimes = [r for r in target_runtimes if r != "codex"]
    _log.debug("Codex gated out: active_targets=%s", active)
    if logger:
        logger.progress(
            "Codex not an active project target -- skipping MCP config "
            "(create .codex/ or set target: codex in apm.yml)"
        )

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +892 to +895
if scope is InstallScope.USER:
user_scope = True
elif scope is InstallScope.PROJECT:
user_scope = False
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[S1] scope silently overrides user_scope -- document or unify

A caller passing scope=PROJECT, user_scope=True gets silently corrected
to user_scope=False. If both params must coexist, at minimum add a
comment explaining the precedence:

# scope enum takes precedence over the raw user_scope bool to prevent
# callers from accidentally mixing scopes.

Longer-term, consider deprecating user_scope in favour of scope as
the single source of truth.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Copy Markdown
Collaborator

@sergio-sisternes-epam sergio-sisternes-epam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline comments for S2, S3, and S4.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[S2] Log which config path was resolved

Now that Codex config can go to either .codex/config.toml (project) or
~/.codex/config.toml (user), the success message should include the
resolved path so the user knows where to look. Currently only the server
name is shown.

Suggested addition after a successful write:

_log.debug("Codex config written to %s", config_path)

And at verbose level:

print(f"[i]  Codex config: {config_path}")

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


**Runtime targeting**: APM detects which runtimes are installed and configures MCP servers for all of them. Use `--runtime <name>` or `--exclude <name>` to control which clients receive configuration.
**Runtime targeting**: APM detects which runtimes are installed and configures MCP servers for all of them. Codex MCP is project-scoped during project installs, so it is written to `.codex/config.toml` only when Codex is an active project target. Use `--runtime <name>` or `--exclude <name>` to control which clients receive configuration.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[S4] Add .gitignore guidance for .codex/

Project-scoped .codex/config.toml lives inside the repo. If MCP server
configs use inline credentials instead of env-var references, a
git add . will commit them. Consider adding a note here (or in the
getting-started guide):

"Review .codex/config.toml before committing -- if your MCP servers
use inline credentials instead of env-var references, add .codex/ to
.gitignore."

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

| VS Code | `.vscode/mcp.json` | JSON `servers` object |
| GitHub Copilot CLI | `~/.copilot/mcp-config.json` | JSON `mcpServers` object |
| Codex CLI | `~/.codex/config.toml` | TOML `mcp_servers` section |
| Codex CLI | Project: `.codex/config.toml` User scope: `~/.codex/config.toml` | TOML `mcp_servers` section |
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[S3] Doc table -- split Codex into two rows for readability

Two paths crammed into one cell is hard to scan. Every other row has a
single path. Suggested:

| Codex CLI (project) | `.codex/config.toml` | TOML `mcp_servers` section |
| Codex CLI (`--global`) | `~/.codex/config.toml` | TOML `mcp_servers` section |

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Copy Markdown
Collaborator

@sergio-sisternes-epam sergio-sisternes-epam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI is red -- one small fix needed

Thanks for the fast iteration, @Nickolaus. Build & Test (Linux) is failing on two tests in tests/unit/test_uninstall_engine_helpers.py that weren't updated when you added project_root / user_scope kwargs to MCPIntegrator.remove_stale(). assert_called_once_with is strict about kwargs, so the assertions now mismatch the real call.

test_stale_servers_removed

mock_mcp.remove_stale.assert_called_once_with(
    {"stale-server"}, project_root=None, user_scope=False, scope=None,
)

test_scope_passed_to_remove_stale

mock_mcp.remove_stale.assert_called_once_with(
    {"stale"}, project_root=None, user_scope=False, scope="user",
)

Once CI is green on those two, I'm happy to approve.


Note on the third failure -- install.py LOC budget

test_install_py_under_legacy_budget is also failing because commands/install.py grew to 1701 LOC (budget 1680). This isn't your problem to solve in this PR -- install.py is already a God-module candidate and every cross-cutting change pushes it further. I've opened #839 to track extracting scope/MCP helpers into a proper apm_cli/install/ package.

For this PR, please bump the budget in tests/unit/install/test_architecture_invariants.py from 1680 to 1710 with a comment referencing #839, e.g.:

# Budget bumped for scope-aware Codex plumbing; see #839 for the extraction plan.
assert n <= 1710, (...)

That keeps the invariant's signal intact (future drift is still caught) while making the bump visible and traceable rather than anonymous creep.

Thanks again for pushing Codex support forward -- once those tests are green, this is ready to land.


Review produced with input from the APM Expert Review Panel: Python Architect, CLI Logging Expert, DevX UX Expert, with CEO synthesis.

auto-merge was automatically disabled April 22, 2026 11:08

Head branch was pushed to by a user without write access

@Nickolaus Nickolaus force-pushed the feature/codex-project-scoped-mcp branch 2 times, most recently from 2d1024b to e4ce9b9 Compare April 22, 2026 11:10
@Nickolaus Nickolaus force-pushed the feature/codex-project-scoped-mcp branch from e4ce9b9 to 21c73b6 Compare April 22, 2026 11:53
srid added a commit to juspay/kolu that referenced this pull request Apr 22, 2026
**Chrome DevTools MCP is now declared in the top-level `apm.yml`, but
this branch keeps project-local fallbacks for the runtimes APM does not
fully cover yet.** Claude still reads the checked-in root `.mcp.json`,
and Codex now gets a checked-in `.codex/config.toml` for trusted
projects. That lets the repo use one MCP server definition across the
current tool mix without pretending upstream support is further along
than it is.

**The split is explicit instead of accidental.** `apm.yml` and
`apm.lock.yaml` describe the `chrome-devtools` server for the runtimes
APM wires up today, OpenCode keeps the generated root `opencode.json`,
and `agents/ai.just` now documents the manual fallbacks plus the
upstream TODOs: `microsoft/apm#655` for Claude and `microsoft/apm#803`
for project-scoped Codex MCP.

**`apm-sync` is tighter in this branch than on `master`.** It now
verifies the root-level `opencode.json` that APM emits for OpenCode MCP
config, using semantic JSON comparison so newline trivia does not create
drift. It also deliberately ignores the manual `.codex/config.toml`
until `microsoft/apm#803` lands, and ignores local OpenCode runtime
noise (`node_modules`, `package.json`, `package-lock.json`,
`.gitignore`) so the check stays focused on APM-managed output.

Verification on commit `ee374f5abaff9b93931be0ee65e9f505dfc77feb`:

- `just fmt`
- `just ai::apm-sync`
- `just ci::fmt`
- `just ci::apm-sync`

Upstream references:
- `microsoft/apm#655` — Claude project/user MCP support (still unmerged)
- `microsoft/apm#803` — Codex project-scoped MCP support (open)
@danielmeppiel danielmeppiel added the panel-review Trigger the apm-review-panel gh-aw workflow label Apr 23, 2026
@github-actions
Copy link
Copy Markdown

APM Review Panel Verdict

Disposition: REQUEST_CHANGES

Two required actions before merge; all other findings are optional clean-ups.


Per-persona findings

Python Architect: The project_root/user_scope threading is clean and
consistent. The MCPClientAdapter.__init__ + project_root property is the
right abstraction -- all subclasses inherit it correctly. Two structural
concerns:

  1. Path(project_root) if project_root is not None else Path.cwd() is
    duplicated as a free-standing local variable in at least four scopes
    across mcp_integrator.py instead of using self.project_root (which
    already performs that fallback). Extract a module-level helper, or rely
    on the property where self is available.

  2. Inside _install_for_runtime, a second ClientFactory.create_client()
    call is made after a successful install solely to log the Codex config
    path. The adapter used for the install is discarded before that block;
    pass it through or stash the path before discarding. Creating an adapter
    object just for a _log.debug() line is wasteful and a maintenance
    footgun.

  3. explicit_target added to install() lacks a type annotation
    (str | None). Minor but inconsistent with the surrounding code.

CLI Logging Expert: The Codex gate skip message routes through
logger.progress(message) -- progress implies forward motion; use
logger.info() or logger.verbose_detail() for a "skipping because not
targeted" signal. The verbose_detail(f" Codex config: {config_path}")
has leading spaces that are inconsistent with other verbose_detail call
sites (no leading indent there). On the _rich_warning call in
codex.py: the copilot adapter already uses print() liberally, so
_rich_warning is strictly better than the status quo. Flag for a future
adapter-output cleanup pass, not a blocker here.

DevX UX Expert: The opt-in model (.codex/ directory or
target: codex in apm.yml) mirrors Cursor/OpenCode -- users who know
those tools will immediately understand Codex. The skip message
"Codex not an active project target -- skipping MCP config (create .codex/ or set target: codex in apm.yml)" is clear and gives two actionable
paths. One gap: normalize_project_arg handles ${workspaceFolder} and
${projectRoot} but not ${workspaceRoot}, which some VS Code extensions
and Codex server configs use as a synonym. This should be handled to avoid
a silent broken-arg edge case. The docs update in
ide-tool-integration.md is correct and the "commit safety" callout for
.codex/ is a valuable ergonomic addition.

Supply Chain Security Expert: No new credential leak surfaces. The
TOML parse-error path now returns None and skips the write -- this is a
meaningful data-safety improvement (corrupt config is preserved, not
silently overwritten with an empty structure). One asymmetry: IOError
on read still returns {} while TomlDecodeError returns None; if a
config file is partially written and unreadable due to an I/O error,
returning {} would overwrite it on the next write -- the same risk the
TomlDecodeError fix addresses. Make both exceptions return None.
Regarding path security: project_root flows through Path(project_root)
without validate_path_segments / ensure_path_within guards. The
primary call site (commands/install.py) hard-codes Path.cwd(), so
there is no current traversal risk. However, the API accepts arbitrary
paths from future callers; add a guard at the MCPClientAdapter.__init__
boundary or document the expected-safe-input contract explicitly to prevent
silent drift.

Auth Expert: No auth surface touched. No findings.

OSS Growth Hacker: Project-scoped Codex MCP targets a growing segment
of AI-native developers using OpenAI tooling; this is a good conversion
surface. However, no CHANGELOG.md entry exists for this feature -- per
project conventions every merged PR that changes behavior must have one
under [Unreleased]. The docs update is present, which is the high-value
surface. Consider a brief "Codex project-scoped MCP" entry in CHANGELOG
to give existing users the signal to try the new workflow.


CEO arbitration

This is a well-scoped incremental feature that extends APM's Codex
integration to match the Cursor/OpenCode opt-in pattern, which is the
right design. The architecture is sound: scope threading is consistent,
the gating logic is correct, and the TOML data-safety fix is a genuine
improvement. The two required actions (CHANGELOG entry and IOError
returning None instead of {}) are low-effort corrections. The
normalize_project_arg gap for ${workspaceRoot} and the
path-security-guard absence are worth addressing before this API is
consumed by more callers, but do not block the current use case. Ship
after the two required items are addressed; the rest can follow in a
clean-up PR or alongside the next Codex-touching change.


Required actions before merge

  1. Add CHANGELOG.md entry under [Unreleased] > Added for
    project-scoped Codex MCP and user_scope support
    (e.g. "feat(codex): project-scoped MCP config and user target support (#803)").

  2. Return None on IOError in CodexClientAdapter.get_current_config()
    to match the TomlDecodeError path and prevent silent overwrite of an
    unreadable config file.


Optional follow-ups

  • Extract the Path(project_root) if project_root is not None else Path.cwd()
    pattern into a module-level helper in mcp_integrator.py (or use
    adapter.project_root where self is available).
  • Eliminate the second ClientFactory.create_client() in
    _install_for_runtime that exists only for logging; pass or capture the
    config path during the actual install call.
  • Add "${workspaceRoot}" to the normalize_project_arg substitution set.
  • Change logger.progress(message) to logger.info() (or
    logger.verbose_detail()) for the Codex gate skip message.
  • Add validate_path_segments / ensure_path_within at the
    MCPClientAdapter.__init__ boundary for project_root, or document the
    safe-input contract so future callers do not introduce traversal paths.
  • Add type annotation str | None to the explicit_target parameter in
    MCPIntegrator.install().
  • Broader adapter output cleanup: replace print() calls in
    CopilotClientAdapter with _rich_* helpers or raise structured
    exceptions (out of scope for this PR).

Generated by PR Review Panel for issue #803 · ● 954.1K ·

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

panel-review Trigger the apm-review-panel gh-aw workflow

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support Codex CLI as integration target

4 participants