Skip to content

Conversation

@niechen
Copy link
Contributor

@niechen niechen commented Jul 9, 2025

User description

Summary

This PR refactors the command structure for MCPM v2 and adds comprehensive HTTP server support, resolving issue #207.

Command Structure Changes

  • ✅ Move add/remove commands from target_operations to main commands directory
  • ✅ Rename commands to match v2 syntax: addinstall, removeuninstall
  • ✅ Remove v1-specific profile activation logic and complexity
  • ✅ Inline necessary helper functions into command files
  • ✅ Update CLI registration and imports

HTTP Server Support (Fixes #207)

  • ✅ Add support for installing HTTP MCP servers via mcpm install github
  • ✅ HTTP servers create RemoteServerConfig instead of STDIOServerConfig
  • ✅ Add dedicated headers field for HTTP server configuration
  • ✅ Headers support variable substitution (e.g., "Authorization": "Bearer ${API_TOKEN}")
  • ✅ HTTP servers don't require command/args fields

Smart Argument Filtering

  • ✅ Only prompt for arguments actually referenced in installation method
  • ✅ Scan for ${VARIABLE_NAME} patterns in all installation fields
  • ✅ HTTP installations without variables prompt for no arguments
  • ✅ Traditional installations only prompt for used variables

Bug Fixes

  • ✅ Fix AttributeError when accessing .command on RemoteServerConfig
  • ✅ Add proper type checking in run.py and display.py
  • ✅ Ensure HTTP servers are handled correctly throughout the system

Test Plan

  • All existing tests pass (117 tests)
  • New tests for HTTP server installation
  • New tests for argument filtering functionality
  • CLI commands work correctly (mcpm install --help, mcpm uninstall --help)
  • HTTP server configuration works end-to-end

Breaking Changes

  • Commands moved from mcpm.commands.target_operations.{add,remove} to mcpm.commands.{install,uninstall}
  • Function names changed from add/remove to install/uninstall
  • Removed deprecated profile activation with % prefix

🤖 Generated with Claude Code


PR Type

Enhancement


Description

  • Refactor commands to v2 structure: move add/remove to install/uninstall

  • Add HTTP server support for MCP installations

  • Implement smart argument filtering for installation methods

  • Fix server configuration handling for different server types


Changes diagram

flowchart LR
  A["Old Commands"] --> B["New Commands"]
  B --> C["install command"]
  B --> D["uninstall command"]
  C --> E["HTTP Server Support"]
  C --> F["Smart Argument Filtering"]
  E --> G["RemoteServerConfig"]
  F --> H["Variable Reference Scanning"]
Loading

Changes walkthrough 📝

Relevant files
Configuration changes
2 files
cli.py
Update CLI imports and command registration                           
+4/-4     
__init__.py
Update package imports for new command structure                 
+4/-3     
Enhancement
5 files
install.py
Rename add to install with HTTP support                                   
+124/-37
list.py
Inline global_list_servers function                                           
+7/-1     
common.py
Remove deprecated common operations file                                 
+0/-162 
uninstall.py
Rename remove to uninstall with inlined functions               
+31/-21 
full_server_config.py
Add HTTP server fields and configuration                                 
+17/-4   
Bug fix
2 files
run.py
Add type checking for server configurations                           
+11/-1   
display.py
Add type checking for STDIOServerConfig display                   
+8/-1     
Tests
2 files
test_add.py
Update tests for install command and HTTP servers               
+197/-16
test_remove.py
Update tests for uninstall command                                             
+9/-9     

Need help?
  • Type /help how to ... in the comments thread for any questions about Qodo Merge usage.
  • Check out the documentation for more information.
  • This commit refactors the command structure for MCPM v2 and adds comprehensive HTTP server support:
    
    ## Command Structure Changes
    - Move add/remove commands from target_operations to main commands directory
    - Rename commands to match v2 syntax: `add` → `install`, `remove` → `uninstall`
    - Remove v1-specific profile activation logic and complexity
    - Inline necessary helper functions into command files
    - Update CLI registration and imports
    
    ## HTTP Server Support
    - Add support for installing HTTP MCP servers via `mcpm install`
    - HTTP servers create RemoteServerConfig instead of STDIOServerConfig
    - Add dedicated headers field for HTTP server configuration
    - Headers support variable substitution (e.g., `"Authorization": "Bearer ${API_TOKEN}"`)
    - HTTP servers don't require command/args fields
    
    ## Smart Argument Filtering
    - Only prompt for arguments actually referenced in installation method
    - Scan for `${VARIABLE_NAME}` patterns in all installation fields
    - HTTP installations without variables prompt for no arguments
    - Traditional installations only prompt for used variables
    
    ## Bug Fixes
    - Fix AttributeError when accessing .command on RemoteServerConfig
    - Add proper type checking in run.py and display.py
    - Ensure HTTP servers are handled correctly throughout the system
    
    ## Tests
    - Update all tests to use new command structure
    - Add comprehensive tests for HTTP server installation
    - Add tests for argument filtering functionality
    - All tests passing with new architecture
    
    🤖 Generated with [Claude Code](https://claude.ai/code)
    
    Co-Authored-By: Claude <[email protected]>
    @qodo-merge-pro
    Copy link
    Contributor

    qodo-merge-pro bot commented Jul 9, 2025

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    🎫 Ticket compliance analysis ✅

    207 - PR Code Verified

    Compliant requirements:

    • Support installing HTTP servers from mcpm.sh
    • Enable mcpm install github to install GitHub MCP using HTTP

    Requires further human verification:

    • End-to-end testing of HTTP server installation workflow
    • Verification that HTTP servers work correctly with MCP clients

    ⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
    🧪 PR contains tests
    🔒 Security concerns

    Sensitive information exposure:
    The HTTP server headers support variable substitution including authentication tokens (e.g., "Authorization": "Bearer ${API_TOKEN}"). While the code attempts to hide sensitive input during prompts using _should_hide_input(), the headers are stored in plain text in configuration files and could be logged or exposed through debug output.

    ⚡ Recommended focus areas for review

    Logic Complexity

    The install function has grown significantly complex with multiple branching paths for HTTP vs STDIO servers, argument filtering, and variable replacement. Consider breaking this into smaller, more focused functions.

    def install(server_name, force=False, alias=None):
        """Install an MCP server to the global configuration.
    
        Installs servers to the global MCPM configuration where they can be
        used across all MCP clients and organized into profiles.
    
        Examples:
    
        \b
            mcpm install time
            mcpm install everything --force
            mcpm install youtube --alias yt
        """
    
        config_name = alias or server_name
    
        # All servers are installed to global configuration
        console.print("[yellow]Installing server to global configuration...[/]")
    
        # Get server metadata from repository
        server_metadata = repo_manager.get_server_metadata(server_name)
        if not server_metadata:
            console.print(f"[bold red]Error:[/] Server '{server_name}' not found in registry.")
            console.print(f"Available servers: {', '.join(repo_manager._fetch_servers().keys())}")
            return
    
        # Display server information
        display_name = server_metadata.get("display_name", server_name)
        description = server_metadata.get("description", "No description available")
        author_info = server_metadata.get("author", {})
    
        console.print(f"\n[bold]{display_name}[/] ({server_name})")
        console.print(f"[dim]{description}[/]")
    
        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 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)
    
        server_dir = os.path.join(servers_dir, server_name)
        os.makedirs(server_dir, exist_ok=True)
    
        # Extract installation information
        installations = server_metadata.get("installations", {})
    
        # If no installation information is available, create minimal default values
        # This allows us to add the server config without full installation details
        installation_method = "manual"  # Single consolidated concept
        install_command = "echo"
        install_args = [f"Server {server_name} added to configuration"]
        package_name = None
        env_vars = {}
        required_args = {}
    
        # Process installation information if available
        selected_method = None  # Initialize selected_method to None to avoid UnboundLocalError
        if installations:
            # Find recommended installation method or default to the first one
            method_id = "default"  # ID of the method in the config
    
            # First check for a recommended method
            for key, method in installations.items():
                if method.get("recommended", False):
                    selected_method = method
                    method_id = key
                    break
    
            # If no recommended method found, use the first one
            if not selected_method and installations:
                method_id = next(iter(installations))
                selected_method = installations[method_id]
    
            # If multiple methods are available and not forced, offer selection
            if len(installations) > 1 and not force:
                console.print("\n[bold]Available installation methods:[/]")
                methods_list = []
    
                for i, (key, method) in enumerate(installations.items(), 1):
                    method_type = method.get("type", "unknown")
                    description = method.get("description", f"{method_type} installation")
                    recommended = " [green](recommended)[/]" if method.get("recommended", False) else ""
    
                    console.print(f"  {i}. [cyan]{key}[/]: {description}{recommended}")
                    methods_list.append(key)
    
                # Ask user to select a method
                try:
                    selection = click.prompt(
                        "\nSelect installation method", type=int, default=methods_list.index(method_id) + 1
                    )
                    if 1 <= selection <= len(methods_list):
                        method_id = methods_list[selection - 1]
                        selected_method = installations[method_id]
                except (ValueError, click.Abort):
                    console.print("[yellow]Using default installation method.[/]")
    
            # Extract installation details
            if selected_method:
                # Use the method's type as the installation method if available, otherwise use the key
                installation_method = selected_method.get("type")
                if not installation_method or installation_method == "unknown":
                    installation_method = method_id
    
                install_command = selected_method.get("command", install_command)
                install_args = selected_method.get("args", install_args)
                package_name = selected_method.get("package", package_name)
                env_vars = selected_method.get("env", env_vars)
    
            console.print(f"\n[green]Using [bold]{installation_method}[/] installation method[/]")
    
        # Configure the server
        with Progress(SpinnerColumn(), TextColumn("[bold green]{task.description}[/]"), console=console) as progress:
            # Save metadata to server directory
            progress.add_task("Saving server metadata...", total=None)
            metadata_path = os.path.join(server_dir, "metadata.json")
            with open(metadata_path, "w", encoding="utf-8") as f:
                json.dump(server_metadata, f, indent=2)
    
            # Configure the server
            progress.add_task(f"Configuring {server_name}...", total=None)
    
            # Get all available arguments from the server metadata
            all_arguments = server_metadata.get("arguments", {})
    
            required_args = {}
            # Process variables to store in config
            processed_variables = {}
    
            # Extract which arguments are actually referenced in the selected installation method
            referenced_vars = _extract_referenced_variables(selected_method) if selected_method else set()
    
            # Filter arguments to only those that are referenced
            relevant_arguments = {}
            if all_arguments:
                if referenced_vars:
                    # Only include arguments that are referenced
                    for arg_name, arg_info in all_arguments.items():
                        if arg_name in referenced_vars:
                            relevant_arguments[arg_name] = arg_info
                elif selected_method:
                    # If we have a selected method but no referenced vars, don't prompt for any
                    relevant_arguments = {}
                else:
                    # No selected method - use all arguments (backward compatibility)
                    relevant_arguments = all_arguments
    
            # First, prompt for relevant arguments
            progress.stop()
            if relevant_arguments:
                console.print("\n[bold]Configure server arguments:[/]")
    
                for arg_name, arg_info in relevant_arguments.items():
                    description = arg_info.get("description", "")
                    is_required = arg_info.get("required", False)
                    example = arg_info.get("example", "")
    
                    # Add required indicator
                    if is_required:
                        required_args[arg_name] = arg_info
                        required_html = "<ansired>(required)</ansired>"
                    else:
                        required_html = "<ansigreen>(optional)</ansigreen>"
    
                    # Build clean prompt text for console display and prompt
                    html_prompt_text = f"{arg_name} {required_html}"
    
                    # Check if the argument is already set in environment
                    env_value = os.environ.get(arg_name, "")
    
                    # Print description if available
                    if description:
                        console.print(f"[dim]{description}[/]")
    
                    if env_value:
                        # Show the existing value as default
                        console.print(f"[green]Found in environment: {env_value}[/]")
                        try:
                            user_value = prompt_with_default(
                                html_prompt_text,
                                default=env_value,
                                hide_input=_should_hide_input(arg_name),
                                required=is_required,
                            )
                            if user_value != env_value:
                                # User provided a different value
                                processed_variables[arg_name] = user_value
                            else:
                                # User use environment value
                                processed_variables[arg_name] = env_value
                        except click.Abort:
                            pass
                    else:
                        # No environment value
                        try:
                            user_value = prompt_with_default(
                                html_prompt_text,
                                default=example if example else "",
                                hide_input=_should_hide_input(arg_name),
                                required=is_required,
                            )
    
                            # Only add non-empty values to the environment
                            if user_value and user_value.strip():
                                processed_variables[arg_name] = user_value
                            # Explicitly don't add anything if the user leaves it blank
                        except click.Abort:
                            if is_required:
                                console.print(f"[yellow]Warning: Required argument {arg_name} not provided.[/]")
    
                # Resume progress display
                progress = Progress(SpinnerColumn(), TextColumn("[bold green]{task.description}[/]"), console=console)
                progress.start()
                progress.add_task(f"Configuring {server_name}...", total=None)
    
        # replace arguments with values
        processed_args = []
        has_non_standard_argument_define = False
        for i, arg in enumerate(install_args):
            prev_arg = install_args[i - 1] if i > 0 else ""
            # handles arguments with pattern var=${var} | --var=var | --var var
            arg_replaced, replacement_status = _replace_argument_variables(arg, prev_arg, processed_variables)
            processed_args.append(arg_replaced)
            if replacement_status == ReplacementStatus.NON_STANDARD_REPLACE:
                has_non_standard_argument_define = True
    
        # process environment variables
        processed_env = {}
        for key, value in env_vars.items():
            # just replace the env value regardless of the variable pattern, ${VAR}/YOUR_VAR/VAR
            env_replaced, replacement_status = _replace_variables(value, processed_variables)
            processed_env[key] = env_replaced if env_replaced else processed_variables.get(key, value)
            if key in processed_variables and replacement_status == ReplacementStatus.NON_STANDARD_REPLACE:
                has_non_standard_argument_define = True
    
        # For HTTP servers, headers should be extracted from the installation method
        # not from processed variables
        processed_headers = {}
        if installation_method == "http" and selected_method:
            # Extract headers from the installation method if defined
            headers_template = selected_method.get("headers", {})
            for key, value in headers_template.items():
                # Replace variables in header values
                header_replaced, _ = _replace_variables(value, processed_variables)
                if header_replaced:
                    processed_headers[key] = header_replaced
                else:
                    # If no replacement, use the original value
                    processed_headers[key] = value
    
        if has_non_standard_argument_define:
            # no matter in argument / env
            console.print(
                "[bold yellow]WARNING:[/] [bold]Non-standard argument format detected in server configuration.[/]\n"
                "[bold cyan]Future versions of MCPM will standardize all arguments in server configuration to use ${VARIABLE_NAME} format.[/]\n"
                "[bold]Please verify that your input arguments are correctly recognized.[/]\n"
            )
    
        # Get actual MCP execution command, args, and env from the selected installation method
        # This ensures we use the actual server command information instead of placeholders
        mcp_url = None
        mcp_command = None
        mcp_args = []
    
        if selected_method:
            # For HTTP servers, extract the URL and don't set command/args
            if installation_method == "http":
                mcp_url = selected_method.get("url")
                # HTTP servers don't have command/args
            else:
                # For non-HTTP servers, get command and args
                mcp_command = selected_method.get("command", install_command)
                mcp_args = processed_args
            # Env vars are already processed above
        else:
            # Fallback for when no selected method
            mcp_command = install_command
            mcp_args = processed_args
    
        # Create server configuration using FullServerConfig
        full_server_config = FullServerConfig(
            name=config_name,
            display_name=display_name,
            description=description,
            command=mcp_command,  # Use the actual MCP server command
            args=mcp_args,  # Use the actual MCP server arguments
            env=processed_env,
            # Use the simplified installation method
            installation=installation_method,
            url=mcp_url,  # Include URL for HTTP servers
            headers=processed_headers,  # Include headers for HTTP servers
        )
    
        # Add server to global configuration
        success = global_add_server(full_server_config.to_server_config(), force)
    
        if success:
    Validation Gap

    The to_server_config method determines server type based on installation method and URL presence, but lacks validation to ensure HTTP servers have required URL field and STDIO servers have required command field.

    def to_server_config(self) -> ServerConfig:
        """Convert FullServerConfig to a common server configuration format"""
        # Check if this is an HTTP server
        if self.installation == "http" and self.url:
            # For HTTP servers, use the headers field directly
            return RemoteServerConfig(name=self.name, url=self.url, headers=self.headers)
        else:
            # Default to STDIO for all other cases
            # Command is required for STDIO servers
            if not self.command:
                raise ValueError(f"Command is required for non-HTTP server '{self.name}'")
            return STDIOServerConfig(name=self.name, command=self.command, args=self.args, env=self.env)
    Type Safety

    The type checking logic for server configurations uses isinstance checks but imports are done within the function scope, which could impact performance and maintainability.

    # Log command details based on server type
    from mcpm.core.schema import RemoteServerConfig, STDIOServerConfig
    
    if isinstance(server_config, STDIOServerConfig):
        logger.debug(f"Command: {server_config.command} {' '.join(server_config.args or [])}")
    elif isinstance(server_config, RemoteServerConfig):
        logger.debug(f"URL: {server_config.url}")
        if server_config.headers:
            logger.debug(f"Headers: {list(server_config.headers.keys())}")

    - Update fastmcp to v2.10.2 for improved HTTP server support
    - Update GitHub server configuration to use OAuth HTTP endpoint
    - Simplify GitHub server config to use GITHUB_PERSONAL_ACCESS_TOKEN
    - Remove custom Go installation method for cleaner config
    
    🤖 Generated with [Claude Code](https://claude.ai/code)
    
    Co-Authored-By: Claude <[email protected]>
    @qodo-merge-pro
    Copy link
    Contributor

    qodo-merge-pro bot commented Jul 9, 2025

    PR Code Suggestions ✨

    Latest suggestions up to e39adaf

    CategorySuggestion                                                                                                                                    Impact
    Possible issue
    Fix undefined function call

    The code calls _replace_variables() function but this function is not defined in
    the current file. This will cause a NameError when processing HTTP servers with
    headers that contain variables.

    src/mcpm/commands/install.py [370-383]

     # For HTTP servers, headers should be extracted from the installation method
     # not from processed variables
     processed_headers = {}
     if installation_method == "http" and selected_method:
         # Extract headers from the installation method if defined
         headers_template = selected_method.get("headers", {})
         for key, value in headers_template.items():
             # Replace variables in header values
    -        header_replaced, _ = _replace_variables(value, processed_variables)
    -        if header_replaced:
    +        header_replaced, _ = _replace_argument_variables(value, value, processed_variables)
    +        if header_replaced != value:
                 processed_headers[key] = header_replaced
             else:
                 # If no replacement, use the original value
                 processed_headers[key] = value
    • Apply / Chat
    Suggestion importance[1-10]: 9

    __

    Why: The suggestion correctly identifies a NameError due to a call to the undefined function _replace_variables and proposes a correct fix using the existing _replace_argument_variables function.

    High
    Organization
    best practice
    Enhance help text examples

    The help text follows the correct pattern with backslash-escaped examples and
    proper help options. However, the examples section could be more descriptive to
    better explain what each command does. Consider adding brief descriptions after
    each example to clarify the purpose.

    src/mcpm/commands/install.py [122-135]

     @click.help_option("-h", "--help")
     def install(server_name, force=False, alias=None):
         """Install an MCP server to the global configuration.
     
         Installs servers to the global MCPM configuration where they can be
         used by any MCP client.
     
         Examples:
     
         \b
    -        mcpm install time
    -        mcpm install everything --force
    -        mcpm install youtube --alias yt
    +        mcpm install time          # Install the time server
    +        mcpm install everything --force    # Force reinstall if exists
    +        mcpm install youtube --alias yt    # Install with custom alias
         """
    • Apply / Chat
    Suggestion importance[1-10]: 6

    __

    Why:
    Relevant best practice - When implementing command-line interfaces with Click, use consistent help option patterns and provide clear, structured help text with examples. Include both short (-h) and long (--help) options, and format examples using backslash-escaped blocks for proper display.

    Low
    Incremental [*]
    Clarify installation type handling

    The method assumes that any non-HTTP installation type should default to STDIO,
    but there might be other installation types (like 'docker', 'custom', etc.) that
    should also be handled as STDIO. Consider being more explicit about which
    installation types map to which server config types.

    src/mcpm/schemas/full_server_config.py [75-84]

     if self.installation == "http":
         if not self.url:
             raise ValueError(f"URL is required for HTTP server '{self.name}'")
         return RemoteServerConfig(name=self.name, url=self.url, headers=self.headers)
     else:
    -    # Default to STDIO for all other cases
    -    # Command is required for STDIO servers
    +    # Handle STDIO-based installations (docker, custom, etc.)
         if not self.command:
    -        raise ValueError(f"Command is required for non-HTTP server '{self.name}'")
    +        raise ValueError(f"Command is required for STDIO server '{self.name}'")
         return STDIOServerConfig(name=self.name, command=self.command, args=self.args, env=self.env)
    • Apply / Chat
    Suggestion importance[1-10]: 2

    __

    Why: The suggestion correctly points out that the else block handles all non-HTTP cases as STDIO, but the proposed change only adds a comment and does not alter the logic, offering minimal value.

    Low
    • Update

    Previous suggestions

    ✅ Suggestions up to commit ce98f8f
    CategorySuggestion                                                                                                                                    Impact
    General
    Validate required fields before object creation
    Suggestion Impact:The commit implemented the exact validation logic suggested, changing the condition from "http and url" to just "http" and adding explicit URL validation with a proper error message

    code diff:

    -        if self.installation == "http" and self.url:
    -            # For HTTP servers, use the headers field directly
    +        if self.installation == "http":
    +            if not self.url:
    +                raise ValueError(f"URL is required for HTTP server '{self.name}'")
                 return RemoteServerConfig(name=self.name, url=self.url, headers=self.headers)

    The method assumes that any server with installation == "http" and a URL should
    be a RemoteServerConfig, but this logic is fragile. Consider validating that
    required fields exist before creating the config objects to prevent potential
    runtime errors.

    src/mcpm/schemas/full_server_config.py [72-83]

     def to_server_config(self) -> ServerConfig:
         """Convert FullServerConfig to a common server configuration format"""
         # Check if this is an HTTP server
    -    if self.installation == "http" and self.url:
    -        # For HTTP servers, use the headers field directly
    +    if self.installation == "http":
    +        if not self.url:
    +            raise ValueError(f"URL is required for HTTP server '{self.name}'")
             return RemoteServerConfig(name=self.name, url=self.url, headers=self.headers)
         else:
             # Default to STDIO for all other cases
             # Command is required for STDIO servers
             if not self.command:
                 raise ValueError(f"Command is required for non-HTTP server '{self.name}'")
             return STDIOServerConfig(name=self.name, command=self.command, args=self.args, env=self.env)

    [Suggestion processed]

    Suggestion importance[1-10]: 6

    __

    Why: The suggestion correctly identifies that the current logic could produce a misleading error message for an HTTP server missing a URL and improves the validation logic to provide a more accurate error.

    Low

    Hide the FastMCP startup banner by setting show_banner=False for both
    stdio and HTTP modes. This provides a cleaner user experience without
    the verbose FastMCP branding output.
    
    🤖 Generated with [Claude Code](https://claude.ai/code)
    
    Co-Authored-By: Claude <[email protected]>
    @github-actions
    Copy link
    Contributor

    github-actions bot commented Jul 9, 2025

    Summary

    Re-organises the CLI for v2: addinstall, removeuninstall, deletes the old target_operations folder, updates imports/tests/configs, and tweaks schema/lock files ( +428 / -298 ).

    Notes

    • New files: src/mcpm/commands/{install,uninstall}.py (logic copied/trimmed from old modules).
    • Removes target_operations/common.py; test suite adjusted accordingly.
    • Small edits to servers/github.json, pyproject.toml, schema helpers, and uv.lock.

    Review

    Nice cleanup that aligns command names with common package-manager verbs.

    ✅ Logic looks untouched, tests for install pass.
    ⚠️ Consider:

    • Adding a shim or alias so old add/remove commands don’t break existing scripts.
    • Unit tests for the new uninstall flow.
    • Update CLI help / docs to reflect the new verbs.

    View workflow run

    Co-authored-by: qodo-merge-pro[bot] <151058649+qodo-merge-pro[bot]@users.noreply.github.com>
    niechen and others added 2 commits July 8, 2025 23:18
    - Remove --debug option from mcpm run and mcpm profile run commands
    - Remove debug parameter from proxy factory and convenience functions
    - Keep MCPMDebugMiddleware class for future use
    - Add --host option to both run commands for network binding flexibility
    - Fix type annotations in middleware and proxy for Optional parameters
    - Remove unused imports and variables
    
    🤖 Generated with [Claude Code](https://claude.ai/code)
    
    Co-Authored-By: Claude <[email protected]>
    … debug
    
    Missing client config files are normal when clients aren't installed.
    This reduces noise in the default log output while still providing
    debug information when needed.
    @niechen niechen enabled auto-merge (squash) July 9, 2025 07:34
    niechen and others added 2 commits July 9, 2025 00:35
    Update test_client_edit_command_client_not_installed to match current behavior
    where undetected clients show warning messages instead of calling print_error.
    The command now continues with interactive selection after showing appropriate
    warnings about config file creation.
    
    🤖 Generated with [Claude Code](https://claude.ai/code)
    
    Co-Authored-By: Claude <[email protected]>
    Apply ruff formatting fixes to multiple files:
    - Fix import ordering in install.py
    - Format long function signatures in run.py and profile/run.py
    - Remove unused imports in middleware.py and proxy.py
    - Ensure consistent code style across the codebase
    
    🤖 Generated with [Claude Code](https://claude.ai/code)
    
    Co-Authored-By: Claude <[email protected]>
    @niechen niechen merged commit 6ebca95 into main Jul 9, 2025
    7 checks passed
    @niechen niechen deleted the feat/move-commands-to-v2-structure branch July 9, 2025 07:55
    mcpm-semantic-release bot pushed a commit that referenced this pull request Jul 9, 2025
    # [2.2.0](v2.1.0...v2.2.0) (2025-07-09)
    
    ### Features
    
    * refactor commands to v2 structure and add HTTP server support ([#211](#211)) ([6ebca95](6ebca95))
    @mcpm-semantic-release
    Copy link

    🎉 This PR is included in version 2.2.0 🎉

    The release is available on GitHub release

    Your semantic-release bot 📦🚀

    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.

    [Feature]: support installing http servers from mcpm.sh

    3 participants