Skip to content

feat: Build an MCP server for cao operations#166

Open
patricka3125 wants to merge 21 commits intoawslabs:mainfrom
patricka3125:feat/cao-ops-mcp
Open

feat: Build an MCP server for cao operations#166
patricka3125 wants to merge 21 commits intoawslabs:mainfrom
patricka3125:feat/cao-ops-mcp

Conversation

@patricka3125
Copy link
Copy Markdown
Contributor

@patricka3125 patricka3125 commented Apr 10, 2026

Overview

Addresses #161

Introduces cao-ops-mcp, a new MCP server that exposes CAO management operations as structured tools for a user's primary agent. It enables end-to-end agent-driven CAO workflows — discovering and installing profiles, launching sessions, delivering prompts, monitoring progress, and shutting down — without leaving the agent interface or switching to a separate terminal.

Motivation

All CAO management operations currently require either the cao CLI or direct HTTP API calls in a separate terminal. The existing cao-mcp-server does not address this — its tools (handoff, assign, send_message) are scoped to inter-agent orchestration within active CAO sessions, not to managing CAO itself.

cao-ops-mcp fills this gap as the agentic control plane alongside the existing Web UI (visual control plane) and CLI (manual control plane). A user's primary agent can now manage the full CAO lifecycle within a single conversation — no terminal switching required.

Key Changes

File What changed and why
src/.../ops_mcp_server/server.py New MCP server (cao-ops-mcp) with 8 tools across two groups: profile management (list_profiles, get_profile_details, install_profile) and session lifecycle (launch_session, send_session_message, list_sessions, get_session_info, shutdown_session)
src/.../ops_mcp_server/models.py Pydantic response models for the ops MCP tools (LaunchResult, InstallResult, ProfileListResult, SessionListResult, SendMessageResult)
src/.../ops_mcp_server/__init__.py Package init for the new ops_mcp_server module
src/.../services/install_service.py New service layer extracted from the CLI install command — pure install_agent() function returning a structured InstallResult, reusable by both the CLI and the new API endpoint
src/.../api/main.py Two new API endpoints: GET /agents/profiles/{name} (full profile content) and POST /agents/profiles/install (install via service layer), both consumed by the ops MCP tools
src/.../cli/commands/install.py Refactored to a thin wrapper over install_service.install_agent() — behavior is unchanged, install logic now lives in the service layer
pyproject.toml Registers the cao-ops-mcp-server entry point
README.md Adds a CAO Ops MCP Server section documenting setup, available tools, and the typical workflow
test/ops_mcp_server/test_server.py Unit tests for all 8 MCP tools with mocked API calls
test/api/test_api_profiles.py Unit tests for the two new API endpoints
test/services/test_install_service.py Unit tests for the extracted install service (each source type, each provider path, env var injection, error cases)
test/cli/commands/test_install.py Slimmed down to cover only CLI-level behavior; install logic tests moved to test_install_service.py

Test Plan

  • uv run pytest test/ --ignore=test/e2e -v — full unit test suite passes with no regressions
  • uv run cao-ops-mcp-server — server starts without errors (exits cleanly when stdin closes)
  • Test implemented MCP tools manually with MCP inspector: list_profilesinstall_profilelaunch_sessionsend_session_messageget_session_infoshutdown_session
    (uvx --with-editable . fastmcp dev inspector src/cli_agent_orchestrator/ops_mcp_server/server.py)

@patricka3125 patricka3125 marked this pull request as draft April 10, 2026 09:36
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 10, 2026

Codecov Report

❌ Patch coverage is 94.15385% with 19 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@a9fdfdf). Learn more about missing BASE report.

Files with missing lines Patch % Lines
...rc/cli_agent_orchestrator/ops_mcp_server/server.py 90.16% 12 Missing ⚠️
...cli_agent_orchestrator/services/install_service.py 95.38% 6 Missing ⚠️
src/cli_agent_orchestrator/api/main.py 96.87% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main     #166   +/-   ##
=======================================
  Coverage        ?   90.94%           
=======================================
  Files           ?       50           
  Lines           ?     4076           
  Branches        ?        0           
=======================================
  Hits            ?     3707           
  Misses          ?      369           
  Partials        ?        0           
Flag Coverage Δ
unittests 90.94% <94.15%> (?)

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

☔ View full report in Codecov by Sentry.
📢 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.

@haofeif haofeif added the enhancement New feature or request label Apr 10, 2026
patricka3125 and others added 9 commits April 11, 2026 20:59
Add tests for invalid session-create responses, prompt delivery
failures, and rejected non-.md install sources so the corresponding
branches in the ops MCP server and install service are exercised.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move the hard-coded 120s terminal ready timeout into a shared
TERMINAL_READY_TIMEOUT constant so both cao-mcp-server handoff and
cao-ops-mcp launch consume the same value, and narrow the broad
except clauses in install_agent and the ops MCP _request_json helper
so real bugs propagate instead of being flattened into error strings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wrap discover_profiles and list_sessions in ProfileListResult and
SessionListResult envelopes so every tool returns a consistent shape
with a success flag instead of mixing list and error dict payloads.
Rewrite the tool docstrings in cao-mcp style and document the
install_profile source-resolution order, including the CWD-collision
gotcha where a bare agent name matching a file in the working
directory routes to path resolution.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shorten TERMINAL_READY_TIMEOUT from 120s to 30s since it acts only as
a fallback after the provider's own initialize() hook has already
run. Rename the discover_profiles tool to list_profiles so the name
matches its sibling list_sessions and more accurately describes the
operation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the prompt parameter from launch_session so it returns session
identifiers immediately without waiting. Add send_session_message which
delivers messages via the inbox API (POST /terminals/{id}/inbox/messages)
using sender_id="cao-ops-mcp", consistent with how the cao-mcp server
delivers messages but without requiring a CAO_TERMINAL_ID env var.

Add SendMessageResult model and 4 new tests for the new tool. Remove 3
now-irrelevant prompt-flow tests from the launch path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
These changes were out of scope for the ops MCP PR. Remove the constant
from constants.py and restore the hardcoded 120.0 timeout in mcp_server
server.py to match the original upstream state.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Documents the new cao-ops-mcp server — setup, available tools, and
typical workflow — alongside the distinction from cao-mcp-server.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…y revert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@patricka3125 patricka3125 marked this pull request as ready for review April 12, 2026 05:31
@haofeif haofeif requested review from a team and tuanknguyen April 12, 2026 08:47
Copy link
Copy Markdown
Contributor

@fanhongy fanhongy left a comment

Choose a reason for hiding this comment

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

Look good. conflict though (due to PR #145 merged)

Please resolve.

patricka3125 and others added 5 commits April 13, 2026 00:44
Upstream added compose_agent_prompt baking for Q/Copilot and skill://
resource support for Kiro during install. The merge conflict resolution
kept the feature branch's service-layer thin wrapper but left those
behaviours out of install_service.py.

- Q: use compose_agent_prompt(profile) to bake skill catalog into prompt
- Kiro: add skill://<SKILLS_DIR>/**/SKILL.md resource; use raw prompt
  (None when empty) so Kiro's native progressive loader handles skills
- Copilot: use compose_agent_prompt(profile, base_prompt=...) to bake
  catalog into the .agent.md body

TestInstallSkillCatalogBaking ported from upstream test_install.py into
test_install_service.py with patch targets corrected for the service layer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Audit of the upstream/main diff found two classes of upstream tests
that were dropped during merge conflict resolution without replacement:

1. TestInstallCommandEnvFlags (8 tests) — env var injection, context
   file secret isolation, unresolved-var detection, and env file
   lifecycle. Ported as TestInstallAgentEnvBehaviour in
   test_install_service.py against install_agent directly.

2. test_install_general_error — upstream CLI caught bare Exception;
   the service only caught (ValueError, OSError). Added except Exception
   fallback to install_agent and a matching test
   test_install_returns_failure_for_unexpected_errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1. Collapse dead except (ValueError, OSError) into except Exception in
   install_agent — both produced the same message; narrower branch was
   unreachable after the broad fallback was added.

2. Restore upstream UX in install.py CLI wrapper:
   - Unresolved-vars warning now reads "Unresolved env var(s) in
     profile: X. Set them with \`cao env set\` or pass --env KEY=VALUE."
   - "Set N env var(s)" now uses len(env_vars) (raw tuple) so duplicate
     --env flags count correctly instead of deduplicating via dict.

3. Fix env_vars round-trip between ops_mcp_server and API: switch from
   comma-separated KEY=VALUE (breaks on values containing ',') to
   JSON-encoded dict. _serialize_env_vars emits json.dumps, _parse_env_vars
   parses json.loads.

4. Return InstallResult directly from the FastAPI install endpoint with
   a typed return annotation instead of result.model_dump() — gives
   FastAPI a proper OpenAPI schema.

5. Inline the one-line _install_result_from_error helper in
   ops_mcp_server/server.py.

6. Add parametrized coverage for the three previously-uncovered
   _resolve_named_source branches: flat provider dir layout, extra dir
   flat layout, extra dir nested layout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
parse_env_assignment was the only caller in _parse_env_vars; the JSON
migration in f8ad731 removed that call site, leaving an F401 dead
import flagged by Ruff.

Also remove the `import json as _json` local shadow inside
_parse_env_vars — json is already imported at module top level.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@patricka3125 patricka3125 marked this pull request as draft April 13, 2026 08:26
…lResult

Upstream's inline install command printed "Downloaded agent from URL to
local store" and "Copied agent from file to local store" before the
success message — this was lost when install logic was extracted to the
service layer.  Adds a source_kind discriminator ('url'|'file'|'name')
to InstallResult and wires it through the CLI output and tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@patricka3125 patricka3125 marked this pull request as ready for review April 13, 2026 08:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants