Skip to content

Conversation

@StreetLamb
Copy link
Collaborator

@StreetLamb StreetLamb commented Sep 12, 2025

Summary by CodeRabbit

  • New Features

    • Added --servers option to invoke for targeting specific MCP servers.
    • Expanded CLI with chat, dev, invoke, serve, server, build, logs, doctor, and configure commands; enabled shell completion and no-args help.
    • Dev now supports live reload via file-watching (when available) with clearer process lifecycle messages.
    • Serve shows an HTTP endpoint URL and improved startup logs.
  • Changes

    • Console output tweaks: color disabled in invoke, extra spacing, notice when no MCP servers configured, clearer success messages, immediate exit on stop.
  • Chores

    • Dependency updates: typer >=0.15.3; optional watchdog >=6.0.0.

@StreetLamb StreetLamb requested a review from saqadri September 12, 2025 15:34
@coderabbitai
Copy link

coderabbitai bot commented Sep 12, 2025

Walkthrough

Bumps CLI dependencies, wires multiple new Typer subcommands, adds a subprocess-based dev runner with optional watchdog live-reload, exposes HTTP endpoint reporting and immediate exit on serve shutdown, switches server config retrieval and explicit logging cleanup, tweaks build reporting, and adds a --servers option to invoke.

Changes

Cohort / File(s) Summary
Dependencies
pyproject.toml
Bump typer to >=0.15.3; add optional watchdog>=6.0.0 to CLI extras.
CLI wiring
src/mcp_agent/cli/main.py
Enable completion and no-args-help; mount chat, dev, invoke, serve, server, build, logs, doctor, configure subcommands.
Build reporting
src/mcp_agent/cli/commands/build.py
When no MCP servers configured, print a yellow notice "No MCP servers found in configuration" and a blank line.
Dev runner & live reload
src/mcp_agent/cli/commands/dev.py
Replace async in-process runner with subprocess-based spawn via sys.executable; integrate optional watchdog to watch non-directory file changes and restart child; manage child lifecycle (terminate/wait/kill) on changes or Ctrl+C; fallback to single-run if watchdog missing; inherit stdio.
Invoke servers option & output
src/mcp_agent/cli/commands/invoke.py
Add --servers option (comma-separated names) parsed into list and passed as server_names; switch Console() to Console(color_system=None); print result blocks with extra blank lines.
Serve endpoints & shutdown
src/mcp_agent/cli/commands/serve.py
Add HTTP Endpoint row for HTTP transport (http://<host>:<port>/mcp); use mcp.streamable_http_app for HTTP and mcp.sse_app for SSE; log HTTP URL; on SIGINT/SIGTERM schedule shutdown and call os._exit(0) for immediate exit.
Server config & cleanup
src/mcp_agent/cli/commands/server.py
Use get_settings() from mcp_agent.config instead of direct Settings instantiation; call cleanup_context(shutdown_logger=True) after probe/test; adjust success message to include extra newline.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant CLI as dev command
  participant Watcher as watchdog (optional)
  participant Proc as Child Process

  User->>CLI: mcp dev <script>
  alt watchdog available
    CLI->>Watcher: start watching (non-dir changes)
    CLI->>Proc: spawn subprocess (sys.executable <script>)
    loop monitor
      CLI->>Proc: poll()
      alt exited
        Proc-->>CLI: exit code
        CLI->>User: print "Process exited with code X"
      end
      Watcher-->>CLI: file change event
      CLI->>Proc: terminate (wait up to 5s, kill if needed)
      CLI->>Proc: restart subprocess
    end
  else no watchdog
    CLI->>Proc: spawn subprocess
    Proc-->>CLI: exit code (no reload)
  end
  User-->>CLI: Ctrl+C
  CLI->>Proc: terminate (kill if needed)
  CLI-->>User: stopping message
Loading
sequenceDiagram
  autonumber
  actor User
  participant CLI as serve command
  participant Uvicorn
  participant App as mcp apps

  User->>CLI: mcp serve --transport http|sse
  alt transport=http
    CLI->>App: select mcp.streamable_http_app
    CLI->>Uvicorn: run host:port
    CLI-->>User: print HTTP Endpoint http://host:port/mcp
  else transport=sse
    CLI->>App: select mcp.sse_app
    CLI->>Uvicorn: run host:port
    CLI-->>User: print SSE URL
  end
  Note over CLI,Uvicorn: On SIGINT/SIGTERM: schedule shutdown then os._exit(0)
Loading
sequenceDiagram
  autonumber
  actor User
  participant CLI as invoke command
  participant Agent as LLM/Agent Factory

  User->>CLI: mcp invoke --agent ... --servers="a,b , c"
  CLI->>CLI: parse servers -> ["a","b","c"]
  CLI->>Agent: create with server_names=["a","b","c"]
  Agent-->>CLI: responses
  CLI-->>User: print messages/payloads with extra blank lines
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • rholinshead
  • saqadri
  • petersonbill64

Pre-merge checks (3 passed)

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Second pass at implementing the mcp-agent CLI" accurately and concisely reflects the primary work in this changeset, which adds and wires multiple CLI subcommands and updates numerous CLI command modules (main.py, dev, invoke, serve, server, build, etc.). It is focused on the main change, clear to reviewers, and free of noisy details.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

Poem

A rabbit taps keys with a joyful bop,
New commands hop in and never stop.
Watchdog sniffs changes, we restart and run,
Servers chosen now — oh what fun!
Quick exit on signals, neat and spry — hop, ship, bye-bye! 🐇

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.

✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/cli_mvp_2

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.

@saqadri
Copy link
Collaborator

saqadri commented Sep 12, 2025

🔥 🔥 🔥

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.

Awesome work @StreetLamb !! Ship it 🚀

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: 2

Caution

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

⚠️ Outside diff range comments (3)
pyproject.toml (1)

110-121: Fix package discovery: hyphen vs underscore breaks setuptools find.

include = ["mcp-agent"] won’t match the Python package (module names can’t contain '-') and will exclude your code at build time. Align both include and package-data keys to mcp_agent.

 [tool.setuptools.packages.find]
-include = ["mcp-agent"]
+include = ["mcp_agent"]

 [tool.setuptools.package-data]
-mcp-agent = [ # TODO: should this be mcp_agent?
+mcp_agent = [
src/mcp_agent/cli/commands/server.py (1)

541-543: Zip Slip risk when extracting .dxt archives.

zipfile.extractall allows path traversal (e.g., ../../etc). Sanitize members before extracting.

-                with zipfile.ZipFile(str(dxt_path), "r") as zf:
-                    zf.extractall(base_extract_dir)
+                def _safe_members(zf):
+                    for m in zf.infolist():
+                        p = _Path(m.filename)
+                        if p.is_absolute() or ".." in p.parts:
+                            continue
+                        yield m
+                with zipfile.ZipFile(str(dxt_path), "r") as zf:
+                    zf.extractall(base_extract_dir, members=list(_safe_members(zf)))
src/mcp_agent/cli/main.py (1)

19-28: Guard cloud imports and alias registration to avoid import-time crashes when cloud extras are absent

from mcp_agent.cli.cloud.commands import ... is unguarded and will blow up on setups without cloud installed, even though cloud_app import is wrapped. Also, alias commands are always registered. Make both conditional.

Apply:

-# Mount existing cloud CLI
-try:
-    from mcp_agent.cli.cloud.main import app as cloud_app  # type: ignore
-except Exception:  # pragma: no cover - cloud is optional for non-cloud development
-    cloud_app = typer.Typer(help="Cloud commands (unavailable)")
-
-# Local command groups (scaffolded)
-from mcp_agent.cli.cloud.commands import deploy_config, login, logout, whoami
+# Mount existing cloud CLI
+try:
+    from mcp_agent.cli.cloud.main import app as cloud_app  # type: ignore
+    from mcp_agent.cli.cloud.commands import (  # type: ignore
+        deploy_config,
+        login,
+        logout,
+        whoami,
+    )
+    _CLOUD_AVAILABLE = True
+except Exception:  # pragma: no cover - cloud is optional for non-cloud development
+    cloud_app = typer.Typer(help="Cloud commands (unavailable)")
+    deploy_config = login = logout = whoami = None  # type: ignore
+    _CLOUD_AVAILABLE = False
-# Mount cloud commands
-app.add_typer(cloud_app, name="cloud", help="MCP Agent Cloud commands")
-
-# Register some key cloud commands directly as top-level commands
-app.command("deploy", help="Deploy an MCP agent (alias for 'cloud deploy')")(
-    deploy_config
-)
-app.command(
-    "login", help="Authenticate to MCP Agent Cloud API (alias for 'cloud login')"
-)(login)
-app.command(
-    "whoami", help="Print current identity and org(s) (alias for 'cloud whoami')"
-)(whoami)
-app.command("logout", help="Clear credentials (alias for 'cloud logout')")(logout)
+# Mount cloud commands
+if _CLOUD_AVAILABLE:
+    app.add_typer(cloud_app, name="cloud", help="MCP Agent Cloud commands")
+    # Register key cloud commands as top-level aliases
+    app.command("deploy", help="Deploy an MCP agent (alias for 'cloud deploy')")(deploy_config)  # type: ignore[arg-type]
+    app.command("login", help="Authenticate to MCP Agent Cloud API (alias for 'cloud login')")(login)  # type: ignore[arg-type]
+    app.command("whoami", help="Print current identity and org(s) (alias for 'cloud whoami')")(whoami)  # type: ignore[arg-type]
+    app.command("logout", help="Clear credentials (alias for 'cloud logout')")(logout)  # type: ignore[arg-type]
+else:
+    app.add_typer(cloud_app, name="cloud", help="Cloud commands (unavailable)")

Also applies to: 150-164

🧹 Nitpick comments (13)
src/mcp_agent/cli/commands/build.py (2)

105-112: Tighten secrets file permission check (include group perms).

Current check only flags world-readable (others). Secrets should also not be group-readable/writable. Consider treating any group/other rwx bits as insecure.

-        if "secrets" in path.name:
-            stat_info = path.stat()
-            mode = stat_info.st_mode
-            # Check if others have read access
-            result["secure"] = not bool(mode & 0o004)
-            result["permissions"] = oct(mode)[-3:]
+        if "secrets" in path.name:
+            stat_info = path.stat()
+            mode = stat_info.st_mode
+            # Secure if no group/other permissions at all (owner-only)
+            result["secure"] = (mode & 0o077) == 0
+            result["permissions"] = oct(mode & 0o777)[-3:]

351-360: Optional: add actionable hint when no servers.

When no servers are configured, consider printing a one-liner to guide users, e.g., “mcp-agent server recipes” or “mcp-agent server discover”.

src/mcp_agent/cli/commands/invoke.py (3)

30-33: servers option parsing is fine; consider list-type option for UX.

Current comma-split works. As a polish, Typer can accept multiple via List[str] (e.g., --server name1 --server name2) to avoid comma edge cases and allow shell completion per item.

-servers: Optional[str] = typer.Option(
-    None, "--servers", help="Comma-separated list of MCP server names"
-),
+servers: Optional[list[str]] = typer.Option(
+    None, "--server", help="Repeatable: MCP server name", rich_help_panel="MCP"
+),
@@
-                server_list = servers.split(",") if servers else []
-                server_list = [s.strip() for s in server_list if s.strip()]
+                server_list = [s.strip() for s in (servers or []) if s.strip()]

Also applies to: 56-61


27-27: Avoid shadowing built-in 'vars'.

Renaming to inputs_json or structured_vars avoids overshadowing vars().

-    vars: Optional[str] = typer.Option(None, "--vars", help="JSON structured inputs"),
+    inputs_json: Optional[str] = typer.Option(None, "--vars", help="JSON structured inputs"),
@@
-        payload = json.loads(vars) if vars else {}
+        payload = json.loads(inputs_json) if inputs_json else {}

108-114: Minor import hygiene.

Path is used in _run; importing it at the top improves readability and static analysis.

src/mcp_agent/cli/commands/serve.py (2)

309-316: Adjust printed scheme when TLS is enabled.

If ssl_certfile/keyfile are set, print https:// in URLs.

-                console.print(f"[bold]URL:[/bold] http://{host}:{port or 8000}")
+                scheme = "https" if (ssl_certfile and ssl_keyfile) else "http"
+                console.print(f"[bold]URL:[/bold] {scheme}://{host}:{port or 8000}")
@@
-                    console.print(f"[bold]SSE:[/bold] http://{host}:{port or 8000}/sse")
+                    console.print(f"[bold]SSE:[/bold] {scheme}://{host}:{port or 8000}/sse")
@@
-                        f"[bold]HTTP:[/bold] http://{host}:{port or 8000}/mcp"
+                        f"[bold]HTTP:[/bold] {scheme}://{host}:{port or 8000}/mcp"

290-299: Add a getattr fallback to mcp.app for streamable_http_app / sse_app

FastMCP exposes streamable_http_app and sse_app (added with streamable‑http support), but keep a getattr fallback to mcp.app for compatibility.

-                uvicorn_config = uvicorn.Config(
-                    mcp.streamable_http_app if transport == "http" else mcp.sse_app,
+                app_attr = "streamable_http_app" if transport == "http" else "sse_app"
+                app_callable = getattr(mcp, app_attr, None) or getattr(mcp, "app", None)
+                uvicorn_config = uvicorn.Config(
+                    app_callable,
src/mcp_agent/cli/commands/server.py (1)

763-765: Same Rich 'end' kw check applies here.

Ensure Console.print accepts end="\n\n" in your pinned Rich. Use explicit blank prints otherwise.

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

60-66: Avoid double “no subcommand” messaging

With no_args_is_help=True, Typer already shows help when no subcommand is given. The manual overview block becomes redundant and may duplicate output. Suggest removing it.

-    # If no subcommand given, show brief overview
-    if ctx.invoked_subcommand is None:
-        console.print("mcp-agent - Model Context Protocol agent CLI\n")
-        console.print("Run 'mcp-agent --help' to see all commands.")

Also applies to: 120-124

src/mcp_agent/cli/commands/dev.py (4)

24-26: Honor global --no-color (and other globals) passed from main

The dev command doesn’t accept ctx, so global options (e.g., --no-color) aren’t applied. Accept typer.Context and toggle the console accordingly.

-@app.callback(invoke_without_command=True)
-def dev(script: Path = typer.Option(Path("agent.py"), "--script")) -> None:
+@app.callback(invoke_without_command=True)
+def dev(
+    ctx: typer.Context,
+    script: Path = typer.Option(Path("agent.py"), "--script"),
+) -> None:
     """Run the user's app script with optional live reload and preflight checks."""
+    # Honor global color setting propagated by main
+    if getattr(ctx, "obj", None) and not ctx.obj.get("color", True):
+        console.no_color = True

28-39: More robust stdio command check (handle str or list command forms)

Some configs use command: "tool" while others use command: ["tool", "arg"]. shutil.which on a full string will fail. Parse the executable first.

+import shlex
@@
-        for name, s in servers.items():
-            if s.transport == "stdio" and s.command and not shutil.which(s.command):
+        for name, s in servers.items():
+            if s.transport != "stdio" or not s.command:
+                continue
+            # s.command may be a string or a list
+            if isinstance(s.command, (list, tuple)):
+                prog = s.command[0] if s.command else ""
+            else:
+                try:
+                    prog = shlex.split(s.command)[0]
+                except Exception:
+                    prog = str(s.command).split()[0] if s.command else ""
+            if prog and not shutil.which(prog):
                 console.print(
-                    f"[yellow]Missing command for server '{name}': {s.command}[/yellow]"
+                    f"[yellow]Missing command for server '{name}': {prog}[/yellow]"
                 )
                 ok = False

To confirm schema variants in your repo, you can grep server configs for command: values and types. Want a quick script to summarize them?


41-51: Fail fast if script missing and run from script directory for predictable relative paths

Improve UX by erroring early when the script isn’t found and by setting cwd to the script’s folder so relative imports/paths behave as during normal execution.

     def _run_script() -> subprocess.Popen:
         """Run the script as a subprocess."""
-        console.print(f"Running {script}")
+        console.print(f"Running {script}")
+        if not script.exists():
+            console.print(f"[red]Script not found: {script}[/red]")
+            raise typer.Exit(2)
         # Run the script with the same Python interpreter
         return subprocess.Popen(
-            [sys.executable, str(script)],
+            [sys.executable, str(script)],
+            cwd=str(script.parent),
             stdout=None,  # Inherit stdout
             stderr=None,  # Inherit stderr
             stdin=None,  # Inherit stdin
         )

97-104: Optional: terminate process trees to avoid orphaned grandchildren on restart

If the script spawns children (e.g., uvicorn), terminate() may leave them running. Consider creating a process group and signaling the group.

+import os
+import signal
@@
-                    process.terminate()
+                    # Terminate the whole process group where possible
+                    try:
+                        if os.name != "nt":
+                            os.killpg(os.getpgid(process.pid), signal.SIGTERM)
+                        else:
+                            process.terminate()
+                    except Exception:
+                        process.terminate()
@@
-                    process = _run_script()
+                    process = _run_script()

And when spawning:

-        return subprocess.Popen(
+        kwargs = {}
+        if os.name != "nt":
+            kwargs["preexec_fn"] = os.setsid  # start new process group
+        else:
+            kwargs["creationflags"] = getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0)
+        return subprocess.Popen(
             [sys.executable, str(script)],
             cwd=str(script.parent),
             stdout=None,
             stderr=None,
             stdin=None,
+            **kwargs,
         )

If you prefer to keep it simpler, you can skip this; just flagging the edge case.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a9d606e and 1f7e626.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • pyproject.toml (2 hunks)
  • src/mcp_agent/cli/commands/build.py (1 hunks)
  • src/mcp_agent/cli/commands/dev.py (3 hunks)
  • src/mcp_agent/cli/commands/invoke.py (5 hunks)
  • src/mcp_agent/cli/commands/serve.py (4 hunks)
  • src/mcp_agent/cli/commands/server.py (5 hunks)
  • src/mcp_agent/cli/main.py (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
src/mcp_agent/cli/commands/invoke.py (1)
src/mcp_agent/workflows/factory.py (1)
  • create_llm (68-86)
src/mcp_agent/cli/commands/serve.py (1)
src/mcp_agent/core/context.py (1)
  • mcp (102-103)
src/mcp_agent/cli/main.py (9)
src/mcp_agent/cli/commands/chat.py (1)
  • chat (90-713)
src/mcp_agent/cli/commands/dev.py (1)
  • dev (25-132)
src/mcp_agent/cli/commands/invoke.py (1)
  • invoke (23-113)
src/mcp_agent/cli/utils/typer_utils.py (1)
  • invoke (33-40)
src/mcp_agent/cli/commands/serve.py (1)
  • serve (77-373)
src/mcp_agent/cli/commands/build.py (1)
  • build (192-433)
src/mcp_agent/cli/commands/logs.py (1)
  • logs (158-297)
src/mcp_agent/cli/commands/doctor.py (1)
  • doctor (197-419)
src/mcp_agent/cli/commands/configure.py (1)
  • configure (84-144)
src/mcp_agent/cli/commands/server.py (3)
src/mcp_agent/config.py (4)
  • Settings (583-690)
  • MCPServerSettings (52-113)
  • MCPSettings (116-124)
  • get_settings (727-826)
src/mcp_agent/cli/utils/importers.py (1)
  • import_servers_from_mcp_json (40-82)
src/mcp_agent/core/context.py (1)
  • cleanup_context (251-264)
🔇 Additional comments (10)
pyproject.toml (1)

76-82: Confirm watchdog 6.x compatibility and platform wheels.

PyPI shows watchdog 6.0.0 (released 2024-11-01) requires Python >=3.9 and lists support for 3.10–3.12; 6.x contains only minor API/behavior tweaks (inotify now uses select.poll, small CLI/utility removals). Verify binary wheels exist for your target OS/architectures (or allow source installs) and run the full test suite in your dev runner/CI to catch any environment-specific issues.

src/mcp_agent/cli/commands/build.py (1)

381-383: Nice UX: explicit message when no servers are configured.

Good addition—prevents an empty table from confusing users.

src/mcp_agent/cli/commands/invoke.py (2)

19-19: Good call disabling color for piping.

Console(color_system=None) improves log piping and JSON post-processing.


67-67: Resolved — Rich Console.print 'end' kwarg supported (Rich v13.9+) Verified: Console.print signature includes end: str = "\n", so console.print(res, end="\n\n\n") is valid; applies to lines 67, 77 and 106–107.

src/mcp_agent/cli/commands/serve.py (1)

195-196: HTTP endpoint row is a helpful clarity boost.

Good addition—distinguishing SSE and HTTP paths reduces confusion.

src/mcp_agent/cli/commands/server.py (4)

15-18: Good move to centralize settings loading and ensure cleanup.

Using get_settings() and cleanup_context(shutdown_logger=True) aligns CLI flows and shuts down logging deterministically.


342-344: LGTM: list_servers now reads from resolved settings.

This matches the new global settings flow.


447-451: LGTM: add() now operates on get_settings() and MCPSettings defaults.

Consistent with the rest of the CLI.


779-780: Explicit logging shutdown after probe is a good addition.

Prevents lingering OTEL/exporter threads in CLI workflows.

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

136-148: New subcommands wiring looks good

Mounting chat/dev/invoke/serve/server/build/logs/doctor/configure under the top-level app is consistent and aligns with the PR goals.

def signal_handler(sig, frame):
console.print("\n[yellow]Shutting down server...[/yellow]")
shutdown_event.set()
os._exit(0)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Don’t call os._exit(0) in signal handler. It bypasses cleanup and can corrupt logs/metrics.

Immediate exit prevents graceful shutdown, resource cleanup, and OTEL/exporter flushes. Use a cooperative shutdown signal instead.

-        def signal_handler(sig, frame):
+        # Keep a reference to the uvicorn server for cooperative shutdown
+        server_ref = {"srv": None}
+
+        def signal_handler(sig, frame):
             console.print("\n[yellow]Shutting down server...[/yellow]")
             shutdown_event.set()
-            os._exit(0)
+            # For HTTP/SSE, ask uvicorn to exit if running
+            srv = server_ref.get("srv")
+            if srv is not None:
+                try:
+                    srv.should_exit = True
+                except Exception:
+                    pass
@@
-                server = uvicorn.Server(uvicorn_config)
+                server = uvicorn.Server(uvicorn_config)
+                server_ref["srv"] = server
📝 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
os._exit(0)
# Keep a reference to the uvicorn server for cooperative shutdown
server_ref = {"srv": None}
def signal_handler(sig, frame):
console.print("\n[yellow]Shutting down server...[/yellow]")
shutdown_event.set()
# For HTTP/SSE, ask uvicorn to exit if running
srv = server_ref.get("srv")
if srv is not None:
try:
srv.should_exit = True
except Exception:
pass
# allow the process to exit naturally after cooperative shutdown
Suggested change
os._exit(0)
server = uvicorn.Server(uvicorn_config)
server_ref["srv"] = server
🤖 Prompt for AI Agents
In src/mcp_agent/cli/commands/serve.py around line 256, replace the direct
os._exit(0) call in the signal handler with a cooperative shutdown mechanism:
set a shared shutdown flag or threading.Event (or call a provided shutdown
coroutine/function) so the main loop can detect the signal, perform graceful
teardown (close resources, flush logs/metrics/OTEL exporters) and then exit
normally; remove any use of os._exit in handlers and ensure the main thread
performs cleanup and returns an appropriate exit code.

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: 0

♻️ Duplicate comments (1)
pyproject.toml (1)

33-33: Typer base bump aligned — mismatch resolved.

This addresses the previous feedback about aligning Typer versions between base and CLI extras. Looks good.

🧹 Nitpick comments (1)
pyproject.toml (1)

80-81: Trim extras to avoid redundant deps; consider pinning upper bounds.

  • You already depend on rich at the base level; typer[all] pulls rich (and shellingham/colorama) again. Unless you need shellingham/colorama, prefer plain typer to keep the CLI extra lean.
  • Since Typer is <1.0, consider adding an upper bound to avoid accidental breakage on future minor bumps. Same for watchdog major.

Option A (drop extras; leaner deps):

-    "typer[all]>=0.15.3",
+    "typer>=0.15.3",

Option B (keep shell completion, but be explicit):

-    "typer[all]>=0.15.3",
+    "typer>=0.15.3",
+    "shellingham>=1.5.0",

Optional safety pins:

-    "typer>=0.15.3",
+    "typer>=0.15.3,<0.16",
-    "watchdog>=6.0.0"
+    "watchdog>=6.0.0,<7.0.0"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1f7e626 and 458f462.

📒 Files selected for processing (2)
  • pyproject.toml (2 hunks)
  • src/mcp_agent/cli/main.py (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/mcp_agent/cli/main.py

@StreetLamb StreetLamb merged commit e8ee962 into main Sep 12, 2025
8 checks passed
@StreetLamb StreetLamb deleted the feat/cli_mvp_2 branch September 12, 2025 16:13
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.

3 participants