Skip to content

Conversation

@rholinshead
Copy link
Member

@rholinshead rholinshead commented Sep 10, 2025

Description

Currently, the top-level CLI commands surface exceptions using typer's default UX of pretty-printing the stack trace with local variables and other things.

Ex (on main):

uv run mcp-agent deploy TemporalExample -c examples/temporal --api-key="TEST"
╭───────────────────────────────────── MCP Agent Deployment ──────────────────────────────────────╮
│ Configuration: /Users/ryanholinshead/Projects/mcp-agent/examples/temporal/mcp_agent.config.yaml │
│ Secrets file: /Users/ryanholinshead/Projects/mcp-agent/examples/temporal/mcp_agent.secrets.yaml │
│ Mode: DEPLOY                                                                                    │
╰────────────────────────────────────────── LastMile AI ──────────────────────────────────────────╯
INFO: Using API at https://mcp-agent.com/api
INFO: Checking for existing app ID for 'TemporalExample'...
╭───────────────────────────────── Traceback (most recent call last) ──────────────────────────────────╮
│ /Users/ryanholinshead/Projects/mcp-agent/src/mcp_agent/cli/cloud/commands/deploy/main.py:158 in      │
│ deploy_config                                                                                        │
│                                                                                                      │
│   155 │   │                                                                                          │
│   156 │   │   print_info(f"Checking for existing app ID for '{app_name}'...")                        │
│   157 │   │   try:                                                                                   │
│ ❱ 158 │   │   │   app_id = run_async(mcp_app_client.get_app_id_by_name(app_name))                    │
│   159 │   │   │   if not app_id:                                                                     │
│   160 │   │   │   │   print_info(                                                                    │
│   161 │   │   │   │   │   f"No existing app found with name '{app_name}'. Creating a new app..       │
│                                                                                                      │
│ ╭───────────────────────────────────────────── locals ─────────────────────────────────────────────╮ │
│ │           api_key = 'TEST'                                                                       │ │
│ │           api_url = 'https://mcp-agent.com/api'                                                  │ │
│ │   app_description = None                                                                         │ │
│ │          app_name = 'TemporalExample'                                                            │ │
│ │        config_dir = PosixPath('/Users/ryanholinshead/Projects/mcp-agent/examples/temporal')      │ │
│ │       config_file = PosixPath('/Users/ryanholinshead/Projects/mcp-agent/examples/temporal/mcp_a… │ │
│ │               ctx = <click.core.Context object at 0x17a96cca0>                                   │ │
│ │           dry_run = False                                                                        │ │
│ │ effective_api_key = 'TEST'                                                                       │ │
│ │ effective_api_url = 'https://mcp-agent.com/api'                                                  │ │
│ │    mcp_app_client = <mcp_agent.cli.mcp_app.api_client.MCPAppClient object at 0x17a96d0c0>        │ │
│ │        no_secrets = False                                                                        │ │
│ │   non_interactive = False                                                                        │ │
│ │      provided_key = 'TEST'                                                                       │ │
│ │      secrets_file = PosixPath('/Users/ryanholinshead/Projects/mcp-agent/examples/temporal/mcp_a… │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ │
│                                                                                                      │
│ /Users/ryanholinshead/Projects/mcp-agent/src/mcp_agent/cli/core/utils.py:25 in run_async             │
│                                                                                                      │
│    22 │   - Within tests that use pytest-asyncio                                                     │
│    23 │   """                                                                                        │
│    24 │   try:                                                                                       │
│ ❱  25 │   │   return asyncio.run(coro)                                                               │
│    26 │   except RuntimeError as e:                                                                  │
│    27 │   │   # If we're already in an event loop (like in pytest-asyncio tests)                     │
│    28 │   │   if "cannot be called from a running event loop" in str(e):                             │
│                                                                                                      │
│ ╭───────────────────────────────── locals ─────────────────────────────────╮                         │
│ │ coro = <coroutine object MCPAppClient.get_app_id_by_name at 0x169cafd10> │                         │
│ ╰──────────────────────────────────────────────────────────────────────────╯                         │
│                                                                                                      │
│ /Users/ryanholinshead/.local/share/uv/python/cpython-3.10.16-macos-aarch64-none/lib/python3.10/async │
│ io/runners.py:44 in run                                                                              │
│                                                                                                      │
│   41 │   │   events.set_event_loop(loop)                                                             │
│   42 │   │   if debug is not None:                                                                   │
│   43 │   │   │   loop.set_debug(debug)                                                               │
│ ❱ 44 │   │   return loop.run_until_complete(main)                                                    │
│   45 │   finally:                                                                                    │
│   46 │   │   try:                                                                                    │
│   47 │   │   │   _cancel_all_tasks(loop)                                                             │
│                                                                                                      │
│ ╭───────────────────────────────── locals ──────────────────────────────────╮                        │
│ │ debug = None                                                              │                        │
│ │  loop = <_UnixSelectorEventLoop running=False closed=True debug=False>    │                        │
│ │  main = <coroutine object MCPAppClient.get_app_id_by_name at 0x169cafd10> │                        │
│ ╰───────────────────────────────────────────────────────────────────────────╯                        │
│                                                                                                      │
│ /Users/ryanholinshead/.local/share/uv/python/cpython-3.10.16-macos-aarch64-none/lib/python3.10/async │
│ io/base_events.py:649 in run_until_complete                                                          │
│                                                                                                      │
│    646 │   │   if not future.done():                                                                 │
│    647 │   │   │   raise RuntimeError('Event loop stopped before Future completed.')                 │
│    648 │   │                                                                                         │
│ ❱  649 │   │   return future.result()                                                                │
│    650 │                                                                                             │
│    651 │   def stop(self):                                                                           │
│    652 │   │   """Stop running the event loop.                                                       │
│                                                                                                      │
│ ╭───────────────────────────────────────────── locals ─────────────────────────────────────────────╮ │
│ │   future = <Task finished name='Task-1' coro=<MCPAppClient.get_app_id_by_name() done, defined at │ │
│ │            /Users/ryanholinshead/Projects/mcp-agent/src/mcp_agent/cli/mcp_app/api_client.py:285> │ │
│ │            exception=UnauthenticatedError('Unauthenticated request. Please check your API key or │ │
│ │            login status.')>                                                                      │ │
│ │ new_task = True                                                                                  │ │
│ │     self = <_UnixSelectorEventLoop running=False closed=True debug=False>                        │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ │
│                                                                                                      │
│ /Users/ryanholinshead/Projects/mcp-agent/src/mcp_agent/cli/mcp_app/api_client.py:302 in              │
│ get_app_id_by_name                                                                                   │
│                                                                                                      │
│   299 │   │   if not name or not isinstance(name, str):                                              │
│   300 │   │   │   raise ValueError(f"Invalid app name format: {name}")                               │
│   301 │   │                                                                                          │
│ ❱ 302 │   │   apps = await self.list_apps(name_filter=name, max_results=10)                          │
│   303 │   │   if not apps.apps:                                                                      │
│   304 │   │   │   return None                                                                        │
│   305                                                                                                │
│                                                                                                      │
│ ╭─────────────────────────────────── locals ───────────────────────────────────╮                     │
│ │ name = 'TemporalExample'                                                     │                     │
│ │ self = <mcp_agent.cli.mcp_app.api_client.MCPAppClient object at 0x17a96d0c0> │                     │
│ ╰──────────────────────────────────────────────────────────────────────────────╯                     │
│                                                                                                      │
│ /Users/ryanholinshead/Projects/mcp-agent/src/mcp_agent/cli/mcp_app/api_client.py:431 in list_apps    │
│                                                                                                      │
│   428 │   │   if name_filter:                                                                        │
│   429 │   │   │   payload["nameFilter"] = name_filter                                                │
│   430 │   │                                                                                          │
│ ❱ 431 │   │   response = await self.post("/mcp_app/list_apps", payload)                              │
│   432 │   │   return ListAppsResponse(**response.json())                                             │
│   433 │                                                                                              │
│   434 │   async def list_app_configurations(                                                         │
│                                                                                                      │
│ ╭─────────────────────────────────────── locals ───────────────────────────────────────╮             │
│ │ max_results = 10                                                                     │             │
│ │ name_filter = 'TemporalExample'                                                      │             │
│ │  page_token = None                                                                   │             │
│ │     payload = {'maxResults': 10, 'isCreator': True, 'nameFilter': 'TemporalExample'} │             │
│ │        self = <mcp_agent.cli.mcp_app.api_client.MCPAppClient object at 0x17a96d0c0>  │             │
│ ╰──────────────────────────────────────────────────────────────────────────────────────╯             │
│                                                                                                      │
│ /Users/ryanholinshead/Projects/mcp-agent/src/mcp_agent/cli/core/api_client.py:85 in post             │
│                                                                                                      │
│    82 │   │   │   │   headers=self._get_headers(),                                                   │
│    83 │   │   │   │   timeout=timeout,                                                               │
│    84 │   │   │   )                                                                                  │
│ ❱  85 │   │   │   _raise_for_unauthenticated(response)                                               │
│    86 │   │   │   _raise_for_status_with_details(response)                                           │
│    87 │   │   │   return response                                                                    │
│    88                                                                                                │
│                                                                                                      │
│ ╭───────────────────────────────────── locals ──────────────────────────────────────╮                │
│ │   client = <httpx.AsyncClient object at 0x17a96ff70>                              │                │
│ │     path = '/mcp_app/list_apps'                                                   │                │
│ │  payload = {'maxResults': 10, 'isCreator': True, 'nameFilter': 'TemporalExample'} │                │
│ │ response = <Response [401 Unauthorized]>                                          │                │
│ │     self = <mcp_agent.cli.mcp_app.api_client.MCPAppClient object at 0x17a96d0c0>  │                │
│ │  timeout = 30.0                                                                   │                │
│ ╰───────────────────────────────────────────────────────────────────────────────────╯                │
│                                                                                                      │
│ /Users/ryanholinshead/Projects/mcp-agent/src/mcp_agent/cli/core/api_client.py:24 in                  │
│ _raise_for_unauthenticated                                                                           │
│                                                                                                      │
│    21 │   │   response.status_code == 307                                                            │
│    22 │   │   and "/api/auth/signin" in response.headers.get("location", "")                         │
│    23 │   ):                                                                                         │
│ ❱  24 │   │   raise UnauthenticatedError(                                                            │
│    25 │   │   │   "Unauthenticated request. Please check your API key or login status."
│    26 │   │   )                                                                                      │
│    27                                                                                                │
│                                                                                                      │
│ ╭───────────────── locals ─────────────────╮                                                         │
│ │ response = <Response [401 Unauthorized]> │                                                         │
│ ╰──────────────────────────────────────────╯                                                         │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────╯
UnauthenticatedError: Unauthenticated request. Please check your API key or login status.

The above exception was the direct cause of the following exception:

╭───────────────────────────────── Traceback (most recent call last) ──────────────────────────────────╮
│ /Users/ryanholinshead/Projects/mcp-agent/src/mcp_agent/cli/cloud/commands/deploy/main.py:175 in      │
│ deploy_config                                                                                        │
│                                                                                                      │
│   172 │   │   │   │   │   f"Found existing app with ID: {app_id} for name '{app_name}'"
│   173 │   │   │   │   )                                                                              │
│   174 │   │   except UnauthenticatedError as e:                                                      │
│ ❱ 175 │   │   │   raise CLIError(                                                                    │
│   176 │   │   │   │   "Invalid API key for deployment. Run 'mcp-agent login' or set MCP_API_KE       │
│   177 │   │   │   ) from e                                                                           │
│   178 │   │   except Exception as e:                                                                 │
│                                                                                                      │
│ ╭───────────────────────────────────────────── locals ─────────────────────────────────────────────╮ │
│ │           api_key = 'TEST'                                                                       │ │
│ │           api_url = 'https://mcp-agent.com/api'                                                  │ │
│ │   app_description = None                                                                         │ │
│ │          app_name = 'TemporalExample'                                                            │ │
│ │        config_dir = PosixPath('/Users/ryanholinshead/Projects/mcp-agent/examples/temporal')      │ │
│ │       config_file = PosixPath('/Users/ryanholinshead/Projects/mcp-agent/examples/temporal/mcp_a… │ │
│ │               ctx = <click.core.Context object at 0x17a96cca0>                                   │ │
│ │           dry_run = False                                                                        │ │
│ │ effective_api_key = 'TEST'                                                                       │ │
│ │ effective_api_url = 'https://mcp-agent.com/api'                                                  │ │
│ │    mcp_app_client = <mcp_agent.cli.mcp_app.api_client.MCPAppClient object at 0x17a96d0c0>        │ │
│ │        no_secrets = False                                                                        │ │
│ │   non_interactive = False                                                                        │ │
│ │      provided_key = 'TEST'                                                                       │ │
│ │      secrets_file = PosixPath('/Users/ryanholinshead/Projects/mcp-agent/examples/temporal/mcp_a… │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────╯
CLIError: Invalid API key for deployment. Run 'mcp-agent login' or set MCP_API_KEY environment variable 
with new API key.

The above exception was the direct cause of the following exception:

╭───────────────────────────────── Traceback (most recent call last) ──────────────────────────────────╮
│ /Users/ryanholinshead/Projects/mcp-agent/src/mcp_agent/cli/cloud/commands/deploy/main.py:288 in      │
│ deploy_config                                                                                        │
│                                                                                                      │
│   285 │   │   │   import traceback                                                                   │
│   286 │   │   │                                                                                      │
│   287 │   │   │   typer.echo(traceback.format_exc())                                                 │
│ ❱ 288 │   │   raise CLIError(f"Deployment failed: {str(e)}") from e                                  │
│   289                                                                                                │
│   290                                                                                                │
│   291 def get_config_files(config_dir: Path, no_secrets: bool) -> tuple[Path, Optional[Path]]:       │
│                                                                                                      │
│ ╭───────────────────────────────────────────── locals ─────────────────────────────────────────────╮ │
│ │           api_key = 'TEST'                                                                       │ │
│ │           api_url = 'https://mcp-agent.com/api'                                                  │ │
│ │   app_description = None                                                                         │ │
│ │          app_name = 'TemporalExample'                                                            │ │
│ │        config_dir = PosixPath('/Users/ryanholinshead/Projects/mcp-agent/examples/temporal')      │ │
│ │       config_file = PosixPath('/Users/ryanholinshead/Projects/mcp-agent/examples/temporal/mcp_a… │ │
│ │               ctx = <click.core.Context object at 0x17a96cca0>                                   │ │
│ │           dry_run = False                                                                        │ │
│ │ effective_api_key = 'TEST'                                                                       │ │
│ │ effective_api_url = 'https://mcp-agent.com/api'                                                  │ │
│ │    mcp_app_client = <mcp_agent.cli.mcp_app.api_client.MCPAppClient object at 0x17a96d0c0>        │ │
│ │        no_secrets = False                                                                        │ │
│ │   non_interactive = False                                                                        │ │
│ │      provided_key = 'TEST'                                                                       │ │
│ │      secrets_file = PosixPath('/Users/ryanholinshead/Projects/mcp-agent/examples/temporal/mcp_a… │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────╯
CLIError: Deployment failed: Invalid API key for deployment. Run 'mcp-agent login' or set MCP_API_KEY 
environment variable with new API key.

Cloud CLI commands already nicely handle known/expected CLIError exceptions:

uv run mcp-agent cloud deploy TemporalExample -c examples/temporal --api-key="TEST"
╭───────────────────────────────────── MCP Agent Deployment ──────────────────────────────────────╮
│ Configuration: /Users/ryanholinshead/Projects/mcp-agent/examples/temporal/mcp_agent.config.yaml │
│ Secrets file: /Users/ryanholinshead/Projects/mcp-agent/examples/temporal/mcp_agent.secrets.yaml │
│ Mode: DEPLOY                                                                                    │
╰────────────────────────────────────────── LastMile AI ──────────────────────────────────────────╯
INFO: Using API at https://mcp-agent.com/api
INFO: Checking for existing app ID for 'TemporalExample'...
ERROR: Deployment failed: Invalid API key for deployment. Run 'mcp-agent login' or set MCP_API_KEY 
environment variable with new API key.

To fix, use the same helpful typer with invoke wrapper for the CLI commands.

Testing

uv run mcp-agent deploy TemporalExample -c examples/temporal --api-key="TEST"
╭───────────────────────────────────── MCP Agent Deployment ──────────────────────────────────────╮
│ Configuration: /Users/ryanholinshead/Projects/mcp-agent/examples/temporal/mcp_agent.config.yaml │
│ Secrets file: /Users/ryanholinshead/Projects/mcp-agent/examples/temporal/mcp_agent.secrets.yaml │
│ Mode: DEPLOY                                                                                    │
╰────────────────────────────────────────── LastMile AI ──────────────────────────────────────────╯
INFO: Using API at https://mcp-agent.com/api
INFO: Checking for existing app ID for 'TemporalExample'...
ERROR: Deployment failed: Invalid API key for deployment. Run 'mcp-agent login' or set MCP_API_KEY 
environment variable with new API key.

Summary by CodeRabbit

  • New Features

    • Improved CLI UX: shows help on incorrect usage, clearer user-facing error messages with consistent exit codes, and prints CLI version info.
    • Cloud commands exposed at the top-level for easier access.
  • Refactor

    • Centralized CLI group and error-handling into a shared utility for consistent behavior across main and cloud commands.
    • Top-level CLI now uses the unified command-group for more predictable command wiring.

@rholinshead rholinshead requested a review from saqadri September 10, 2025 17:00
@coderabbitai
Copy link

coderabbitai bot commented Sep 10, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Replaces local HelpfulTyperGroup with a shared implementation in mcp_agent.cli.utils.typer_utils; updates top-level and cloud Typer apps to use it, centralizes CLI error handling and logging to file, adds run() wrappers that log and surface user-friendly errors, and makes small formatting edits in server token parsing.

Changes

Cohort / File(s) Summary
New shared CLI utilities
src/mcp_agent/cli/utils/typer_utils.py
Added HelpfulTyperGroup(TyperGroup) with resolve_command and invoke to handle UsageError and CLIError uniformly (renders help/error output and exits with appropriate codes).
Cloud CLI adoption & wiring
src/mcp_agent/cli/cloud/main.py
Removed local HelpfulTyperGroup and click/rich usage; now imports HelpfulTyperGroup from utils; reworked command imports/registrations (apps, app, workflows, servers, auth, logger); added version callback and run() wrapper with broad exception handling and file-only logging setup.
Top-level CLI adoption & wiring
src/mcp_agent/cli/main.py
Switched top-level Typer to cls=HelpfulTyperGroup; added run() wrapper that logs and prints errors via print_error; registered cloud command aliases (deploy/login/logout/whoami) under cloud.
Server request handlers (formatting only)
src/mcp_agent/server/app_server.py
Minor formatting change: expanded bearer-token extraction ternary into multi-line conditional inside parentheses in three handlers; no behavioral changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User
  participant App as Typer App
  participant Group as HelpfulTyperGroup
  participant Cmd as Command
  participant UX as print_error / logger

  User->>App: invoke CLI with args
  App->>Group: resolve_command(args)
  alt UsageError / command not found
    Group-->>User: show help + print usage error
    Group-->>App: exit(2)
  else Command resolved
    App->>Group: invoke(ctx)
    Group->>Cmd: execute()
    alt raises CLIError
      Cmd-->>Group: CLIError
      Group->>UX: log & print_error()
      Group-->>App: exit(error.exit_code)
    else raises other Exception
      Cmd-->>Group: Exception
      Group->>UX: log & print_error()
      Group-->>App: exit(1)
    else Success
      Cmd-->>Group: return
      Group-->>App: finish
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • saqadri

Poem

I nibble keys and tidy hops,
A HelpfulGroup in shared workshops.
Errors tucked in file-bound nests,
Commands aligned and nicely dressed.
Thump-thump — the CLI now rests. 🐇

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f34f979 and 913df1a.

📒 Files selected for processing (4)
  • src/mcp_agent/cli/cloud/main.py (2 hunks)
  • src/mcp_agent/cli/main.py (3 hunks)
  • src/mcp_agent/cli/utils/typer_utils.py (1 hunks)
  • src/mcp_agent/server/app_server.py (3 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/cli-invoke-wrapper

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Collaborator

@saqadri saqadri 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!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/mcp_agent/cli/cloud/main.py (1)

197-203: Pipeline failure: undefined name print_error in run().

print_error is used but not imported, causing F821 and breaking CI.

Add the missing import (see next comment for diff).

🧹 Nitpick comments (1)
src/mcp_agent/cli/utils/typer_utils.py (1)

16-31: Send help to stderr on usage errors.

Direct the help text to stderr to match common CLI conventions when a usage error occurs.

-            click.echo(ctx.get_help())
+            click.echo(ctx.get_help(), err=True)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad946ab and f34f979.

📒 Files selected for processing (3)
  • src/mcp_agent/cli/cloud/main.py (1 hunks)
  • src/mcp_agent/cli/main.py (1 hunks)
  • src/mcp_agent/cli/utils/typer_utils.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/mcp_agent/cli/utils/typer_utils.py (2)
src/mcp_agent/cli/exceptions.py (1)
  • CLIError (4-9)
src/mcp_agent/cli/utils/ux.py (1)
  • print_error (80-91)
src/mcp_agent/cli/main.py (1)
src/mcp_agent/cli/utils/typer_utils.py (1)
  • HelpfulTyperGroup (13-40)
src/mcp_agent/cli/cloud/main.py (1)
src/mcp_agent/cli/utils/typer_utils.py (1)
  • HelpfulTyperGroup (13-40)
🪛 GitHub Actions: Pull Request Checks
src/mcp_agent/cli/cloud/main.py

[error] 202-202: F821 Undefined name print_error (undefined name) in code. Ensure 'print_error' is defined or imported.

🔇 Additional comments (2)
src/mcp_agent/cli/main.py (1)

34-42: Good move: root CLI now uses the helpful invoke wrapper.

Using cls=HelpfulTyperGroup aligns top-level error UX with cloud subcommands.

src/mcp_agent/cli/cloud/main.py (1)

39-39: Add missing print_error import
In src/mcp_agent/cli/cloud/main.py (around line 39), add:

 from mcp_agent.cli.utils.typer_utils import HelpfulTyperGroup
+from mcp_agent.cli.utils.ux import print_error

This ensures the call to print_error in run() is defined.

Comment on lines 33 to 40
def invoke(self, ctx):
try:
return super().invoke(ctx)
except CLIError as e:
# Handle CLIError cleanly - show error message and exit
logging.error(f"CLI error: {str(e)}")
print_error(str(e))
ctx.exit(e.exit_code) No newline at end of file
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid double-logging and handle unexpected exceptions here for consistent UX.

Prevents duplicate log lines and ensures unhandled exceptions are rendered cleanly across all CLIs without per-file wrappers.

     def invoke(self, ctx):
         try:
             return super().invoke(ctx)
         except CLIError as e:
-            # Handle CLIError cleanly - show error message and exit
-            logging.error(f"CLI error: {str(e)}")
-            print_error(str(e))
-            ctx.exit(e.exit_code)
+            # Expected CLI errors: log once and print cleanly
+            logging.error(str(e))
+            print_error(str(e), log=False)
+            ctx.exit(e.exit_code)
+        except Exception as e:
+            # Unexpected errors: include traceback in logs, concise console message
+            logging.exception("Unhandled exception in CLI")
+            print_error(f"An unexpected error occurred: {e}", log=False)
+            ctx.exit(1)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def invoke(self, ctx):
try:
return super().invoke(ctx)
except CLIError as e:
# Handle CLIError cleanly - show error message and exit
logging.error(f"CLI error: {str(e)}")
print_error(str(e))
ctx.exit(e.exit_code)
def invoke(self, ctx):
try:
return super().invoke(ctx)
except CLIError as e:
# Expected CLI errors: log once and print cleanly
logging.error(str(e))
print_error(str(e), log=False)
ctx.exit(e.exit_code)
except Exception as e:
# Unexpected errors: include traceback in logs, concise console message
logging.exception("Unhandled exception in CLI")
print_error(f"An unexpected error occurred: {e}", log=False)
ctx.exit(1)
🤖 Prompt for AI Agents
In src/mcp_agent/cli/utils/typer_utils.py around lines 33 to 40, the current
invoke() handler logs CLIError via logging.error and also prints the error which
can produce duplicate messages and it doesn't handle unexpected exceptions;
change it so the CLIError branch only prints a user-facing message
(print_error(str(e))) and exits with ctx.exit(e.exit_code) (remove the
logging.error call to avoid double-logging), and add a second broad except
Exception as e that calls logging.exception("Unhandled CLI exception") to
capture the traceback, prints a concise user-facing message (e.g.
print_error("Unexpected error occurred")) and exits with a non-zero code
(ctx.exit(1)).

@rholinshead rholinshead merged commit 7a16f22 into main Sep 10, 2025
7 of 8 checks passed
andrew-lastmile added a commit that referenced this pull request Sep 11, 2025
* Temporarily exclude CLI from test coverage (#429)

### TL;DR

Exclude CLI code from test coverage metrics for now. Will add tests when we're done sprinting 10000 mph 

![Added via Giphy](https://media4.giphy.com/media/v1.Y2lkPWM5NDg3NzQzOTNudmtpNXcyazNnZWo2enIzem5neXR2a3l0cGx5aWFlbDB6ZTA1dyZlcD12MV9naWZzX3NlYXJjaCZjdD1n/sRKg9r2YWeCTG5JTTo/giphy.gif)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

* **Tests**
  * Adjusted test coverage collection to exclude non-critical CLI components, resulting in more accurate coverage metrics for core functionality.

* **Chores**
  * Updated coverage reporting configuration to align with the new exclusion rules, ensuring consistent results across local and CI runs.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

* Add workflow commands to CLI (#424)

### TL;DR

Added workflow management commands to the MCP Agent CLI, including describe, suspend, resume, and cancel operations.

### What changed?

- Added four new workflow management commands:
    - `describe_workflow`: Shows detailed information about a workflow execution
    - `suspend_workflow`: Pauses a running workflow execution
    - `resume_workflow`: Resumes a previously suspended workflow
    - `cancel_workflow`: Permanently stops a workflow execution
- Implemented corresponding API client methods in `WorkflowAPIClient`:
    - `suspend_workflow`
    - `resume_workflow`
    - `cancel_workflow`
- Updated the CLI structure to expose these commands under `mcp-agent cloud workflows`
- Added an alias for `describe_workflow` as `status` for backward compatibility

### How to test?

Test the new workflow commands with a running workflow:

```
# Get workflow details
mcp-agent cloud workflows describe run_abc123
mcp-agent cloud workflows status run_abc123  # alias

# Suspend a workflow
mcp-agent cloud workflows suspend run_abc123

# Resume a workflow (with optional payload)
mcp-agent cloud workflows resume run_abc123
mcp-agent cloud workflows resume run_abc123 --payload '{"data": "value"}'

# Cancel a workflow (with optional reason)
mcp-agent cloud workflows cancel run_abc123
mcp-agent cloud workflows cancel run_abc123 --reason "User requested cancellation"
```

### Why make this change?

These commands provide essential workflow lifecycle management capabilities to users, allowing them to monitor and control workflow executions through the CLI. The ability to suspend, resume, and cancel workflows gives users more control over long-running operations and helps manage resources more efficiently.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- New Features
  - Introduced “workflows” CLI group with commands: describe (alias: status), resume, suspend, and cancel.
  - Describe supports text, JSON, and YAML output; all commands work with server ID or URL and include improved error messages.

- Refactor
  - Renamed CLI group from “workflow” to “workflows” and reorganized command registrations.
  - Consolidated internal utility imports (no behavior change).

- Chores
  - Updated module descriptions.
  - Removed legacy workflow status package/exports in favor of the new workflows commands.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

* add servers workflow subcommand (#428)

# Add servers workflows subcommand

This PR adds a new `workflows` subcommand to the `mcp-agent cloud servers` command that allows users to list workflows associated with a specific server. The command supports:

- Filtering by workflow status
- Limiting the number of results
- Multiple output formats (text, JSON, YAML)
- Accepting server IDs, app config IDs, or server URLs as input

Examples:
```
mcp-agent cloud servers workflows app_abc123
mcp-agent cloud servers workflows https://server.example.com --status running
mcp-agent cloud servers workflows apcnf_xyz789 --limit 10 --format json
```

The PR also cleans up the examples in the existing workflow commands and adds the necessary API client support for listing workflows.

* add workflow list and runs (#430)

### TL;DR

Reorganized workflow commands

`mcp-agent cloud workflows runs`
`mcp-agent cloud workflows list`
`mcp-agent cloud server workflows` (alias of workflows list)

### What changed?

- Moved `list_workflows_for_server` from the servers module to the workflows module as `list_workflow_runs`
- Added new workflow commands: `list_workflows` and `list_workflow_runs`
- Updated CLI command structure to make workflows commands more intuitive
- Applied consistent code formatting with black across all server and workflow related files

### How to test?

Test the new and reorganized workflow commands:

```bash
# List available workflow definitions
mcp-agent cloud workflows list app_abc123

# List workflow runs (previously under servers workflows)
mcp-agent cloud workflows runs app_abc123

# Test with different output formats
mcp-agent cloud workflows list app_abc123 --format json
mcp-agent cloud workflows runs app_abc123 --format yaml

# Verify existing commands still work
mcp-agent cloud servers list
mcp-agent cloud workflows describe app_abc123 run_xyz789
```

* [ez] Move deploy command to cloud namespace (#431)

### TL;DR

Added `cloud deploy` command as an alias for the existing `deploy` command.

* First pass at implementing the mcp-agent CLI (#409)

* Initial scaffolding

* initial CLI

* checkpoint

* checkpoint 2

* various updates to cli

* fix lint and format

* fix: should load secrets.yaml template instead when running init cli command

* fix: prevent None values in either mcp-agent secrets and config yaml files from overwriting one another when merging both

* fix: when running config check, use get_settings() instead of Settings() to ensure settings are loaded.

* fix: handle None values for servers in MCPSettings so it defaults to empty dict and update secrets.yaml template so it does not overwrite mcp servers in config

* Inform users to save and close editor to continue when running config edit command

* fix: Update openai, anthropic and azure regex for keys cli command

* Sort model list by provider and model name

* Add filtering support for models list cli command

* disable untested commands

* lint, format, gen_schema

* get rid of accidental otlp exporter changes from another branch

* get rid of accidental commit from other branch

---------

Co-authored-by: StreetLamb <[email protected]>

* Docs MVP (#436)

* Initial scaffolding

* initial CLI

* checkpoint

* checkpoint 2

* various updates to cli

* fix lint and format

* fix: should load secrets.yaml template instead when running init cli command

* fix: prevent None values in either mcp-agent secrets and config yaml files from overwriting one another when merging both

* fix: when running config check, use get_settings() instead of Settings() to ensure settings are loaded.

* fix: handle None values for servers in MCPSettings so it defaults to empty dict and update secrets.yaml template so it does not overwrite mcp servers in config

* Inform users to save and close editor to continue when running config edit command

* fix: Update openai, anthropic and azure regex for keys cli command

* Sort model list by provider and model name

* Add filtering support for models list cli command

* disable untested commands

* Fixes to docs

* Updating the main.py and !developer_secrets for secrets

* updating python entry files to main.py

* Fix tracer.py

---------

Co-authored-by: StreetLamb <[email protected]>
Co-authored-by: Andrew Hoh <[email protected]>

* fix: max complete token for openai gen structured (#438)

* Fix regression in CLI ("cloud cloud")

* docs fixes

* Fix top-level cli cloud commands (deploy, login, etc)

* Add eager tool validation to ensure json serializability of input params/result types

* More docs updates

* Refactor workflow runs list to use MCP tool calls (#439)

### TL;DR

Refactored the workflow runs listing command to use MCP tool calls instead of direct API client calls.

### What changed?

- Replaced the direct API client approach with MCP tool calls to retrieve workflow runs
- Added a new `_list_workflow_runs_async` function that uses the MCP App and gen_client to communicate with the server
- Improved status filtering and display logic to work with both object and dictionary response formats
- Enhanced error handling and formatting of workflow run information
- Updated the workflow data processing to handle different response formats more robustly

### How to test?

```bash
# List workflow runs from a server
mcp-agent cloud workflows runs <server_id_or_url>

# Filter by status
mcp-agent cloud workflows runs <server_id_or_url> --status running

# Limit results
mcp-agent cloud workflows runs <server_id_or_url> --limit 10

# Change output format
mcp-agent cloud workflows runs <server_id_or_url> --format json
```

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- New Features
  - Add status filtering for workflow runs, with common aliases (e.g., timeout → timed_out).
  - Add an optional limit to constrain the number of results.
  - Allow server selection via direct URL or config-based server ID.

- Refactor
  - Update text output: columns now show Workflow ID, Name, Status, Run ID, Created; Principal removed.
  - Improve date formatting and consistent JSON/YAML/Text rendering.

- Bug Fixes
  - Clearer error messages and safer handling when server info is missing or no data is returned.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

* Update workflows commands UI to be more consistant with the rest of the CLI (#432)

### TL;DR

Improved CLI workflow command output formatting with better visual indicators and consistent styling.

### How to test?

```
mcp-agent cloud workflows cancel <run-id>
mcp-agent cloud workflows describe <run-id>
mcp-agent cloud workflows resume <run-id>
```

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

* **Style**
  * Cancel workflow: added a blank line before the status and changed the success icon to 🚫 (yellow).
  * Describe workflow: replaced panel UI with a clean, header-based text layout (“🔍 Workflow Details”), showing name with colorized status and fields for Workflow ID, Run ID, and Created. Updated status indicators with emojis and colors; timestamp is now plain text on its own line.
  * Resume workflow: success message now applies consistent coloring to the entire line for improved readability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

* Feature: Update Workflow Tool Calls to Work with workflow_id (#435)

* Support for workflow_id and run_id

* Update temporal workflow registry

* tests

* Update LLMS.txt

* Fix config

* Return bool for cancel result

* Validate ids provided

* Fix cancel workflow id

* Fix workflows-resume response

* Add workflow-name specific resume and cancel tools

* Fix return type

* Fix examples

* Remove redundant workflows-{name}-tool tool calls

* Add _workflow_status back

* Use registry helper

* Changes from review

* Add back evaluator_optimizer enum fix

* Fix a hang that can happen at shutdown (#440)

* Fix a shutdown hang

* Fix tests

* fix taskgroup closed in a different context than when it was started in error

* some PR feedback fixes

* PR feedback

* Fix random failures of server aggregator not found for agent in temporal (#441)

* Fix a shutdown hang

* Fix tests

* fix taskgroup closed in a different context than when it was started in error

* some PR feedback fixes

* Fix random failures of server aggregator not found for agent in temporal environment

* Bump pyproject version

* Fix gateway URL resolution (#443)

* Fix gateway URL resolution

Removed incorrect dependence on ServerRegistry for gateway URLs; the gateway is not an MCP server.
App server (src/mcp_agent/server/app_server.py) builds workflow memo with:
- gateway_url precedence: X-MCP-Gateway-URL or X-Forwarded-Url → reconstruct X-Forwarded-Proto/Host/Prefix → request.base_url → MCP_GATEWAY_URL env.
- gateway_token precedence: X-MCP-Gateway-Token → MCP_GATEWAY_TOKEN env.
Worker-side (SystemActivities/SessionProxy) uses memo.gateway_url and gateway_token; falls back to worker env.
Client proxy helpers (src/mcp_agent/mcp/client_proxy.py):
- _resolve_gateway_url: explicit param → context → env → local default.
- Updated public signatures to drop server_registry parameter.

* Cloud/deployable temporal example (#395)

* Move workflows to workflows.py file

* Fix router example

* Add remaining dependencies

* Update orchestrator to @app.async_tool example

* Changes from review

* Fix interactive_workflow to be runnable via tool

* Fix resume tool params

* Fix: Use helpful typer and invoke for root cli commands (#444)

* Use helpful typer and invoke for root cli commands

* Fix lint

* Fix enum check (#445)

* Fix/swap relative mcp agent dependency on deploy (#446)

* Update wrangler wrapper to handle requirements.txt processing

* Fix backup handling

* pass api key to workflow (#447)

* pass api key to workflow

* guard against settings not existing

---------

Co-authored-by: John Corbett <[email protected]>
Co-authored-by: Sarmad Qadri <[email protected]>
Co-authored-by: StreetLamb <[email protected]>
Co-authored-by: Yi <[email protected]>
Co-authored-by: Ryan Holinshead <[email protected]>
Co-authored-by: roman-van-der-krogt <[email protected]>
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.

4 participants