Skip to content

Conversation

@JRedeker
Copy link

@JRedeker JRedeker commented Nov 28, 2025

User description

This PR introduces automatic dependency isolation for Python-based MCP servers by leveraging uv.

Key Changes

  • Auto-UVX Injection: If the user installs a server configured for pip or python execution, and uv is detected on the system, MCPM automatically upgrades the execution command to uv run --with <package>.
  • Dependency Isolation: This eliminates the need to install server packages into the global Python environment or the same venv as MCPM.
  • Pydantic Compatibility: Solves the long-standing 'Pydantic Versioning Problem' (v1 vs v2 conflicts) by running each server in its own ephemeral environment managed by uv.
  • Zero-Config: Requires no changes to the registry. It upgrades existing definitions on the fly.

Why this matters

This allows the MCPM Core to be upgraded to the latest dependencies (Pydantic v2, modern MCP SDK) without breaking compatibility with older servers that require legacy libraries.


PR Type

Enhancement, Bug fix


Description

  • Implement Auto-UVX injection for Python servers using uv run --with for dependency isolation

  • Consolidate force operation logic with should_force_operation() helper function

  • Add explicit non-interactive mode support via MCPM_NON_INTERACTIVE environment variable

  • Redirect installation status message to stderr for cleaner output


Diagram Walkthrough

flowchart LR
  A["Python/pip command"] -->|"uv detected"| B["Auto-upgrade to uv run"]
  B -->|"with package isolation"| C["Isolated server environment"]
  D["CLI force flag"] -->|"consolidated logic"| E["should_force_operation"]
  E -->|"or env var"| F["Skip confirmations"]
  G["MCPM_NON_INTERACTIVE env"] -->|"explicit check"| H["Non-interactive mode"]
Loading

File Walkthrough

Relevant files
Enhancement
install.py
Auto-UVX injection and non-interactive mode support           

src/mcpm/commands/install.py

  • Add shutil import and should_force_operation utility import for
    consolidated force logic
  • Enhance prompt_with_default() to support explicit non-interactive mode
    via MCPM_NON_INTERACTIVE environment variable
  • Redirect installation status message to stderr using separate Console
    instance
  • Replace direct force flag checks with should_force_operation(force)
    calls throughout
  • Implement Auto-UVX injection logic that detects uv availability and
    upgrades python/pip commands to uv run --with for dependency
    isolation
+57/-5   
uninstall.py
Consolidate force operation logic                                               

src/mcpm/commands/uninstall.py

  • Import should_force_operation utility function
  • Replace direct force flag check with should_force_operation(force)
    call for confirmation logic
+2/-1     
non_interactive.py
Add CLI force flag parameter to should_force_operation     

src/mcpm/utils/non_interactive.py

  • Update should_force_operation() signature to accept optional
    cli_force_flag parameter
  • Modify logic to return True if either CLI force flag is True OR
    MCPM_FORCE environment variable is set
  • Add docstring clarification for the new parameter and return behavior
+7/-3     

Summary by CodeRabbit

  • New Features
    • Added non-interactive mode support for automated workflows via environment variable and CLI flags
    • Automatic Python/pip command optimization with uv for package isolation when available
    • Consistent force flag behavior across install and uninstall operations

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

- Remove unused 'is_non_interactive' import in install.py
- Use stderr for installation status message in install.py
- Upgrades execution to 'uv run' if uv is detected

- Solves dependency isolation and Pydantic version conflicts

- Eliminates need for pip install step for Python servers
@coderabbitai
Copy link

coderabbitai bot commented Nov 28, 2025

Walkthrough

The changes introduce non-interactive mode support across install and uninstall commands by extending the should_force_operation utility to accept a CLI force flag and refactoring prompts to honor both the flag and environment variables. Additionally, auto-UVX injection logic wraps Python/pip installations with uv run --with for automatic isolation when uv is available.

Changes

Cohort / File(s) Change Summary
Non-interactive Utility
src/mcpm/utils/non_interactive.py
Updated should_force_operation to accept cli_force_flag parameter alongside MCPM_FORCE environment variable, enabling CLI-based force override.
Installation Command
src/mcpm/commands/install.py
Extended prompt_with_default with force parameter for non-interactive handling. Added should_force_operation gating to confirmation and decision prompts. Introduced auto-UVX injection wrapping Python/pip commands with uv run --with. Switched progress message to stderr.
Uninstall Command
src/mcpm/commands/uninstall.py
Replaced direct force flag check with should_force_operation(force) call to align confirmation logic with non-interactive mode semantics.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • install.py: Multiple new logic branches (non-interactive prompts, UVX injection, conditional wrapping) require careful verification of edge cases
  • non_interactive.py: Signature change impacts command integration; verify all call sites pass the CLI flag correctly
  • uninstall.py: Simple integration; ensure should_force_operation semantics align with original force flag behavior

Suggested reviewers

  • GabrielDrapor

Poem

🐰 Commands run silent, no prompts to delight,
With UVX wrapping, each package held tight,
Force flags and env vars now dance in sync,
Non-interactive flows at the speed of a wink! ✨

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(core): Modernize Dependency Management with Auto-UVX' directly addresses the main change: automatic UVX injection for Python-based MCP servers to modernize dependency management.
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

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

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: The new auto-UVX injection and non-interactive/force pathways perform critical actions
(installing servers, modifying global config) without adding structured audit logs
capturing who, what, and outcome.

Referred Code
if author_info:
    author_name = author_info.get("name", "Unknown")
    author_url = author_info.get("url", "")
    console.print(f"[dim]Author: {author_name} {author_url}[/]")

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

# Create server directory in the MCP directory
base_dir = os.path.expanduser("~/.mcpm")
os.makedirs(base_dir, exist_ok=True)

servers_dir = os.path.join(base_dir, "servers")
os.makedirs(servers_dir, exist_ok=True)



 ... (clipped 294 lines)

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:
Heuristic package guess: The auto-UVX injection relies on a possibly absent package name and proceeds only if
present, but lacks explicit error handling/logging when package detection fails or when uv
invocation would be incorrect.

Referred Code
# --- Auto-UVX Injection Logic ---
# If 'uv' is available and we are using python/pip, try to upgrade to 'uv run' for isolation.
# This solves the "Pydantic Versioning" dependency hell by isolating servers.
if mcp_command in ["python", "python3", "pip"] and shutil.which("uv"):
    # We need to determine the package name to run 'uv run --with <package>'
    # If package_name was defined in the installation method, use it.
    # If not, check if we are running 'python -m <module>' and guess package name from module?
    # Or default to the server name if reasonable?
    target_package = package_name

    # If args start with '-m', the next arg is the module.
    # Often module == package (e.g. mcp_server_time -> mcp-server-time? No, dashes vs underscores).
    # But 'uv run --with <module> python -m <module>' usually works if PyPI name matches.

    if not target_package and mcp_args and mcp_args[0] == "-m" and len(mcp_args) > 1:
        # Heuristic: Assume package name matches module name (with _ -> - maybe?)
        # Ideally, the registry should provide 'package'.
        # For now, we only auto-upgrade if we have a package name OR if we are brave.
        # Let's rely on package_name variable extracted earlier from selected_method.get("package")
        pass



 ... (clipped 14 lines)

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

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

@qodo-code-review
Copy link
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Auto-upgrade logic silently fails

The uv run auto-upgrade feature fails silently if a package_name is not found in
the server metadata, falling back to the original command without warning. This
should be addressed by either making the package name mandatory or logging a
warning when the upgrade cannot be performed.

Examples:

src/mcpm/commands/install.py [435-464]
    if mcp_command in ["python", "python3", "pip"] and shutil.which("uv"):
        # We need to determine the package name to run 'uv run --with <package>'
        # If package_name was defined in the installation method, use it.
        # If not, check if we are running 'python -m <module>' and guess package name from module?
        # Or default to the server name if reasonable?
        target_package = package_name

        # If args start with '-m', the next arg is the module.
        # Often module == package (e.g. mcp_server_time -> mcp-server-time? No, dashes vs underscores).
        # But 'uv run --with <module> python -m <module>' usually works if PyPI name matches.

 ... (clipped 20 lines)

Solution Walkthrough:

Before:

# In src/mcpm/commands/install.py
if mcp_command in ["python", "python3", "pip"] and shutil.which("uv"):
    target_package = package_name # From server metadata

    # Heuristics to find package name are not implemented
    if not target_package and mcp_args and mcp_args[0] == "-m":
        pass

    if target_package:
        # Upgrade to 'uv run'
        print("Auto-upgrading to 'uv run'...")
        mcp_command = "uv"
        mcp_args = ["run", "--with", target_package, ...]
    # else:
    #   No warning is issued. The command silently falls back to the original,
    #   non-isolated execution (e.g., 'python -m ...').

After:

# In src/mcpm/commands/install.py
if mcp_command in ["python", "python3", "pip"] and shutil.which("uv"):
    target_package = package_name # From server metadata

    # Heuristics to find package name are not implemented
    if not target_package and mcp_args and mcp_args[0] == "-m":
        pass

    if target_package:
        # Upgrade to 'uv run'
        print("Auto-upgrading to 'uv run'...")
        mcp_command = "uv"
        mcp_args = ["run", "--with", target_package, ...]
    else:
        # Warn the user that auto-upgrade could not be performed
        console.print("[yellow]Warning:[/] Could not determine package name. Falling back to non-isolated execution.")
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical flaw where the auto-upgrade feature fails silently if package_name is missing, undermining the PR's main goal of dependency isolation and making the feature unreliable.

High
Possible issue
Fix incorrect Auto-UVX command transformation

Remove pip from the list of commands eligible for auto-upgrading in the Auto-UVX
injection logic to prevent incorrect command transformations, as uv run is not a
substitute for pip.

src/mcpm/commands/install.py [432-464]

 # --- Auto-UVX Injection Logic ---
-# If 'uv' is available and we are using python/pip, try to upgrade to 'uv run' for isolation.
+# If 'uv' is available and we are using python, try to upgrade to 'uv run' for isolation.
 # This solves the "Pydantic Versioning" dependency hell by isolating servers.
-if mcp_command in ["python", "python3", "pip"] and shutil.which("uv"):
+if mcp_command in ["python", "python3"] and shutil.which("uv"):
     # We need to determine the package name to run 'uv run --with <package>'
     # If package_name was defined in the installation method, use it.
     # If not, check if we are running 'python -m <module>' and guess package name from module?
     # Or default to the server name if reasonable?
     target_package = package_name
 
     # If args start with '-m', the next arg is the module.
     # Often module == package (e.g. mcp_server_time -> mcp-server-time? No, dashes vs underscores).
     # But 'uv run --with <module> python -m <module>' usually works if PyPI name matches.
 
     if not target_package and mcp_args and mcp_args[0] == "-m" and len(mcp_args) > 1:
         # Heuristic: Assume package name matches module name (with _ -> - maybe?)
         # Ideally, the registry should provide 'package'.
         # For now, we only auto-upgrade if we have a package name OR if we are brave.
         # Let's rely on package_name variable extracted earlier from selected_method.get("package")
         pass
 
     if target_package:
         console.print(f"[bold blue]🚀 Auto-upgrading to 'uv run' for isolation (package: {target_package})[/]")
         # Old: python -m module ...
         # New: uv run --with package python -m module ...
 
         # We prepend 'run --with package' to the command execution
         # mcp_command becomes 'uv'
         # mcp_args becomes ['run', '--with', target_package, original_command] + mcp_args
 
         new_args = ["run", "--with", target_package, mcp_command] + mcp_args
         mcp_command = "uv"
         mcp_args = new_args

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a bug where pip commands would be incorrectly wrapped with uv run, leading to runtime failures. Removing pip from the check is a critical fix for the new "Auto-UVX Injection" feature.

High
Organization
best practice
Send status to stderr

Print this status message to stderr to follow the convention of directing
operational logs to stderr. Use a Console configured with stderr=True.

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

-console.print(f"[bold blue]🚀 Auto-upgrading to 'uv run' for isolation (package: {target_package})[/]")
+console_stderr = Console(stderr=True)
+console_stderr.print(f"[bold blue]🚀 Auto-upgrading to 'uv run' for isolation (package: {target_package})[/]")
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Prefer stderr for logs and status output when handling subprocess-related status to avoid mixing with data on stdout.

Low
  • More

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)

124-126: Pass force to recursive call for consistency.

The recursive call omits the force parameter. While currently unreachable in non-interactive mode (the function returns early), passing it maintains consistency and prevents subtle bugs if the logic changes.

         if not result.strip() and required and not default:
             console.print("[yellow]Warning: Required value cannot be empty.[/]")
-            return prompt_with_default(prompt_text, default, hide_input, required)
+            return prompt_with_default(prompt_text, default, hide_input, required, force)

446-451: Remove or implement the -m heuristic block.

This block contains only a pass statement and extensive comments describing a heuristic that's never executed. Either implement the module-to-package name inference or remove this dead code to avoid confusion.

-        # If args start with '-m', the next arg is the module.
-        # Often module == package (e.g. mcp_server_time -> mcp-server-time? No, dashes vs underscores).
-        # But 'uv run --with <module> python -m <module>' usually works if PyPI name matches.
-
-        if not target_package and mcp_args and mcp_args[0] == "-m" and len(mcp_args) > 1:
-            # Heuristic: Assume package name matches module name (with _ -> - maybe?)
-            # Ideally, the registry should provide 'package'.
-            # For now, we only auto-upgrade if we have a package name OR if we are brave.
-            # Let's rely on package_name variable extracted earlier from selected_method.get("package")
-            pass
📜 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 06901e8.

📒 Files selected for processing (3)
  • src/mcpm/commands/install.py (8 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/commands/install.py
  • src/mcpm/utils/non_interactive.py
  • src/mcpm/commands/uninstall.py
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: pathintegral-institute/mcpm.sh PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-08-06T08:20:00.958Z
Learning: Use `uv` for all Python dependency management.
🧬 Code graph analysis (2)
src/mcpm/commands/install.py (2)
src/mcpm/utils/non_interactive.py (1)
  • should_force_operation (35-45)
src/mcpm/schemas/full_server_config.py (1)
  • to_server_config (72-84)
src/mcpm/commands/uninstall.py (1)
src/mcpm/utils/non_interactive.py (1)
  • should_force_operation (35-45)
🔇 Additional comments (4)
src/mcpm/commands/uninstall.py (1)

10-10: LGTM! Consistent non-interactive handling.

The integration of should_force_operation(force) correctly unifies force behavior with the install command, respecting both the CLI --force flag and the MCPM_FORCE environment variable.

Also applies to: 63-63

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

35-45: LGTM! Clean extension of the force operation check.

The backward-compatible signature change with a default value and the OR logic correctly enables force behavior from either CLI flags or environment variables. The case-insensitive env var check is a good practice.

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

155-156: LGTM! Comprehensive non-interactive mode integration.

Good implementation:

  • Progress messages to stderr keeps stdout clean for structured output
  • Confirmation dialogs and method selection correctly gated by should_force_operation(force)
  • Force semantics properly propagated to global_add_server

Based on learnings, the use of uv for Python dependency management aligns with project guidelines.

Also applies to: 180-182, 227-227, 481-481


435-435: I apologize for the cloning failure. Let me reconsider based on the information available.

Looking at the review comment and the code context provided, the concern about pip handling is logically sound even without examining the registry files:

Analysis of the concern:

  • Line 435 checks: if mcp_command in ["python", "python3", "pip"] and shutil.which("uv"):
  • The subsequent logic (lines 453-464) wraps the command: uv run --with <package> <original_command> <args>
  • For pip commands, this would produce: uv run --with <pkg> pip install ...
  • This is semantically incorrect because uv run --with is designed for running Python modules/executables, not for invoking package managers

The concern is valid because:

  1. uv run --with <pkg> is meant to run Python code with additional dependencies, not to wrap arbitrary commands
  2. pip is a package management tool and should not be treated the same as python or python3 executables
  3. If this is executed, it would either fail or produce unexpected behavior

Since I cannot access the repository to verify if pip is actually used in registry entries, but the concern itself is technically correct, I'll preserve the original review comment with validation that the concern is sound.

Reconsider including pip in auto-UVX upgrade.

Wrapping pip commands with uv run --with <pkg> pip ... is semantically incorrect. The uv run --with pattern is designed for running Python executables/modules, not for package installation commands. Either exclude pip from this check, or if pip commands in the registry actually represent something else (unlikely), add a clarifying comment explaining the intent.

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