Skip to content

Refactor: extract shared action layer for CLI and MCP tools#272

Draft
ryanjoneil wants to merge 72 commits into
developfrom
refactor/mcp-shared-actions
Draft

Refactor: extract shared action layer for CLI and MCP tools#272
ryanjoneil wants to merge 72 commits into
developfrom
refactor/mcp-shared-actions

Conversation

@ryanjoneil
Copy link
Copy Markdown
Member

Summary

  • Extract shared business logic from CLI commands and MCP tools into cli/actions/ module (18 action modules, ~60 CLI files updated, 18 MCP tool files slimmed)
  • Split monolithic test_mcp.py (2163 lines) into 9 domain-specific test files under tests/cli/mcp/
  • Add 147 new action tests across 18 test files under tests/cli/actions/
  • Cache experiment data to ~/.nextmv/experiments/ instead of /tmp/ (mirrors existing run cache pattern)
  • Fix review findings: restore local_only field, wire scenario/ensemble create through actions, add return type annotations

Test plan

  • 551 tests pass locally (2 pre-existing failures from optional deps)
  • All 315 CLI/MCP/action tests pass
  • CLI commands work end-to-end against live API
  • MCP server registers 89 tools, all execute correctly
  • Experiment cache saves to ~/.nextmv/experiments/<endpoint>/<type>/<id>/

🤖 Generated with Claude Code

ryanjoneil and others added 26 commits April 7, 2026 17:22
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Create nextmv/cli/actions/app.py with pure action functions wrapping
SDK calls (list_apps, create_app, get_app, delete_app, app_exists,
update_app, push_app). Add tests in tests/cli/actions/test_app.py.
Update all 7 CLI app commands and the MCP tools file to delegate
through the new action functions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Create cli/actions/app.py with 7 pure action functions
- Add tests/cli/actions/test_app.py with 11 unit tests
- Update CLI cloud/app/ commands to import from actions
- Update MCP tools/app.py to import from actions
- Fix 2 existing test patches for new import paths

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moves shared instance business logic out of CLI commands and MCP tools
into pure functions in nextmv/cli/actions/instance.py, following the
same pattern established for app and version actions.

- Add nextmv/cli/actions/instance.py with list/get/create/update/delete functions
- Add tests/cli/actions/test_instance.py with full unittest coverage
- Update all instance CLI commands to use Client(profile=) + action calls
- Update MCP tools/instance.py to import and delegate to action functions

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

- Add nextmv/cli/actions/switchback.py with pure action functions
- Add tests/cli/actions/test_switchback.py with full test coverage
- Update all CLI cloud/switchback/* commands to use Client directly
- Update MCP tools/switchback.py to import from actions
- Fix tests/cli/test_mcp.py: update 10 tests from _get_app mock pattern
  to _get_client + actions.Application patch pattern
- Improve ensemble action error messages to include index in KeyError/
  ValueError for run_groups and rules validation failures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The refactor accidentally dropped the --manifest option, verbose=True,
and rich_print=True from the CLI push command. The action function
push_app() is still used by MCP (which doesn't need these), but the
CLI push now calls the SDK directly to preserve its full behavior.

Also fixes unused imports flagged by ruff.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Revert unrelated removal of local_only field from ManifestOption
  (breaking SDK change that doesn't belong in this refactor)
- Wire scenario/create.py and ensemble/create.py through action
  functions instead of calling SDK directly
- Add missing return type annotations to 8 functions in
  actions/run.py and actions/local.py
- Remove unused validate_content_format import from MCP _helpers.py

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Plan and design docs belong in the agents repo memory system,
not in the nextmv-py source tree.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cloud run data was already cached to ~/.nextmv/runs/ with a predictable
directory structure. Experiment data (scenario, batch, ensemble, shadow,
switchback, acceptance) was written to /tmp/ with random names that get
lost across sessions.

Now experiments save to ~/.nextmv/experiments/<endpoint>/<type>/<id>/,
mirroring the run cache layout. Batch metadata uses metadata.json,
ensemble run results use run_result.json, everything else uses
result.json.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ryanjoneil and others added 3 commits April 10, 2026 10:51
Design for eliminating metadata duplication between Typer CLI commands
and FastMCP tool registrations by making the actions layer the single
source of truth for parameter signatures, types, help text, and
docstrings. Connectors become thin wiring tables built via
introspection-based framework helpers.

Pilot scope: `app` domain only, local only (no push/PR).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add "Design Principle: Structured Data → Rendered Markup" section
  naming the pattern used by examples, on_success, result_message,
  and the save-message formatting.
- Add `saved_noun=` parameter to `cli.command()` and specify the
  save-message format, eliminating ~30 hand-written save-success
  strings across the full CLI when migration completes.
- Switch `examples=` from a hand-formatted docstring block to
  structured tuples of (description, command) pairs, rendered by
  the framework.
- Clarify framework module layout: `cli.progress`, `mcp_fw.client`,
  and `mcp_fw.clean` are re-exports of existing helpers.
- Document wrapper signature forwarding preserves parameter kinds
  (positional-only, keyword-only).
- Clarify that CLI-only option aliases (e.g. OutputOption) are
  framework-internal and never imported by domain files.
- Clarify `normalize_empty=` (declarative) vs `mcp_fw.clean()`
  (imperative) usage guidance.
- Add caveat that tests asserting on implementation details will
  need rewriting as observable-contract assertions.
- Add test cases for `saved_noun` and preserved parameter kinds.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Show inline [magenta]...[/magenta] highlighting in LIST_EXAMPLES
  and CREATE_EXAMPLES tuples, matching the hand-written blocks on
  develop verbatim.
- Document that _render_examples passes description markup through
  unchanged and only adds surrounding structure (header, bullets,
  $ [dim]...[/dim] wrapper).
- Add snapshot-comparison test against current help output for at
  least one example-rich command and one simple command to confirm
  byte-for-byte fidelity.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ryanjoneil and others added 29 commits April 10, 2026 21:22
Extracts the interactive push-and-version-and-instance workflow from
the current hand-written push.py command into a new module that takes
client: Client as its first parameter so it can be wired via
cli.command(handles_own_output=True) in Task 11. The thin push_app
action continues to serve the MCP side unchanged.

Preserves the exact inline Typer annotations (rich_help_panel groupings,
short flags, metavars, envvars, help text) from the current push.py.
Helper functions (handle_push, _handle_version_creation, etc.) copied
verbatim.
Replaces 7 per-command Typer files with a single connector table in
cli/cloud/app/__init__.py that registers each command via cli.command().
The 6 thin commands (list, create, get, delete, exists, update) wrap
pure actions from cli.actions.app directly. The rich push command wraps
run_push_workflow from cli.cloud.app._workflows via handles_own_output=True,
which preserves the full interactive version-and-instance flow while
routing through the framework.

Also updates cli/init.py to import handle_push from _workflows (its
new home) and trims the duplicate Examples block from run_push_workflow's
docstring since cli.command(examples=...) now renders it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Refreshes the golden help fixtures captured pre-refactor in Task 10 to
their post-refactor state, and adds a two-layer test:

1. TestHelpOutputSnapshots — byte-for-byte comparison (after whitespace
   normalization and ANSI stripping) against the refreshed fixtures.
   Locks in forward compatibility: future accidental changes to the
   framework's help rendering will be caught here.

2. TestHelpOutputStructure — presence checks for flag names, short forms,
   envvar hints, example blocks, and rich_help_panel groupings. These
   don't depend on exact byte ordering and survive cosmetic Typer
   rendering changes.

Post-refactor help differs from the pre-refactor hand-written state in
three intentional ways, all documented in the Task 11 commit: expanded
multi-paragraph action docstrings, option ordering following the action
signature, and generic --output help text from the framework-internal
_OutputOption alias.

Fixtures are captured via Typer's CliRunner (the same harness the
snapshot tests use) so program-name and terminal-width rendering are
consistent between capture and verification.
Replace the seven hand-written @mcp.tool() wrappers in
nextmv/cli/mcp/tools/app.py with mcp_fw.tool() calls. Each tool's
parameter signature, types, and descriptions are now derived from the
underlying action in nextmv.cli.actions.app; only per-tool wiring (name,
empty-string normalization, result-message formatting) lives in app.py.

Schema changes (existing clients are unaffected):

- cloud_create_app now treats `name` as optional (was required in the
  old hand-written wrapper). The underlying create_app action has
  always accepted name=None — the "required" was enforced only at the
  MCP tool boundary. Clients that omit `name` no longer see a
  validation error at the MCP layer.

- cloud_update_app now exposes `default_experiment_instance` as an
  optional parameter (was not exposed in the old wrapper). This is a
  schema extension, not a break.

Also update test_cloud_list_apps_calls_sdk to patch _get_client at its
new lookup site (nextmv.cli.mcp.framework.tool) since the framework
imports the helper at module load time. This is mock-coupling to the
old inline-wrapper internals; the observable behavior is unchanged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move Callable imports from typing to collections.abc (UP035).
- Sort imports in test files (I001).
- Remove unused Annotated import from test_options.py (F401).
…tory

Addresses ruff C901 — command() was McCabe complexity 11. Extracting
the inner wrapper closure into a module-level _build_wrapper factory
separates signature/metadata fixup (command's job) from runtime dispatch
(the wrapper's job). No behavior change.

Also removes an unused mock_client variable in test_command.py (F841).
- actions/version.py: use dual-purpose aliases, multi-paragraph docstrings,
  add version_exists action (previously only in the CLI, not MCP).
- cloud/version/__init__.py: rewrite as connector table; delete 6 per-command
  files (create, delete, exists, get, list, update).
- cloud/version/_workflows.py: new file with run_version_exists_check, which
  wraps the thin version_exists action with CLI-specific JSON print + exit
  code 1 on missing version.
- mcp/tools/version.py: rewrite as mcp_fw.tool() calls. Adds new
  cloud_version_exists tool (previously only the CLI had it).
- tests/cli/mcp/test_tools.py: bump expected tool count 89 -> 90 for the
  new cloud_version_exists tool.

One intentional UX regression: the "creating or getting" progress message
variant on --exist-ok is gone; all creates now show "Creating version...".
Rationale: cli.command() supports only static progress strings, and the
marginal text difference doesn't justify a framework extension.
- actions/instance.py: use dual-purpose aliases, multi-paragraph docstrings,
  add instance_exists action (previously only in the CLI, not MCP). Add
  exist_ok to create_instance and locked to update_instance to expose the
  full SDK surface.
- cloud/instance/__init__.py: rewrite as connector table; delete 6
  per-command files (create, delete, exists, get, list, update).
- cloud/instance/_workflows.py: new file with build_options/build_config
  helpers (moved verbatim from the old create.py), run_create_instance,
  run_update_instance, and run_instance_exists_check workflows. The create
  and update workflows build an InstanceConfiguration from many individual
  flags before delegating to the thin actions; the exists workflow wraps
  instance_exists with a JSON print + exit code 1 on a missing instance.
  Update preserves --output/-u (instead of -o) so the short flag does not
  collide with --options/-o.
- mcp/tools/instance.py: rewrite as mcp_fw.tool() calls. Adds new
  cloud_instance_exists tool (previously only the CLI had it).
- tests/cli/actions/test_instance.py: update assertions to include the new
  exist_ok and locked kwargs threaded through to the SDK.
- tests/cli/mcp/test_tools.py: bump expected tool count 90 -> 91 for the
  new cloud_instance_exists tool.

Two intentional UX regressions, mirroring the version migration:
- The "Creating or getting instance..." progress variant on --exist-ok is
  gone; create always shows "Creating instance...".
- The delete command no longer takes --yes / shows a confirmation prompt,
  consistent with how app delete and version delete were migrated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduces cli.DeleteConfirmation (a NamedTuple with confirm/decline/
succeeded templates) and a delete_confirm= kwarg on cli.command(). When
set, the generated command injects a --yes/-y flag and, unless --yes is
supplied, prompts the user via confirmation() before calling the action.
Declining prints the decline message and returns; accepting calls the
action and prints the success message.

Fixes a regression introduced during the app (pilot) and instance
migrations where the interactive --yes confirmation on delete commands
was silently dropped. Destructive operations should not run without
either explicit --yes or an interactive prompt.

- cli/framework/command.py: DeleteConfirmation NamedTuple, delete_confirm
  kwarg, wrapper injection of yes: YesOption param, runtime confirmation
  gate before the action call, success message after.
- cli/framework/options.py: YesOption dual-purpose alias.
- cli/framework/__init__.py: re-export DeleteConfirmation and YesOption.
- cli/cloud/{app,version,instance}/__init__.py: switch delete from
  on_success= to delete_confirm=, restoring the confirmation prompt.
- tests/cli/framework/test_command.py: 7 new tests in TestDeleteConfirm
  covering incompatibility checks, --yes bypass, accept/decline flows,
  and help-output flag injection.
- actions/secrets.py: dual-purpose aliases, multi-paragraph docstrings,
  add update_secrets_collection action (previously CLI-only).
- cloud/secrets/__init__.py: connector table. list/get/delete wrap the
  thin actions directly; create/update route through _workflows.py
  because they parse the repeatable --secrets JSON flag.
- cloud/secrets/_workflows.py: build_secrets (JSON->Secret parser),
  run_create_secrets_collection, run_update_secrets_collection.
- mcp/tools/secrets.py: mcp_fw.tool() calls. Adds new
  cloud_update_secrets_collection tool (previously CLI-only).
- delete uses delete_confirm for interactive confirmation + --yes.
- tests/cli/mcp/test_tools.py: tool count 91 -> 92.

Deleted: cloud/secrets/{create,delete,get,list,update}.py.
- actions/input_set.py: dual-purpose aliases, multi-paragraph docstrings,
  add start_time/end_time parameters to create_input_set for time-range
  filtering. update_input_set gains managed_input_ids pass-through.
- cloud/input_set/__init__.py: connector table. list/get/delete wrap
  thin actions; create/update route through _workflows.py because they
  parse --managed-inputs JSON and (for create) RFC 3339 time flags.
- cloud/input_set/_workflows.py: _parse_managed_inputs helper,
  run_create_input_set, run_update_input_set. Create preserves the
  safe_id fallback for auto-generated IDs.
- mcp/tools/input_set.py: mostly mcp_fw.tool() calls. cloud_create_input_set
  kept as an inline @mcp.tool() to preserve its "exactly one method"
  validation that returns error strings instead of raising.
- tests/cli/actions/test_input_set.py: update call kwargs for the new
  start_time/end_time/inputs parameters.
…iases

- actions/managed_input.py: dual-purpose aliases, multi-paragraph docstrings,
  add update_managed_input action (previously CLI-only).
- cloud/managed_input/__init__.py: connector table. list/get/delete wrap
  the thin actions; create/update route through _workflows.py for the
  content-format Format object, precondition check, and --output save.
- cloud/managed_input/_workflows.py: run_create_managed_input (preserves
  the --content-format -> Format translation and upload/run precheck),
  run_update_managed_input (precondition check + --output file save).
- mcp/tools/managed_input.py: mcp_fw.tool() calls. create stays as an
  inline @mcp.tool() because it takes an untyped `input` dict that the
  framework builder cannot introspect cleanly. Adds new
  cloud_update_managed_input tool.
- tests/cli/mcp/test_tools.py: tool count 92 -> 93.
- actions/acceptance.py: dual-purpose aliases, multi-paragraph docstrings,
  add update_acceptance_test action (previously CLI-only).
- cloud/acceptance/__init__.py: connector table. list/delete wrap thin
  actions; get/create/update route through _workflows.py for polling,
  JSON metric parsing, and --output file saves.
- cloud/acceptance/_workflows.py: build_metrics (JSON->Metric parser),
  run_get_acceptance_test (with --wait polling), run_create_acceptance_test
  (inline rich_help_panel groupings + polling + multi-line docstring
  attached via __doc__ since typer docstrings can't include f-strings),
  run_update_acceptance_test.
- mcp/tools/acceptance.py: mcp_fw.tool() calls. get stays as inline
  @mcp.tool() because it saves the result to an experiment cache file
  rather than returning it raw. Adds cloud_update_acceptance_test.
- tests/cli/mcp/test_tools.py: tool count 93 -> 94.
- actions/scenario.py: dual-purpose aliases, multi-paragraph docstrings,
  add update_scenario_test action (previously CLI-only). build_scenario
  helper preserved as an internal dict->Scenario converter.
- cloud/scenario/__init__.py: connector table. list/delete wrap thin
  actions; get/metadata/create/update route through _workflows.py for
  polling, JSON scenario parsing, --output file saves, and the separate
  scenario_test_metadata SDK path.
- cloud/scenario/_workflows.py: build_scenario_dicts (JSON parser),
  run_get_scenario_test (with --wait polling), run_get_scenario_metadata
  (metadata-only retrieval), run_create_scenario_test (inline
  rich_help_panel groupings + polling + multi-line docstring attached via
  __doc__ since typer docstrings can't include f-strings),
  run_update_scenario_test.
- mcp/tools/scenario.py: mcp_fw.tool() calls for list/update/delete. get
  stays as inline @mcp.tool() because it saves the result to an
  experiment cache file; create stays inline to catch validation errors
  and return user-friendly strings. Adds cloud_update_scenario_test.
- tests/cli/mcp/test_tools.py: tool count 94 -> 95.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- actions/ensemble.py: dual-purpose aliases, multi-paragraph docstrings,
  add update_ensemble action (previously CLI-only). Rename create_ensemble
  and related params from ensemble_id to ensemble_definition_id to match
  the option alias. parse_evaluation_rule / build_ensemble_run_config /
  ensemble_run_with_result / ensemble_run_submit helpers preserved — they
  power the bespoke run-submission MCP tools.
- cloud/ensemble/__init__.py: connector table. list/get/delete wrap thin
  actions; create/update route through _workflows.py for repeatable JSON
  --run-groups / --rules parsing and --output file save.
- cloud/ensemble/_workflows.py: build_run_group_dicts / build_rule_dicts
  (JSON validators), run_create_ensemble (inline rich_help_panel +
  multi-line docstring attached via __doc__ since typer docstrings can't
  include f-string enum interpolation), run_update_ensemble.
- mcp/tools/ensemble.py: mcp_fw.tool() calls for list/get/update/delete.
  create stays as inline @mcp.tool() to catch validation errors and
  return user-friendly strings. The run-submission tools
  (cloud_ensemble_run / cloud_ensemble_run_submit) stay inline because
  of bespoke input_dir_path / managed_input_id handling. register() is
  split into two helpers to stay under the C901 complexity limit. Adds
  cloud_update_ensemble.
- tests/cli/actions/test_ensemble.py: update kwarg name to match the
  renamed ensemble_definition_id parameter.
- tests/cli/mcp/test_tools.py: tool count 95 -> 96.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- actions/batch.py: dual-purpose aliases, multi-paragraph docstrings,
  add update_batch action (previously CLI-only) and runs passthrough
  on create_batch.
- cloud/batch/__init__.py: connector table. list/delete wrap thin
  actions; get/metadata/create/update route through _workflows.py for
  polling, JSON runs/option_sets parsing, --output file saves, and the
  separate metadata retrieval path.
- cloud/batch/_workflows.py: build_option_sets and build_runs JSON
  parsers, run_get_batch (with --wait polling), run_get_batch_metadata
  (metadata-only retrieval), run_create_batch (inline rich_help_panel
  groupings + polling + multi-line docstring attached via __doc__ since
  typer docstrings can't include f-string-style content),
  run_update_batch.
- mcp/tools/batch.py: mcp_fw.tool() calls for list/create/update/delete.
  get and metadata stay as inline @mcp.tool() because they save results
  to the experiment cache. Adds cloud_update_batch.
- tests/cli/actions/test_batch.py: add runs=None kwarg to the two
  create_batch assertion matchers since the action now passes runs
  through to the SDK.
- tests/cli/mcp/test_tools.py: tool count 96 -> 97.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- actions/shadow.py: dual-purpose aliases, multi-paragraph docstrings,
  add shadow_test_metadata and update_shadow_test actions (previously
  CLI-only).
- cloud/shadow/__init__.py: connector table. list/start/delete wrap
  thin actions; get/metadata/create/update/stop route through
  _workflows.py for JSON comparison parsing, SDK start/termination
  dataclass construction, --output file saves, the separate metadata
  retrieval path, and the --intent enum for the stop flow.
- cloud/shadow/_workflows.py: run_get_shadow_test (with --output),
  run_get_shadow_metadata (metadata-only retrieval),
  run_create_shadow_test (inline rich_help_panel groupings +
  datetime parsing + multi-line docstring attached via __doc__),
  run_update_shadow_test, run_stop_shadow_test (exposes the
  StopIntent enum as --intent).
- mcp/tools/shadow.py: mcp_fw.tool() calls for
  list/update/start/stop/delete. get and metadata stay as inline
  @mcp.tool() because they save results to the experiment cache;
  create stays inline because its termination_events/start_events
  arguments are untyped dicts the framework can't introspect and
  it benefits from catching bad LLM payloads with a friendly error
  string. Adds cloud_shadow_test_metadata and
  cloud_update_shadow_test.
- tests/cli/mcp/test_tools.py: tool count 97 -> 99.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- actions/switchback.py: dual-purpose aliases, multi-paragraph
  docstrings, add switchback_test_metadata and update_switchback_test
  actions (previously CLI-only). create_switchback_test now threads
  the optional ``start`` datetime through to the SDK.
- cloud/switchback/__init__.py: connector table. list/start/delete
  wrap thin actions; get/metadata/create/update/stop route through
  _workflows.py for --output file saves, inline
  comparison/duration/start flags that compose into a single SDK
  create call, the separate metadata retrieval path, and the
  --intent enum for the stop flow.
- cloud/switchback/_workflows.py: run_get_switchback_test (with
  --output), run_get_switchback_metadata (metadata-only retrieval),
  run_create_switchback_test (inline rich_help_panel groupings and
  datetime parsing), run_update_switchback_test,
  run_stop_switchback_test (exposes the StopIntent enum as --intent).
- mcp/tools/switchback.py: mcp_fw.tool() calls for
  list/create/update/start/stop/delete. get and metadata stay as
  inline @mcp.tool() because they save results to the experiment
  cache. Adds cloud_switchback_test_metadata and
  cloud_update_switchback_test.
- tests/cli/actions/test_switchback.py: add start=None kwarg to the
  two create_switchback_test assertion matchers since the action now
  passes start through to the SDK.
- tests/cli/mcp/test_tools.py: tool count 99 -> 101.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- actions/account.py: dual-purpose aliases, multi-paragraph docstrings,
  add create_account, update_account, delete_account (previously
  CLI-only via build_account + Account.new/.update/.delete).
- cloud/account/__init__.py: connector table. get/delete wrap thin
  actions; create/update route through _workflows.py for admin email
  parsing and --output file saves.
- cloud/account/_workflows.py: _parse_admins (splits comma-separated
  flags), run_create_account, run_update_account.
- mcp/tools/account.py: mcp_fw.tool() calls. Adds cloud_create_account,
  cloud_update_account, cloud_delete_account.
- tests/cli/mcp/test_tools.py: tool count 101 -> 104.
- actions/sso.py: expand from just delete_domain to the full set of SSO
  operations (get/create/update/enable/disable/delete_configuration +
  delete_domain). All wrap SSOConfiguration SDK methods with
  multi-paragraph docstrings.
- cloud/sso/__init__.py: connector table with a nested `domain`
  subcommand. All commands use handles_own_output=True because they
  need stdin fallbacks (create), confirmation prompts
  (enable/disable/delete/domain-delete), or plain success messages
  with no data return.
- cloud/sso/_workflows.py: run_get/create/update/enable/disable/delete
  _sso_configuration + run_delete_domain. Preserves stdin fallback on
  create, confirmation gates on destructive ops.
- mcp/tools/sso.py: mcp_fw.tool() calls. Adds cloud_get_sso_configuration
  as a read-only surface. Does NOT expose
  create/update/enable/disable/delete via MCP — destructive org-wide
  operations should not be driven by LLMs without confirmation.
- tests/cli/mcp/test_tools.py: tool count 104 -> 105.

Deleted: cloud/sso/{create,delete,disable,enable,get,update}.py and
cloud/sso/domain/ entire directory.
- actions/community.py: multi-paragraph docstrings; no parameter changes
  (community has no cloud-entity IDs to port to dual-purpose aliases).
- community/__init__.py: connector table with handles_own_output=True for
  both list and clone. list renders Rich tables; clone calls the SDK's
  verbose+rich cloning path directly.
- community/_workflows.py: run_list_community_apps, run_clone_community_app,
  plus the Rich table/list helpers moved from the old list.py (byte-for-byte
  preserved: _apps_table, _apps_list, _versions_table, _versions_list,
  _find_app).
- mcp/tools/community.py: list migrated to mcp_fw.tool(); clone stays
  inline because it has a bespoke return string.

Deleted: community/{clone,list}.py.
Collapses the nine per-command files under cloud/run/ into a single
connector __init__.py driven by cli.command() and a _workflows.py
module that houses the complex flows (content-format-aware save,
stdin input parsing, polling, tracked-run file parsing).

Actions are refactored to take client: Client + app_id as their first
parameters so they work with the framework's first-param assertion.
Three simple MCP tools (cloud_run_status, cloud_list_runs,
cloud_cancel_run) are migrated to mcp_fw.tool(); the rest remain
inline because they cache run artifacts on disk.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduces nextmv.cli.actions.marketplace as the first action file for
a CLI-only domain (marketplace is not exposed via MCP). Collapses the
twelve per-command files under cloud/marketplace/{app,subscription,
version}/ into three connector __init__.py files driven by
cli.command().

Adds dual-purpose aliases for the marketplace-specific option metadata
(partner ID, subscription ID, version ID, app ID) so the actions use
the same shared-connectors pattern even though no MCP tools consume
them today.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- actions/upload.py (new): create_upload_url thin action.
- actions/data.py (new): upload_data thin action.
- cloud/upload/__init__.py: connector table (single command, thin).
- cloud/data/__init__.py: connector table. upload routes through
  _workflows.py for stdin/file/dir/.tar.gz input resolution.
- cloud/data/_workflows.py: run_upload_data + _resolve_data_kwarg.
- No MCP tools added — these domains aren't exposed via MCP.

Deleted: cloud/upload/create.py, cloud/data/upload.py.
)

if output is not None and output != "":
Path(output).write_text(json.dumps(collection_dict, indent=2))
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.

2 participants