Skip to content

fix(mcp): launch bundled cao-mcp-server without a per-launch network fetch#403

Open
tedswinyar wants to merge 1 commit into
awslabs:mainfrom
tedswinyar:fix/cao-7le-mcp-server-command
Open

fix(mcp): launch bundled cao-mcp-server without a per-launch network fetch#403
tedswinyar wants to merge 1 commit into
awslabs:mainfrom
tedswinyar:fix/cao-7le-mcp-server-command

Conversation

@tedswinyar

Copy link
Copy Markdown

What happens

The bundled agent profiles start the orchestration MCP server with:

command: uvx
args: ["--from", "git+https://github.com/awslabs/cli-agent-orchestrator.git@main", "cao-mcp-server"]

On a cold uvx cache this clones and builds the package from GitHub before the server starts. If that runs longer than the provider's MCP startup timeout, the agent comes up without its orchestration tools (handoff, assign, send_message) and can't delegate, even though cao-server is running fine.

The repo's own test suite already documents this. test/e2e/conftest.py pre-warms the cache with this note:

On a cold cache uvx must download and install ~80 packages, which takes ~20s and exceeds Codex's default 10s MCP startup timeout.

The tests work around it by warming the cache first; a real cold start hits the failure.

It also pins the MCP server to @main regardless of the installed CAO version, so the orchestration client can drift out of sync with the API server it talks to.

The fix

cao-mcp-server ships in the same package as cao-server (declared in [project.scripts] since the initial launch), so the running install already has a matching copy. This change:

  • Points the bundled profiles (5 in agent_store/), docs/agent-profile.md, and 26 example profiles at the bare cao-mcp-server console script instead of the uvx network fetch.
  • Adds utils/mcp_resolution.py to turn the bare command into a PATH-independent invocation, since the agent subprocess's PATH may not contain the script dir (unactivated venv, devcontainer, pip install --prefix). It keeps the Copilot provider's 3-tier fallback, now shared by all providers:
    1. the cao-mcp-server next to the running interpreter (exact env match), else
    2. cao-mcp-server resolved on PATH, else
    3. <python> -m cli_agent_orchestrator.mcp_server.server (no console script needed).
  • Profiles never carry a static path. Runtime providers (Claude Code, Codex, Kimi, Copilot) resolve on each launch and prefer the interpreter sibling. Install-time providers (Kiro, OpenCode) and the Antigravity/Cursor providers write the command to a config file consumed later by the CLI itself, so they resolve with persisted=True, preferring the stable PATH launcher (e.g. ~/.local/bin/cao-mcp-server) that survives uv tool upgrade, whereas the versioned venv path would not.
  • Extends _inject_kiro_mcp_timeout detection to match the new command forms (bare script, resolved absolute path, module entrypoint) in addition to the legacy uvx args form, so custom-named cao-mcp-server entries keep the raised kiro tool-call timeout.
  • Deletes the e2e uvx cache-warmup fixture (no network fetch left to warm).
  • Adds a regression test that dynamically enumerates the bundled profiles so a newly added profile cannot silently reintroduce the uvx form (this happened once already while this fix was in development: docs(examples): add AWS cloud-ops agent examples with config #377 added 7 example profiles with the old form, now also fixed here).

Notes

  • The cao-ops-mcp-server snippets in the README are intentionally untouched: they are configured by an external agent where CAO isn't installed, so the uvx form is correct there. Same for examples/fleet/deploy/bootstrap.sh, which installs CAO itself.
  • Copilot's previously-inline resolution now uses the shared helper (no behavior change).

Testing

  • test/utils/test_mcp_resolution.py: resolution order for runtime vs persisted, passthrough for non-CAO commands, module-entrypoint fallback, idempotency.
  • test/utils/test_bundled_profiles.py: every bundled profile declares the bare console script; regression guard against the uvx form.
  • Provider unit tests for Codex and Antigravity resolution; extended _inject_kiro_mcp_timeout tests for all command forms.
  • black / isort / mypy / full pytest suite green.

Implements the fix discussed with the maintainers in the internal Slack channel (approach and the two refinements confirmed there: shared 3-tier fallback, runtime injection over static paths in profiles).

@codecov-commenter

codecov-commenter commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (main@45636f8). Learn more about missing BASE report.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #403   +/-   ##
=======================================
  Coverage        ?   87.97%           
=======================================
  Files           ?      124           
  Lines           ?    15690           
  Branches        ?        0           
=======================================
  Hits            ?    13803           
  Misses          ?     1887           
  Partials        ?        0           
Flag Coverage Δ
unittests 87.97% <100.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 removes the per-launch uvx --from git+https://...@main fetch for the bundled orchestration MCP server and instead launches the already-installed cao-mcp-server, with shared runtime/install-time resolution to make that invocation PATH-independent and version-aligned with the installed CAO.

Changes:

  • Update bundled + example agent profiles and docs to declare command: cao-mcp-server (no network fetch).
  • Add a shared resolver (utils/mcp_resolution.py) and integrate it across providers, install-time config materialization, and OpenCode translation.
  • Remove the E2E uvx cache warmup and add regression/unit tests to prevent reintroducing the fetch form.

Reviewed changes

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

Show a summary per file
File Description
test/utils/test_opencode_config.py Adjusts OpenCode translation tests to assert passthrough for custom uvx commands and resolution for bundled cao-mcp-server.
test/utils/test_mcp_resolution.py Adds unit tests for the new MCP command resolver (runtime vs persisted behavior and fallback modes).
test/utils/test_bundled_profiles.py Adds a regression guard ensuring all bundled wheel profiles use bare cao-mcp-server and never reference the git+https uvx fetch.
test/services/test_install_service.py Extends Kiro timeout injection tests to detect new command forms (bare, resolved path, module entrypoint).
test/providers/test_codex_provider_unit.py Ensures Codex provider runs bundled cao-mcp-server entries through the shared resolver.
test/providers/test_antigravity_cli_unit.py Ensures Antigravity provider persists a resolved, PATH-stable cao-mcp-server command into its config.
test/e2e/conftest.py Removes the session-scoped uvx cache warmup fixture (no longer needed).
src/cli_agent_orchestrator/utils/opencode_config.py Resolves bundled cao-mcp-server before flattening into OpenCode’s command-list schema.
src/cli_agent_orchestrator/utils/mcp_resolution.py Introduces shared resolution for launching bundled cao-mcp-server without PATH dependency or network fetch.
src/cli_agent_orchestrator/services/install_service.py Resolves MCP server commands at install time for persisted provider configs; expands Kiro timeout detection.
src/cli_agent_orchestrator/providers/kimi_cli.py Applies shared MCP command resolution before emitting Kimi MCP config.
src/cli_agent_orchestrator/providers/cursor_cli.py Applies shared MCP command resolution when materializing Cursor plugin MCP manifests.
src/cli_agent_orchestrator/providers/copilot_cli.py Replaces inline resolution logic with the shared resolver (intended no behavior change).
src/cli_agent_orchestrator/providers/codex.py Applies shared MCP command resolution before emitting -c mcp_servers.* overrides.
src/cli_agent_orchestrator/providers/claude_code.py Applies shared MCP command resolution before writing the per-session MCP config file.
src/cli_agent_orchestrator/providers/antigravity_cli.py Resolves bundled cao-mcp-server for persisted config output (prefers stable PATH launcher).
src/cli_agent_orchestrator/agent_store/workflow_scout.md Switches bundled profile MCP server from uvx git fetch to bare cao-mcp-server.
src/cli_agent_orchestrator/agent_store/reviewer.md Switches bundled profile MCP server from uvx git fetch to bare cao-mcp-server.
src/cli_agent_orchestrator/agent_store/memory_manager.md Switches bundled profile MCP server from uvx git fetch to bare cao-mcp-server.
src/cli_agent_orchestrator/agent_store/developer.md Switches bundled profile MCP server from uvx git fetch to bare cao-mcp-server.
src/cli_agent_orchestrator/agent_store/code_supervisor.md Switches bundled profile MCP server from uvx git fetch to bare cao-mcp-server.
examples/orchestration/reviewer-opus.md Updates example profile to use bare cao-mcp-server.
examples/orchestration/dev-sonnet.md Updates example profile to use bare cao-mcp-server.
examples/orchestration/dev-opus.md Updates example profile to use bare cao-mcp-server.
examples/orchestration/dev-kimi.md Updates example profile to use bare cao-mcp-server.
examples/cross-provider/report_generator_codex.md Updates example profile to use bare cao-mcp-server.
examples/cross-provider/README.md Updates documentation snippet to use bare cao-mcp-server.
examples/cross-provider/data_analyst_kiro_cli.md Updates example profile to use bare cao-mcp-server.
examples/cross-provider/data_analyst_kimi_cli.md Updates example profile to use bare cao-mcp-server.
examples/cross-provider/data_analyst_copilot_cli.md Updates example profile to use bare cao-mcp-server.
examples/cross-provider/data_analyst_codex.md Updates example profile to use bare cao-mcp-server.
examples/cross-provider/data_analyst_claude_code.md Updates example profile to use bare cao-mcp-server.
examples/cross-provider/cross_provider_supervisor.md Updates example profile to use bare cao-mcp-server.
examples/codex-basic/codex_reviewer.md Updates example profile to use bare cao-mcp-server.
examples/codex-basic/codex_documenter.md Updates example profile to use bare cao-mcp-server.
examples/codex-basic/codex_developer.md Updates example profile to use bare cao-mcp-server.
examples/aws/stepfunction/stepfunction-agent.md Updates AWS example profile to use bare cao-mcp-server.
examples/aws/sqs-send/sqs-send-agent.md Updates AWS example profile to use bare cao-mcp-server.
examples/aws/sqs-monitor/sqs-monitor-agent.md Updates AWS example profile to use bare cao-mcp-server.
examples/aws/sqs-dlq-check/sqs-dlq-check-agent.md Updates AWS example profile to use bare cao-mcp-server.
examples/aws/dynamodb-query/dynamodb-query-agent.md Updates AWS example profile to use bare cao-mcp-server.
examples/aws/dynamodb-delete/dynamodb-delete-agent.md Updates AWS example profile to use bare cao-mcp-server.
examples/aws/cloudwatch-logs/cloudwatch-logs-agent.md Updates AWS example profile to use bare cao-mcp-server.
examples/assign/report_generator.md Updates assign example profile to use bare cao-mcp-server.
examples/assign/README.md Updates documentation snippet to use bare cao-mcp-server.
examples/assign/data_analyst.md Updates assign example profile to use bare cao-mcp-server.
examples/assign/analysis_supervisor.md Updates assign example profile to use bare cao-mcp-server.
docs/agent-profile.md Updates agent-profile documentation snippet to use bare cao-mcp-server.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +100 to +103
for label, candidate in order:
if candidate:
logger.debug("Resolved %s via %s: %s", command, label, candidate)
return candidate, []

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.

Good catch. Fixed in 04b5486: resolution now preserves caller-supplied args in every tier (script resolution returns them unchanged; the module-entrypoint fallback appends them after -m <module>). Added a TestArgsPreservation suite covering all three tiers plus a no-aliasing check.

Comment on lines +105 to +110
# Module entrypoint via the current interpreter — runnable without any
# console script on PATH. Falls back to a bare ``python3`` only if
# sys.executable is unavailable (best effort in degenerate environments).
interpreter = sys.executable or "python3"
logger.debug("Resolved %s to module entrypoint via %s", command, interpreter)
return interpreter, ["-m", CAO_MCP_SERVER_MODULE]

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 in 04b5486 — the fallback now returns [-m, <module>, *args] so flags reach the server in that tier too. Covered by test_module_fallback_appends_args_after_module.

@tedswinyar tedswinyar force-pushed the fix/cao-7le-mcp-server-command branch from 0d71958 to 04b5486 Compare July 10, 2026 04:48

@gutosantos82 gutosantos82 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PR Review: #403 — Point bundled agent profiles at the installed cao-mcp-server console script (drop the per-launch uvx git fetch)

Summary

Replaces the uvx --from git+https://github.com/awslabs/cli-agent-orchestrator.git@main cao-mcp-server cold-fetch in bundled/example agent profiles with the bare cao-mcp-server console script, and adds utils/mcp_resolution.py — a shared 3-tier resolver (interpreter-sibling → PATH → python -m …server) with a persisted flag — wired into 8 sites (6 providers + install_service + opencode_config). This fixes agents coming up without orchestration tools when a cold uvx clone exceeds the provider MCP-startup timeout, and removes an unpinned @main network dependency. The change is well-constructed, thoroughly documented, and verified end-to-end (265/0 tests pass, resolver claims confirmed, the target console script launches). Recommend merge after minor cleanups; no blocking or correctness-critical issues.

Important (should fix)

  • [correctness] src/cli_agent_orchestrator/utils/mcp_resolution.py:124-129 (surfaces in providers/codex.py:287-291, claude_code.py:257, cursor_cli.py:510) — resolve_mcp_server_config unconditionally sets command/args even when the input entry has no command key. profile.mcpServers is typed Dict[str, Any] and isn't validated against the command-required McpServer model, so a URL/transport server like {"type":"http","url":"..."} would gain command=""/args=[] after resolution. In codex.py the if "command" in cfg: guard then becomes always-true and emits mcp_servers.<name>.command="", clobbering a command-less custom server that previously passed through untouched. Introduced. Likelihood is low (all bundled profiles + the McpServer model are stdio/command-based), but it's a real behavior change for command-less custom entries and is unguarded by tests. Fix: early-return an untouched copy when "command" is absent. (Path-weighted: lives in providers/+utils/; kept at Important rather than Blocking only because it can't affect the PR's actual targets.)
  • [tests] provider wiring — claude_code / cursor_cli / kimi_cli / copilot + install_agent block — 4 of 8 wired sites have no test that would fail if the resolve_* call were removed. Notably test_kimi_cli_unit.py fixtures use command: "uv", args: ["run","cao-mcp-server"] (not the bare command), so the resolver just passes through and kimi_cli.py:342 is never meaningfully exercised. install_agent's persisted-resolution block (install_service.py:290-296) only runs against non-cao command: service-mcp fixtures (no-op). Recommend at minimum a kimi test with a bare cao-mcp-server and an install_agent test asserting the written Kiro/Q JSON command is resolved (persisted=True). (antigravity/codex/opencode already have dedicated assertions.)
  • [conventions] src/cli_agent_orchestrator/providers/copilot_cli.py:11,13import shutil and import sys are now dead after the inline 3-tier block was replaced by resolve_cao_mcp_command() (Path/subprocess still used). Flagged by 3 reviewers. Won't fail CI (repo runs black/isort/mypy, not ruff/flake8), so it's cleanup, not a gate — drop both imports. Introduced.
  • [conventions] CHANGELOG.md — no entry for a user-facing behavior fix (bundled profiles now launch cao-mcp-server via the installed console script instead of a per-launch uvx git+… fetch). Other PRs add entries referencing their number; add one under ## [Unreleased]### Fixed referencing #403. Introduced.

Nits (optional)

  • [security] src/cli_agent_orchestrator/providers/codex.py:288,290 — the resolved command/args are interpolated into TOML -c overrides via unescaped f-strings (f'{prefix}.command="{cfg["command"]}"'). No shell injection (whole list is shlex.join'd at :325), but a resolved absolute install path containing " or \ (legal on Linux) would emit malformed TOML. The adjacent developer_instructions handling at :271 already escapes \/"/\n, so this is an inconsistency. Value is install-path-controlled, not remote/agent-controlled → low risk; harden for defense-in-depth. Introduced (the resolver widens what flows here from a fixed literal to a resolved path).
  • [tests] utils/mcp_resolution.py:36-38 — the sys.platform=='win32' .exe filename branch and _sibling_script().exists() are never exercised (all tests mock _sibling_script wholesale). The one resolver branch with zero coverage; low risk (mechanical).
  • [correctness] utils/mcp_resolution.py:126-129 — for non-cao passthrough servers, args is force-set to [] when the original omitted it. Harmless for stdio servers; folds into the command-less fix above.

Prior feedback

None — the only existing PR comments are empty-body entries from the author (@tedswinyar); no maintainer feedback to dedupe against. All findings above are net-new.

Tests

Strong resolver unit coverage: new test/utils/test_mcp_resolution.py (194 lines) covers both persisted modes, all 3 tiers, args preservation + copy semantics, idempotency, non-cao passthrough, and empty sys.executable; test/utils/test_bundled_profiles.py dynamically enumerates bundled profiles and guards against reintroducing the uvx git+https form (good regression guard). Markers are correct (plain unit tests, no live CLI/tmux). Deleting the e2e uvx cache-warmup fixture is safe — it was best-effort and only warmed a cache the profiles no longer use. Gap is provider-integration coverage (see Important); the codex mock is signature-faithful.

Verification

Run in a clean Docker env (ghcr.io/astral-sh/uv:python3.12-bookworm-slim), uv sync succeeded (numpy built in-container — no local-host blocker):

  • Baseline: uv run pytest over the 6 new/touched files → 265 passed, 0 failed (mcp_resolution 97% cov, opencode_config 98%, install_service 85%, codex 94%).
  • ✓ VERIFIED — passthrough leaves non-cao commands (and abs paths) unchanged.
  • ✓ VERIFIED — persisted=False prefers sibling; persisted=True prefers PATH; reverse fallbacks confirmed.
  • ✓ VERIFIED — module-entrypoint fallback when neither sibling nor PATH exists; caller args appended in tier 3.
  • ✓ VERIFIED — idempotency: resolve_mcp_server_config(resolve_mcp_server_config(cfg)) == resolve_mcp_server_config(cfg), other keys preserved.
  • ✓ VERIFIED — the central claim: cao-mcp-server is a real, shipped console script (pyproject [project.scripts]server:main), resolvable (which → venv path) and launchable (live FastMCP stdio launch confirmed; tier-3 python -m …server also launches).
  • ⁇ NOT VERIFIED — the full end-to-end "agent comes up with orchestration tools inside Codex's 10s timeout" outcome (needs a live provider CLI session); the mechanism it depends on (fast local script vs. slow uvx clone) is confirmed.

Verdict

Approve with nits. A well-scoped, well-documented, end-to-end-verified fix with no blocking or correctness-critical issues. Recommended before merge: guard the command-less-entry edge case, add the copilot dead-import + CHANGELOG cleanups, and close the kimi/install_agent test gaps.

@fanhongy

Copy link
Copy Markdown
Collaborator

Code Review: resolver introduces a live TOML-escaping gap on the Codex path

Verdict: Request changes (or merge together with a companion escaping fix — see below)

This PR adds a shared mcp_resolution.py helper that rewrites the bundled cao-mcp-server bare console-script command to a PATH-independent invocation (interpreter-sibling → PATH → module entrypoint), and wires it into every provider. The resolver itself is well-designed, well-tested, and a good simplification (it replaces copilot_cli.py's inline three-tier fallback with the shared helper, removing duplicated logic).

Finding: this PR turns a previously-inert Codex TOML gap into a live one

File: src/cli_agent_orchestrator/providers/codex.py:666-667 (unchanged context in this diff, but its risk profile changes because of this PR)

if "command" in cfg:
    command_parts.extend(["-c", f'{prefix}.command="{cfg["command"]}"'])

Codex's -c overrides are TOML, and this line has always emitted command via raw, unescaped string interpolation — no backslash/quote escaping at all. Before this PR, that was low-risk in practice: for the bundled cao-mcp-server server, command was always the static literal "cao-mcp-server" (or "uvx" in the pre-bundling era) — a value with no TOML-special characters.

This PR changes that. cfg = resolve_mcp_server_config(cfg) (line 665, new) now runs the resolver before the command is emitted, and the resolver can return:

  • an absolute filesystem path from _sibling_script()/shutil.which() — on Windows this routinely contains backslashes (C:\Users\bob\.venv\Scripts\cao-mcp-server.exe), which is not a valid TOML escape sequence when interpolated raw;
  • the module-entrypoint interpreter path in the same shape.

So this PR is what makes command a dynamic, environment-dependent value for the very code path that has never escaped it. Merging this PR alone (without a companion escaping fix) means: on Windows, or any install layout that lands in the PATH/module-entrypoint fallback tiers, the emitted -c mcp_servers.cao-mcp-server.command="C:\Users\...\cao-mcp-server.exe" is invalid TOML — Codex's -c override either fails to parse or is misinterpreted, and the bundled orchestration MCP server (handoff/assign/send_message) silently fails to register for that session.

Test coverage gap: the new test test_bundled_mcp_command_is_resolved (test/providers/test_codex_provider_unit.py, ~line 1099) mocks the resolver to return /home/u/.local/bin/cao-mcp-server — a path with no TOML-special characters — so it passes despite the missing escaping. No test in this PR exercises a resolved command containing a backslash or quote, which is exactly the case that would expose the bug on Windows.

Suggested fix: either land this together with (or immediately followed by) a fix that escapes command, args, and env values the same way codexConfig/developer_instructions already are elsewhere in this file (a _toml_scalar-style helper), or add the escaping in this PR directly since it's the one that makes command dynamic. If a follow-up PR is already planned to add TOML escaping, please link it here so reviewers know the gap is intentionally sequenced rather than missed.

What's solid (no changes needed)

  • mcp_resolution.py's three-tier fallback and persisted flag semantics — correct and thoroughly unit-tested (test/utils/test_mcp_resolution.py), including the args-copy-not-alias behavior and the empty-sys.executable edge case.
  • Every other call site (antigravity_cli.py, claude_code.py, cursor_cli.py, kimi_cli.py, install_service.py, opencode_config.py) writes JSON, not TOML, so the resolved dynamic path is safely serialized there — no equivalent escaping concern outside codex.py.
  • copilot_cli.py simplification — replacing the inline three-tier fallback with the shared resolver is a clean dedup with identical behavior.
  • _inject_kiro_mcp_timeout widening to detect the marker in command and the module-entrypoint fallback form, with matching new tests.
  • test/utils/test_bundled_profiles.py (new) — good regression guard against reintroducing the old uvx --from git+https://... cold fetch in any bundled profile.
  • test/e2e/conftest.py cleanup (dropping warmup_mcp_server_cache and the now-unused subprocess import) is complete and correct.

…fetch

The bundled agent profiles configured the orchestration MCP server as
`uvx --from git+https://github.com/awslabs/cli-agent-orchestrator.git@main
cao-mcp-server`, which cold-clones and builds the whole package from GitHub on
every agent launch. The project's own e2e cache-warmup fixture documents this
as ~20s on a cold cache, which exceeds Codex's default 10s MCP startup timeout,
so the agent starts WITHOUT its orchestration tools (handoff / assign /
send_message) and silently no-ops -- the supervisor looks healthy but cannot
delegate. It also pinned the MCP server to @main regardless of the installed
CAO version, so the orchestration client could drift out of sync with the API
server it talks to.

cao-mcp-server is a console script installed alongside cao / cao-server, so the
running CAO install already has a matching one. Point the bundled profiles (and
examples/docs) at it, and add utils/mcp_resolution.py to turn the bare command
into a PATH-independent invocation, since the agent subprocess's PATH may not
contain the script dir (unactivated venv, devcontainer, pip --prefix):

  1. the cao-mcp-server next to the running interpreter (exact env match), else
  2. cao-mcp-server resolved on PATH, else
  3. <python> -m cli_agent_orchestrator.mcp_server.server (no script needed).

Runtime providers (Claude Code, Codex, Kimi, Copilot) rebuild the launch config
each time and prefer the interpreter sibling. Install-time providers (Kiro,
OpenCode) and the Antigravity/Cursor providers write the command to a config
file they read at a later launch, so they resolve with persisted=True and
prefer the stable PATH launcher (e.g. ~/.local/bin/...) -- which survives
`uv tool upgrade`, whereas the versioned venv path would not. Copilot's
previously-inline resolution now uses the shared helper.

Also: guard the resolver against an empty sys.executable; delete the e2e uvx
cache-warmup fixture (no network fetch to warm now); update the example
profiles; and add resolver and bundled-profile tests.
@tedswinyar tedswinyar force-pushed the fix/cao-7le-mcp-server-command branch from 04b5486 to 89b23ee Compare July 10, 2026 07:44
@tedswinyar

Copy link
Copy Markdown
Author

Thanks for the careful review — you're right that this PR is what turns the Codex command field from a static literal into a dynamic, environment-dependent value, and the sequencing deserves to be explicit.

The companion escaping fix is #404 (linked from the description of that PR back here). It routes command, each arg, each env value, and env_vars through the existing _toml_scalar helper — the same escaping developer_instructions and codexConfig already use — so a resolved absolute path containing a backslash or quote emits valid TOML. It also includes the test you called out as missing: a resolved command containing both a backslash and a quote, asserting the escaped form present and the raw form absent.

Two options from my side, happy with either:

  1. Merge fix(mcp): launch bundled cao-mcp-server without a per-launch network fetch #403 then fix(codex): TOML-escape MCP command/args/env in -c overrides #404 immediately after (they're stacked; fix(codex): TOML-escape MCP command/args/env in -c overrides #404 collapses to codex.py + tests once this lands), or
  2. If you'd rather not have even a brief window with the gap live, I can fold fix(codex): TOML-escape MCP command/args/env in -c overrides #404's codex.py hunk into this PR and close fix(codex): TOML-escape MCP command/args/env in -c overrides #404.

One practical note on the Windows scenario specifically: CAO's tmux/libtmux dependency means the orchestrator doesn't currently run natively on Windows (WSL paths are POSIX-shaped), so the backslash path is defensive rather than reachable today — but agreed it should be correct regardless, and env values are caller-supplied either way.

@tedswinyar

Copy link
Copy Markdown
Author

Thanks for the thorough review (and for running it end-to-end in a clean container — the differential verification is much appreciated). All four Important items are addressed in the updated head (89b23ee):

  1. Command-less entry guardresolve_mcp_server_config now early-returns an untouched copy when "command" is absent, so url/transport servers ({"type": "http", "url": ...}) pass through without gaining command=""/args=[]. Passthrough entries that omit args also no longer gain an empty one. Tests added for both (test_commandless_url_server_passes_through_untouched, test_command_without_args_key_does_not_gain_args_for_passthrough).
  2. Provider wiring tests — added one per gap: claude_code (test_build_command_resolves_bundled_mcp_command, asserts the written --mcp-config JSON), cursor (test_mcp_resolves_bundled_command_in_manifest, asserts the plugin manifest), kimi (test_build_command_resolves_bundled_mcp_command, asserts the --mcp-config flag payload with a bare cao-mcp-server), copilot (test_build_runtime_mcp_config_resolves_bundled_command), plus install_agent for Kiro (test_install_kiro_resolves_bundled_mcp_command_persisted, asserts the written agent JSON prefers the PATH launcher under persisted=True). Each is a wiring guard: removing the resolve call fails the test.
  3. Dead importsshutil/sys dropped from copilot_cli.py.
  4. CHANGELOG — entry added under [Unreleased]Fixed referencing fix(mcp): launch bundled cao-mcp-server without a per-launch network fetch #403.

Also took the nits: _sibling_script now has direct tests (exists / missing / empty sys.executable), and the args-force-set folded into the guard above. The TOML-escaping nit is #404's territory — it routes command/args/env through _toml_scalar (see my reply to the other review comment on sequencing).

The one line codecov flags as uncovered is the interpreter = sys.executable or "python3" fallback for a frozen interpreter — exercised indirectly but hard to hit both branches of in-process; left as is.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants