Skip to content

Conversation

@JRedeker
Copy link

@JRedeker JRedeker commented Nov 27, 2025

User description

Overview

This Pull Request significantly improves the robustness of the mcpm CLI when running in non-interactive environments (e.g., CI/CD pipelines, Docker builds, or driven by AI agents).

The Problem

Previously, setting MCPM_NON_INTERACTIVE=true or MCPM_FORCE=true was not consistently respected across all prompt types. Specifically:

  1. Installation Method Selection: If a server had multiple installation candidates (e.g., stdio vs Docker), the CLI would hang waiting for user input even in non-interactive mode.
  2. Confirmation Prompts: Some confirmation checks did not fully verify the global force flags, leading to hanging processes in headless environments.

The Solution

We have updated src/mcpm/commands/install.py and src/mcpm/commands/uninstall.py to integrate stricter checks for is_non_interactive() and should_force_operation().

Key Changes:

  • prompt_with_default helper: Now immediately returns the default value (or aborts if required) when in non-interactive mode, preventing IO blocks.
  • Installation Logic:
    • Automatically bypasses the "Select Installation Method" menu if forced/non-interactive (defaults to the first available method).
    • Ensures global_add_server receives the force flag correctly.
  • Uninstallation Logic:
    • Bypasses confirmation prompts when force/non-interactive flags are present.

Usage

Setting standard environment variables now guarantees zero interaction:

export MCPM_NON_INTERACTIVE=true
export MCPM_FORCE=true
mcpm install <server>

PR Type

Enhancement


Description

  • Adds non-interactive mode support to install/uninstall commands

  • Integrates should_force_operation() and is_non_interactive() checks throughout

  • Prevents hanging prompts in CI/CD and headless environments

  • Auto-selects first installation method when forced/non-interactive


Diagram Walkthrough

flowchart LR
  A["Non-Interactive Mode"] --> B["is_non_interactive()"]
  A --> C["should_force_operation()"]
  B --> D["prompt_with_default"]
  C --> D
  B --> E["Installation Method Selection"]
  C --> E
  B --> F["Confirmation Prompts"]
  C --> F
  D --> G["Return Default or Abort"]
  E --> H["Auto-select First Method"]
  F --> I["Skip Confirmation"]
Loading

File Walkthrough

Relevant files
Enhancement
install.py
Integrate non-interactive checks into install workflow     

src/mcpm/commands/install.py

  • Imports should_force_operation and is_non_interactive utilities
  • Updates prompt_with_default() to check non-interactive mode and return
    default/abort
  • Adds should_force_operation() check to installation confirmation
    prompt
  • Adds should_force_operation() check to installation method selection
    logic
  • Passes combined force flag to global_add_server() call
+13/-3   
uninstall.py
Add non-interactive support to uninstall confirmation       

src/mcpm/commands/uninstall.py

  • Imports should_force_operation utility
  • Adds should_force_operation() check to uninstall confirmation prompt
+2/-1     

Summary by CodeRabbit

  • New Features

    • CLI force flag now combines with the environment to force operations; explicit non-interactive mode detection added.
  • Behavior Changes

    • Install/uninstall flows respect combined force and explicit non-interactive modes.
    • Prompts are suppressed when forced or explicitly non-interactive: defaults are used when present; required values error fast.
    • Confirmation steps bypassed under forced/non-interactive runs.
  • Tests

    • Tests updated to assert no prompts when force or env variables provide values.
  • Other

    • Minor user-facing message spacing/formatting tweaks.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 27, 2025

Walkthrough

Adds explicit non-interactive detection and threads a CLI-level force flag through prompting, confirmation, and global-config flows so prompts short-circuit and config writes respect combined force/non-interactive checks.

Changes

Cohort / File(s) Summary
Non-interactive utility
src/mcpm/utils/non_interactive.py
Adds is_explicit_non_interactive() and changes should_force_operation()should_force_operation(cli_force_flag: bool = False). Now returns true when CLI flag is true or MCPM_FORCE env var equals "true" (case-insensitive). Docstrings updated.
Install command: prompt & force gating
src/mcpm/commands/install.py
Imports is_explicit_non_interactive and should_force_operation. Extends prompt_with_default(..., force=False) signature and behavior to short-circuit prompting when explicit non-interactive or forced; raises UsageError if a required value with no default is requested in explicit non-interactive mode. Uses should_force_operation(force) and explicit non-interactive checks to bypass confirmation, suppress method selection, and decide final global-config writes. Propagates force into prompt calls, replacement helpers, and argument/environment prompts. Minor user-message formatting tweaks.
Uninstall command: confirmation gating
src/mcpm/commands/uninstall.py
Imports is_explicit_non_interactive and should_force_operation. Changes confirmation flow to skip prompting when should_force_operation(force) or explicit non-interactive is true; otherwise prompt remains. Adjusts Rich tag spacing in messages.
Tests: input & env handling
tests/test_add.py
Tests updated to supply required inputs via environment variables and assert PromptSession.prompt is not called when --force or explicit non-interactive behavior applies. Mocks adjusted accordingly.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI
    participant Cmd as Command (install/uninstall)
    participant NonInt as NonInteractiveUtil
    participant Prompt as prompt_with_default
    participant Config as GlobalConfig

    User->>CLI: run command (with/without --force)
    CLI->>Cmd: invoke (force flag passed)
    Cmd->>NonInt: is_explicit_non_interactive()
    Cmd->>NonInt: should_force_operation(cli_force_flag=force)
    NonInt-->>Cmd: explicit? / forced?
    alt Explicit non-interactive OR Forced
        Cmd->>Prompt: prompt_with_default(..., force=true) short-circuits
        Note right of Prompt: returns default or raises UsageError for required missing value
        Cmd->>Config: write global config (should_force_operation=true)
        Config-->>Cmd: success
    else Interactive
        Cmd->>Prompt: prompt_with_default(..., force=false) shows prompt
        User-->>Prompt: provides input
        Prompt-->>Cmd: input
        Cmd->>Config: write global config (should_force_operation=false)
        Config-->>Cmd: success
    end
    Cmd-->>User: operation complete / aborted
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Verify all call sites pass the new force / cli_force_flag consistently (install prompts, replacement helpers, uninstall).
  • Confirm is_explicit_non_interactive() precedence relative to existing isatty checks and that UsageError is raised only in appropriate cases.
  • Validate case-insensitive handling of MCPM_FORCE and that tests correctly cover non-prompting behavior.

Poem

🐇
I hop where flags make silence grow,
Prompts tuck in and breezes flow.
Force and env in moonlit code,
Servers land on steady road.
Hop, install — soft and slow.

Pre-merge checks and finishing touches

✅ 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 'feat: Enhanced Non-Interactive Mode for Automation' accurately summarizes the main objective of the PR, which is to improve the mcpm CLI's behavior in non-interactive environments by ensuring MCPM_NON_INTERACTIVE and MCPM_FORCE are consistently respected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 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 65f7134 and 8fc172c.

📒 Files selected for processing (1)
  • src/mcpm/commands/install.py (11 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Always format Python code with ruff.

Files:

  • src/mcpm/commands/install.py
🧬 Code graph analysis (1)
src/mcpm/commands/install.py (4)
src/mcpm/utils/non_interactive.py (2)
  • is_explicit_non_interactive (10-20)
  • should_force_operation (48-58)
src/mcpm/clients/base.py (3)
  • add_server (63-72)
  • add_server (263-283)
  • add_server (536-560)
src/mcpm/utils/repository.py (1)
  • get_server_metadata (152-163)
src/mcpm/schemas/full_server_config.py (1)
  • to_server_config (72-84)
🔇 Additional comments (1)
src/mcpm/commands/install.py (1)

23-23: LGTM! Non-interactive mode enhancements correctly implemented.

All past review feedback has been addressed:

  1. prompt_with_default now accepts and uses the force parameter
  2. ✅ Recursive call properly passes force through
  3. ✅ Both call sites (lines 341, 359) correctly pass force=force
  4. ✅ Non-interactive checks consistently use is_explicit_non_interactive() and should_force_operation(force)
  5. ✅ Confirmation and selection prompts are bypassed when force or non-interactive mode is active
  6. ✅ The distinction between prompt bypassing (non-interactive OR force) and server overwrite permission (force only) is preserved for safety

The implementation correctly fulfills the PR objectives of preventing hangs in CI/CD and other headless environments while maintaining safe defaults for destructive operations.

Also applies to: 76-104, 130-130, 184-189, 232-232, 336-342, 354-360, 454-454


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.

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Nov 27, 2025

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Missing audit logs: New paths that auto-bypass confirmations in non-interactive/force modes perform critical
changes without adding audit log entries capturing who, what, and outcome.

Referred Code
# Confirm addition
alias_text = f" as '{alias}'" if alias else ""
if not force and not should_force_operation() and not Confirm.ask(f"Install this server to global configuration{alias_text}?"):
    console.print("[yellow]Operation cancelled.[/]")
    return

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Non-interactive abort: In non-interactive mode, required prompts without defaults raise click.Abort() without
contextual error/logging, which may hinder diagnostics in headless runs.

Referred Code
# Check for non-interactive mode
if is_non_interactive() or should_force_operation():
    if default:
        return default
    if required:
        # Cannot fulfill required argument without default in non-interactive mode
        raise click.Abort()
    return ""

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Nov 27, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Consolidate force logic into one utility

Refactor the code to use a single utility function,
should_force_operation(force_flag), to consolidate the logic for checking both
the command-line force flag and the environment variable. This eliminates
redundant checks.

Examples:

src/mcpm/commands/install.py [174]
    if not force and not should_force_operation() and not Confirm.ask(f"Install this server to global configuration{alias_text}?"):
src/mcpm/commands/uninstall.py [63]
    if not force and not should_force_operation():

Solution Walkthrough:

Before:

# install.py
def install(server_name, force=False, alias=None):
    # ...
    if not force and not should_force_operation() and not Confirm.ask(...):
        # ...
    
    # ...
    if len(installations) > 1 and not force and not should_force_operation():
        # ...
    
    # ...
    success = global_add_server(..., force or should_force_operation())

# uninstall.py
def uninstall(server_name, force):
    # ...
    if not force and not should_force_operation():
        # ...

After:

# utils/non_interactive.py
def should_force_operation(cli_force_flag: bool = False) -> bool:
    # Returns True if either the flag is set or the env var is set
    return cli_force_flag or os.getenv("MCPM_FORCE") == "true"

# install.py
def install(server_name, force=False, alias=None):
    # ...
    if not should_force_operation(force) and not Confirm.ask(...):
        # ...
    
    if len(installations) > 1 and not should_force_operation(force):
        # ...
    
    success = global_add_server(..., should_force_operation(force))

# uninstall.py
def uninstall(server_name, force):
    if not should_force_operation(force):
        # ...
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies redundant logic where both the force flag and should_force_operation() are checked separately, proposing a valid refactoring that improves code clarity and maintainability by creating a single source of truth.

Medium
General
Improve non-interactive mode error reporting
Suggestion Impact:The commit replaced raise click.Abort() with raise click.UsageError(...) and added a descriptive message in the non-interactive branch, matching the suggestion’s intent.

code diff:

             # Cannot fulfill required argument without default in non-interactive mode
-            raise click.Abort()
+            raise click.UsageError(
+                "A required value has no default and cannot be prompted in non-interactive mode."
+            )

Replace click.Abort() with click.UsageError in non-interactive mode to provide a
more descriptive error message when a required value without a default is
encountered.

src/mcpm/commands/install.py [88-95]

 # Check for non-interactive mode
 if is_non_interactive() or should_force_operation():
     if default:
         return default
     if required:
         # Cannot fulfill required argument without default in non-interactive mode
-        raise click.Abort()
+        raise click.UsageError(
+            "A required value has no default and cannot be prompted in non-interactive mode."
+        )
     return ""

[Suggestion processed]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that click.Abort() provides poor user feedback in non-interactive mode and proposes using click.UsageError for a more descriptive error message, which significantly improves usability for automation.

Medium
Organization
best practice
Print status to stderr
Suggestion Impact:The commit replaced console.print with a Console(stderr=True) instance and used it to print the installation status message, directing it to stderr as suggested.

code diff:

-    console.print("[yellow]Installing server to global configuration...[/]")
+    console_stderr = Console(stderr=True)
+    console_stderr.print("[yellow]Installing server to global configuration...[/]")
 

Route status/log messages to stderr by using console configured for stderr to
align with logging best practices.

src/mcpm/commands/install.py [150]

-console.print("[yellow]Installing server to global configuration...[/]")
+console_stderr = Console(stderr=True)
+console_stderr.print("[yellow]Installing server to global configuration...[/]")

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Prefer stderr over stdout when printing subprocess/app status logs to avoid mixing data output with logs.

Low
  • Update

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

🧹 Nitpick comments (1)
src/mcpm/commands/install.py (1)

23-23: Non-interactive short-circuit in prompt_with_default is aligned with goals

Importing is_non_interactive/should_force_operation and using them to short-circuit prompt_with_default avoids blocking I/O in headless mode and correctly distinguishes between required-with-default, required-without-default (Abort), and optional fields. Behavior in fully interactive mode is preserved since the early return only triggers when flags/env demand it.

Optionally, you could update the docstring of prompt_with_default to mention the non-interactive behavior so callers know it may raise click.Abort immediately when required values lack defaults under MCPM_NON_INTERACTIVE/MCPM_FORCE.

Also applies to: 88-96

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c7fa277 and f50374f.

📒 Files selected for processing (2)
  • src/mcpm/commands/install.py (5 hunks)
  • src/mcpm/commands/uninstall.py (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Always format Python code with ruff.

Files:

  • src/mcpm/commands/uninstall.py
  • src/mcpm/commands/install.py
🧬 Code graph analysis (2)
src/mcpm/commands/uninstall.py (1)
src/mcpm/utils/non_interactive.py (1)
  • should_force_operation (35-41)
src/mcpm/commands/install.py (2)
src/mcpm/utils/non_interactive.py (2)
  • should_force_operation (35-41)
  • is_non_interactive (10-32)
src/mcpm/schemas/full_server_config.py (1)
  • to_server_config (72-84)
🔇 Additional comments (5)
src/mcpm/commands/uninstall.py (2)

10-10: LGTM!

The import is correctly added to support the new force operation check.


63-70: I attempted to verify the concern by accessing the codebase, but encountered a repository cloning error. Let me analyze the review comment based on the provided context:

Analysis of the review concern:

The review comment identifies a logical gap in the code at lines 63-70:

if not force and not should_force_operation():
    console.print(f"\n[bold yellow]Are you sure you want to remove:[/] {server_name}")
    console.print("[italic]To bypass this confirmation, use --force[/]")
    confirmed = Confirm.ask("Proceed with removal?")
    if not confirmed:
        console.print("Removal cancelled.")
        return

The concern is valid on its face:

  • The condition checks not force and not should_force_operation() (which reads MCPM_FORCE)
  • However, it does NOT check for non-interactive mode
  • If a user sets MCPM_NON_INTERACTIVE=true without MCPM_FORCE=true, the code would attempt to call Confirm.ask(), which could block on stdin in a non-interactive environment

This is a logical concern that appears sound, assuming:

  1. is_non_interactive() function exists (mentioned in PR summary)
  2. Confirm.ask() from the Rich library blocks on input
  3. install.py demonstrates the correct pattern for handling this case

However, I cannot verify these assumptions without codebase access to:

  • Confirm is_non_interactive() exists and is importable
  • Compare with install.py implementation
  • Verify Rich's Confirm.ask() behavior specification
src/mcpm/commands/install.py (3)

439-439: Propagating env-based force into global_add_server is correct

Passing force or should_force_operation() into global_add_server means both the CLI --force flag and the MCPM_FORCE env var now consistently control overwrite behavior. This aligns the global add logic with the non-interactive/force semantics described in the PR without changing the default behavior when neither is set.


172-176: I'll help you verify this review comment. Let me start by examining the code in question and understanding the non-interactive handling patterns in the codebase.
<function_calls>

Examine the specific file and lines mentioned in the review

#!/bin/bash

First, check the file exists and read the relevant lines

if [ -f "src/mcpm/commands/install.py" ]; then
echo "=== File: src/mcpm/commands/install.py (lines 160-185) ==="
sed -n '160,185p' src/mcpm/commands/install.py | cat -n
else
echo "File not found at src/mcpm/commands/install.py"
find . -name "install.py" -type f 2>/dev/null | head -5
fi


</function_calls>

<function_calls>

Search for is_non_interactive() definition and usage

#!/bin/bash
echo "=== Searching for is_non_interactive() definition ==="
rg -n "def is_non_interactive" --type py

echo -e "\n=== Searching for is_non_interactive() usage ==="
rg -n "is_non_interactive()" --type py -C 2


</function_calls>

<function_calls>

Search for should_force_operation() definition and all Confirm.ask calls

#!/bin/bash
echo "=== Searching for should_force_operation() definition ==="
rg -n "def should_force_operation" --type py

echo -e "\n=== Searching for all Confirm.ask() calls ==="
rg -n "Confirm.ask" --type py -C 2


</function_calls>


219-242: I'm unable to verify this review comment due to repository access issues preventing me from cloning the codebase. Without direct access to the repository files, I cannot confirm:

  1. Whether is_non_interactive() function exists and how it's defined
  2. How prompt_with_default() implements non-interactive checks for comparison
  3. All click.prompt calls in the file to assess scope of the issue
  4. Whether the suggested fix aligns with existing patterns

Since verification requires examining the actual codebase and I cannot access it, I cannot definitively determine if the original review comment's concern is valid or if the proposed fix is appropriate.

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

🧹 Nitpick comments (2)
src/mcpm/commands/install.py (2)

23-23: Non‑interactive short‑circuit in prompt_with_default matches the goals; minor clarity and reuse tweaks possible.

The early return/abort when MCPM_NON_INTERACTIVE=true or MCPM_FORCE=true is set nicely prevents blocking I/O in headless runs and fails fast for required/no‑default fields. The behavior for required+default vs optional fields is also consistent with the rest of the function.

Two small suggestions:

  • The comment about isatty() is a bit confusing relative to is_non_interactive()’s implementation (it triggers on not isatty()); consider rephrasing so future readers understand that the real issue is test runs where sys.stdin.isatty() is False, making is_non_interactive() return True and thereby skipping mocked prompts.
  • You’re partially re‑implementing the env‑var check that already lives in is_non_interactive(). To keep behavior centralized while still avoiding the isatty() path in tests, consider adding a helper in mcpm.utils.non_interactive for “explicit env‑driven non‑interactive only” and calling that here instead of inlining the os.getenv("MCPM_NON_INTERACTIVE") logic.

Also applies to: 76-99


175-180: Force flag now correctly skips confirmations & method selection; consider whether MCPM_NON_INTERACTIVE alone should also bypass these prompts.

Using not force and not should_force_operation() around the install confirmation and method‑selection prompt means MCPM_FORCE=true now behaves like a global --force, which fixes the “headless hangs due to missing confirmations” issue.

One open design question: right now, a caller that sets only MCPM_NON_INTERACTIVE=true (but not MCPM_FORCE=true and without --force) will still hit these interactive prompts. That’s defensible (non‑interactive does not imply “assume yes” for potentially destructive operations), but it does differ from prompt_with_default, which treats MCPM_NON_INTERACTIVE as a reason to skip prompting entirely.

If you want “non‑interactive but not forced” installs to also auto‑select the default method and auto‑answer “no” or “yes” consistently without blocking, you could fold the explicit non‑interactive flag into these guards as well, e.g. via a small helper from non_interactive.py:

explicit_non_interactive = os.getenv("MCPM_NON_INTERACTIVE", "").lower() == "true"
if len(installations) > 1 and not force and not should_force_operation() and not explicit_non_interactive:
    ...

and similarly for the confirmation condition.

Otherwise, it would be good to document that “CI‑safe” usage requires setting MCPM_FORCE=true (in addition to MCPM_NON_INTERACTIVE=true if you also want argument prompts to short‑circuit) so consumers know what to expect.

Also applies to: 221-244

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f50374f and f57f3b8.

📒 Files selected for processing (1)
  • src/mcpm/commands/install.py (5 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Always format Python code with ruff.

Files:

  • src/mcpm/commands/install.py
🧬 Code graph analysis (1)
src/mcpm/commands/install.py (2)
src/mcpm/utils/non_interactive.py (2)
  • should_force_operation (35-41)
  • is_non_interactive (10-32)
src/mcpm/schemas/full_server_config.py (1)
  • to_server_config (72-84)
🔇 Additional comments (1)
src/mcpm/commands/install.py (1)

441-443: Propagating force or should_force_operation() into global_add_server aligns global config behavior with env‑level force.

Passing force or should_force_operation() here ensures MCPM_FORCE=true alone is enough to override existing global server entries, even when the CLI wasn’t invoked with --force. That’s consistent with the new confirmation/method‑selection logic and with the stated goal of a single global “force” control for automation.

No changes needed from my side.

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f57f3b8 and ddf4ab9.

📒 Files selected for processing (3)
  • src/mcpm/commands/install.py (5 hunks)
  • src/mcpm/commands/uninstall.py (2 hunks)
  • src/mcpm/utils/non_interactive.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Always format Python code with ruff.

Files:

  • src/mcpm/utils/non_interactive.py
  • src/mcpm/commands/uninstall.py
  • src/mcpm/commands/install.py
🧬 Code graph analysis (1)
src/mcpm/commands/install.py (1)
src/mcpm/utils/non_interactive.py (2)
  • should_force_operation (35-45)
  • is_non_interactive (10-32)
🔇 Additional comments (7)
src/mcpm/commands/uninstall.py (2)

10-10: LGTM - Import aligns with non-interactive enhancements.

The import of should_force_operation is correctly placed and used at line 63 to gate the confirmation prompt.


63-63: LGTM - Confirmation properly gated by combined force check.

The change from if not force to if not should_force_operation(force) correctly implements the PR objective of respecting both the CLI --force flag and the MCPM_FORCE environment variable.

src/mcpm/commands/install.py (4)

179-179: LGTM - Install confirmation properly gated.

The confirmation prompt correctly uses should_force_operation(force) to respect both the CLI --force flag and the MCPM_FORCE environment variable, preventing hangs in non-interactive environments.


224-224: LGTM - Method selection properly gated.

The installation method selection correctly uses should_force_operation(force) to automatically use the first/recommended method when forced, preventing hangs when multiple installation methods exist (e.g., stdio vs Docker).


444-444: LGTM - Force flag properly propagated to global_add_server.

The call correctly passes should_force_operation(force) to global_add_server, ensuring that server override behavior respects both the CLI flag and environment variable.


23-23: I encountered a repository clone failure and cannot directly verify the codebase. However, based on the review comment provided, I can analyze what's presented:

The review comment makes a specific assertion: is_non_interactive is imported but not called, with a function call check provided. Since I cannot access the repository to confirm whether:

  1. is_non_interactive is actually unused in install.py
  2. is_non_interactive is used elsewhere in the codebase
  3. The line references (89, 92, 179, 224, 444) are accurate

I must flag this for manual verification.


Verify whether is_non_interactive import is unused before removing it.

The review claims is_non_interactive is imported at line 23 but not called in the file. While the analysis appears plausible (noting that line 92 uses should_force_operation() instead), this requires confirmation by examining the actual code to ensure removing the import won't break anything.

src/mcpm/utils/non_interactive.py (1)

35-45: LGTM - Public API enhanced to support CLI force flag.

The addition of the cli_force_flag parameter to should_force_operation correctly implements the combined forcing behavior. The default value preserves backward compatibility, and the OR logic appropriately respects both the CLI flag and the MCPM_FORCE environment variable.

- Remove unused 'is_non_interactive' import in install.py
- Use stderr for installation status message in install.py
…non-interactive checks and test fixes

- Add is_explicit_non_interactive() helper to avoid TTY detection issues in tests

- Update install/uninstall logic to strictly respect force/non-interactive flags

- Pass force flag correctly to prompt_with_default

- Update prompt_with_default docstring

- Update tests to use env vars for input when --force is used
@JRedeker
Copy link
Author

I have pushed updates to address the review feedback:

  1. Added is_explicit_non_interactive() helper to cleanly separate env var checks from TTY checks, fixing test issues.
  2. Updated prompt_with_default to use the new helper and strictly respect the force flag.
  3. Updated install command call sites to pass force=force to prompt_with_default.
  4. Updated install and uninstall logic to bypass confirmation prompts correctly when MCPM_NON_INTERACTIVE=true is set.
  5. Updated tests to set environment variables for required inputs when using --force, simulating correct CI usage.

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

🧹 Nitpick comments (1)
src/mcpm/commands/install.py (1)

68-68: Minor: Inconsistent spacing in Rich markup.

The space between [/ ] creates extra whitespace after "Error:". Consider using [/bold red] or [/] without the trailing space for consistency.

-        console.print(f"[bold red]Error:[/ ] Server '{server_config.name}' already exists in global configuration.")
+        console.print(f"[bold red]Error:[/] Server '{server_config.name}' already exists in global configuration.")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a1373dc and 65f7134.

📒 Files selected for processing (1)
  • src/mcpm/commands/install.py (12 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Always format Python code with ruff.

Files:

  • src/mcpm/commands/install.py
🧬 Code graph analysis (1)
src/mcpm/commands/install.py (4)
src/mcpm/utils/non_interactive.py (2)
  • is_explicit_non_interactive (10-20)
  • should_force_operation (48-58)
src/mcpm/clients/base.py (3)
  • add_server (63-72)
  • add_server (263-283)
  • add_server (536-560)
src/mcpm/utils/repository.py (1)
  • get_server_metadata (152-163)
src/mcpm/schemas/full_server_config.py (1)
  • to_server_config (72-84)
🔇 Additional comments (7)
src/mcpm/commands/install.py (7)

23-23: LGTM!

The import correctly brings in the non-interactive utilities that are used throughout this file for handling automation scenarios.


76-130: LGTM!

The force parameter is now properly integrated into prompt_with_default. The function correctly:

  • Returns the default value when in non-interactive or forced mode
  • Raises UsageError when a required value has no default in non-interactive mode
  • Propagates the force parameter in the recursive call (line 130)

184-187: LGTM!

The confirmation prompt is correctly bypassed when either the force flag is set or explicit non-interactive mode is enabled, preventing hangs in automation scenarios.


231-232: LGTM!

Multi-method installation selection correctly uses the recommended/first method when in forced or non-interactive mode, aligning with the PR's goal of preventing interactive prompts in automation.


336-360: LGTM!

Both call sites to prompt_with_default now correctly pass the force parameter (lines 341 and 359), addressing the previously flagged issue about inconsistent force behavior.


454-454: LGTM!

Using should_force_operation(force) ensures both the CLI --force flag and MCPM_FORCE environment variable are respected when adding the server to global configuration.


159-160: LGTM!

Using stderr for status messages keeps stdout clean for potential machine-readable output or piping scenarios—good CLI practice.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant