Skip to content

Package Handlers

The package handlers module (cli_package.py) contains handlers for package management commands.

Overview

This module provides handlers for:

  • Package Installation: Add packages to environments
  • Package Removal: Remove packages with confirmation
  • Package Listing: List packages (deprecated - use env list)
  • Package Synchronization: Synchronize package MCP servers to hosts

Handler Functions

Package Management

  • handle_package_add(): Add packages to environments with optional host configuration
  • handle_package_remove(): Remove packages with confirmation
  • handle_package_list(): List packages (deprecated - use hatch env list)
  • handle_package_sync(): Synchronize package MCP servers to hosts

Internal Helpers

  • _get_package_names_with_dependencies(): Get package name and dependencies
  • _configure_packages_on_hosts(): Shared logic for configuring packages on hosts

Handler Signature

All handlers follow the standard signature:

def handle_package_command(args: Namespace) -> int:
    """Handle 'hatch package command' command.

    Args:
        args: Namespace with:
            - env_manager: HatchEnvironmentManager instance
            - mcp_manager: MCPHostConfigurationManager instance
            - <command-specific arguments>

    Returns:
        Exit code (0 for success, 1 for error)
    """

Module Reference

hatch.cli.cli_package

Package CLI handlers for Hatch.

This module contains handlers for package management commands. Packages are MCP server implementations that can be installed into environments and configured on MCP host platforms.

Commands
  • hatch package add : Add a package to an environment
  • hatch package remove : Remove a package from an environment
  • hatch package list: List packages in an environment
  • hatch package sync : Synchronize package MCP servers to hosts
Package Workflow
  1. Add package to environment: hatch package add my-mcp-server
  2. Configure on hosts: hatch mcp configure claude-desktop my-mcp-server ...
  3. Or sync automatically: hatch package sync my-mcp-server --host all
Handler Signature

All handlers follow: (args: Namespace) -> int - args.env_manager: HatchEnvironmentManager instance - Returns: EXIT_SUCCESS (0) on success, EXIT_ERROR (1) on failure

Internal Helpers

_configure_packages_on_hosts(): Shared logic for configuring packages on hosts

Example

$ hatch package add mcp-server-fetch $ hatch package list $ hatch package sync mcp-server-fetch --host claude-desktop,cursor

Classes

Functions

handle_package_add(args)

Handle 'hatch package add' command.

Parameters:

Name Type Description Default
args Namespace

Namespace with: - env_manager: HatchEnvironmentManager instance - mcp_manager: MCPHostConfigurationManager instance - package_path_or_name: Package path or name - env: Optional environment name - version: Optional version - force_download: Force download even if cached - refresh_registry: Force registry refresh - auto_approve: Skip confirmation prompts - host: Optional comma-separated host list for MCP configuration

required

Returns:

Type Description
int

Exit code (0 for success, 1 for error)

Source code in hatch/cli/cli_package.py
def handle_package_add(args: Namespace) -> int:
    """Handle 'hatch package add' command.

    Args:
        args: Namespace with:
            - env_manager: HatchEnvironmentManager instance
            - mcp_manager: MCPHostConfigurationManager instance
            - package_path_or_name: Package path or name
            - env: Optional environment name
            - version: Optional version
            - force_download: Force download even if cached
            - refresh_registry: Force registry refresh
            - auto_approve: Skip confirmation prompts
            - host: Optional comma-separated host list for MCP configuration

    Returns:
        Exit code (0 for success, 1 for error)
    """
    env_manager: "HatchEnvironmentManager" = args.env_manager
    mcp_manager: MCPHostConfigurationManager = args.mcp_manager

    package_path_or_name = args.package_path_or_name
    env = getattr(args, "env", None)
    version = getattr(args, "version", None)
    force_download = getattr(args, "force_download", False)
    refresh_registry = getattr(args, "refresh_registry", False)
    auto_approve = getattr(args, "auto_approve", False)
    host_arg = getattr(args, "host", None)
    dry_run = getattr(args, "dry_run", False)

    # Create reporter for unified output
    reporter = ResultReporter("hatch package add", dry_run=dry_run)

    # Add package to environment
    reporter.add(ConsequenceType.ADD, f"Package '{package_path_or_name}'")

    if not env_manager.add_package_to_environment(
        package_path_or_name,
        env,
        version,
        force_download,
        refresh_registry,
        auto_approve,
    ):
        reporter.report_error(f"Failed to add package '{package_path_or_name}'")
        return EXIT_ERROR

    # Handle MCP host configuration if requested
    if host_arg:
        try:
            hosts = parse_host_list(host_arg)
            env_name = env or env_manager.get_current_environment()

            package_name, package_names, _ = _get_package_names_with_dependencies(
                env_manager, package_path_or_name, env_name
            )

            success_count, total = _configure_packages_on_hosts(
                env_manager=env_manager,
                mcp_manager=mcp_manager,
                env_name=env_name,
                package_names=package_names,
                hosts=hosts,
                no_backup=False,  # Always backup when adding packages
                dry_run=dry_run,
                reporter=reporter,
            )

        except ValueError as e:
            format_warning(f"MCP host configuration failed: {e}")
            # Don't fail the entire operation for MCP configuration issues

    # Report results
    reporter.report_result()
    return EXIT_SUCCESS

handle_package_list(args)

Handle 'hatch package list' command.

.. deprecated:: This command is deprecated. Use 'hatch env list' instead, which shows packages inline with environment information.

Parameters:

Name Type Description Default
args Namespace

Namespace with: - env_manager: HatchEnvironmentManager instance - env: Optional environment name (default: current)

required

Returns:

Type Description
int

Exit code (0 for success)

Source code in hatch/cli/cli_package.py
def handle_package_list(args: Namespace) -> int:
    """Handle 'hatch package list' command.

    .. deprecated::
        This command is deprecated. Use 'hatch env list' instead,
        which shows packages inline with environment information.

    Args:
        args: Namespace with:
            - env_manager: HatchEnvironmentManager instance
            - env: Optional environment name (default: current)

    Returns:
        Exit code (0 for success)
    """
    import sys

    # Emit deprecation warning to stderr
    print(
        "Warning: 'hatch package list' is deprecated. "
        "Use 'hatch env list' instead, which shows packages inline.",
        file=sys.stderr,
    )

    env_manager: "HatchEnvironmentManager" = args.env_manager
    env = getattr(args, "env", None)

    packages = env_manager.list_packages(env)

    if not packages:
        print(f"No packages found in environment: {env}")
        return EXIT_SUCCESS

    print(f"Packages in environment '{env}':")
    for pkg in packages:
        print(
            f"{pkg['name']} ({pkg['version']})\tHatch compliant: {pkg['hatch_compliant']}\tsource: {pkg['source']['uri']}\tlocation: {pkg['source']['path']}"
        )
    return EXIT_SUCCESS

handle_package_remove(args)

Handle 'hatch package remove' command.

Parameters:

Name Type Description Default
args Namespace

Namespace with: - env_manager: HatchEnvironmentManager instance - package_name: Name of package to remove - env: Optional environment name (default: current) - dry_run: Preview changes without execution - auto_approve: Skip confirmation prompt

required

Returns:

Type Description
int

Exit code (0 for success, 1 for error)

Reference: R03 §3.1 (03-mutation_output_specification_v0.md)

Source code in hatch/cli/cli_package.py
def handle_package_remove(args: Namespace) -> int:
    """Handle 'hatch package remove' command.

    Args:
        args: Namespace with:
            - env_manager: HatchEnvironmentManager instance
            - package_name: Name of package to remove
            - env: Optional environment name (default: current)
            - dry_run: Preview changes without execution
            - auto_approve: Skip confirmation prompt

    Returns:
        Exit code (0 for success, 1 for error)

    Reference: R03 §3.1 (03-mutation_output_specification_v0.md)
    """
    env_manager: "HatchEnvironmentManager" = args.env_manager
    package_name = args.package_name
    env = getattr(args, "env", None)
    dry_run = getattr(args, "dry_run", False)
    auto_approve = getattr(args, "auto_approve", False)

    # Create reporter for unified output
    reporter = ResultReporter("hatch package remove", dry_run=dry_run)
    reporter.add(ConsequenceType.REMOVE, f"Package '{package_name}'")

    if dry_run:
        reporter.report_result()
        return EXIT_SUCCESS

    # Show prompt and request confirmation unless auto-approved
    if not auto_approve:
        prompt = reporter.report_prompt()
        if prompt:
            print(prompt)

        if not request_confirmation("Proceed?"):
            format_info("Operation cancelled")
            return EXIT_SUCCESS

    if env_manager.remove_package(package_name, env):
        reporter.report_result()
        return EXIT_SUCCESS
    else:
        reporter.report_error(f"Failed to remove package '{package_name}'")
        return EXIT_ERROR

handle_package_sync(args)

Handle 'hatch package sync' command.

Parameters:

Name Type Description Default
args Namespace

Namespace with: - env_manager: HatchEnvironmentManager instance - mcp_manager: MCPHostConfigurationManager instance - package_name: Package name to sync - host: Comma-separated host list (required) - env: Optional environment name - dry_run: Preview only - auto_approve: Skip confirmation - no_backup: Skip backup creation

required

Returns:

Type Description
int

Exit code (0 for success, 1 for error)

Source code in hatch/cli/cli_package.py
def handle_package_sync(args: Namespace) -> int:
    """Handle 'hatch package sync' command.

    Args:
        args: Namespace with:
            - env_manager: HatchEnvironmentManager instance
            - mcp_manager: MCPHostConfigurationManager instance
            - package_name: Package name to sync
            - host: Comma-separated host list (required)
            - env: Optional environment name
            - dry_run: Preview only
            - auto_approve: Skip confirmation
            - no_backup: Skip backup creation

    Returns:
        Exit code (0 for success, 1 for error)
    """
    env_manager: "HatchEnvironmentManager" = args.env_manager
    mcp_manager: MCPHostConfigurationManager = args.mcp_manager

    package_name = args.package_name
    host_arg = args.host
    env = getattr(args, "env", None)
    dry_run = getattr(args, "dry_run", False)
    auto_approve = getattr(args, "auto_approve", False)
    no_backup = getattr(args, "no_backup", False)

    # Create reporter for unified output
    reporter = ResultReporter("hatch package sync", dry_run=dry_run)

    try:
        # Parse host list
        hosts = parse_host_list(host_arg)
        env_name = env or env_manager.get_current_environment()

        # Get all packages to sync (main package + dependencies)
        package_names = [package_name]

        # Try to get dependencies for the main package
        try:
            env_data = env_manager.get_environment_data(env_name)
            if env_data:
                # Find the main package in the environment
                main_package = None
                for pkg in env_data.packages:
                    if pkg.name == package_name:
                        main_package = pkg
                        break

                if main_package:
                    # Create a minimal metadata structure for PackageService
                    metadata = {
                        "name": main_package.name,
                        "version": main_package.version,
                        "dependencies": {},
                    }
                    package_service = PackageService(metadata)

                    # Get Hatch dependencies
                    dependencies = package_service.get_dependencies()
                    hatch_deps = dependencies.get("hatch", [])
                    dep_names = [
                        dep.get("name") for dep in hatch_deps if dep.get("name")
                    ]

                    # Add dependencies to the sync list (before main package)
                    package_names = dep_names + [package_name]
                else:
                    format_warning(
                        f"Package '{package_name}' not found in environment '{env_name}'",
                        suggestion="Syncing only the specified package",
                    )
            else:
                format_warning(
                    f"Could not access environment '{env_name}'",
                    suggestion="Syncing only the specified package",
                )
        except Exception as e:
            format_warning(
                f"Could not analyze dependencies for '{package_name}': {e}",
                suggestion="Syncing only the specified package",
            )

        # Get MCP server configurations for all packages
        server_configs: List[Tuple[str, MCPServerConfig]] = []
        for pkg_name in package_names:
            try:
                config = get_package_mcp_server_config(env_manager, env_name, pkg_name)
                server_configs.append((pkg_name, config))
            except Exception as e:
                format_warning(
                    f"Could not get MCP configuration for package '{pkg_name}': {e}"
                )

        if not server_configs:
            reporter.report_error(
                f"No MCP server configurations found for package '{package_name}' or its dependencies"
            )
            return EXIT_ERROR

        # Build consequences for preview/confirmation
        for pkg_name, config in server_configs:
            for host in hosts:
                try:
                    host_type = MCPHostType(host)
                    report = generate_conversion_report(
                        operation="create",
                        server_name=config.name,
                        target_host=host_type,
                        config=config,
                        dry_run=dry_run,
                    )
                    reporter.add_from_conversion_report(report)
                except ValueError:
                    reporter.add(ConsequenceType.SKIP, f"Invalid host '{host}'")

        # Show preview and get confirmation
        prompt = reporter.report_prompt()
        if prompt:
            print(prompt)

        if dry_run:
            reporter.report_result()
            return EXIT_SUCCESS

        # Confirm operation unless auto-approved
        if not request_confirmation("Proceed?", auto_approve):
            format_info("Operation cancelled")
            return EXIT_SUCCESS

        # Perform synchronization (reporter already has consequences from preview)
        success_count, total_operations = _configure_packages_on_hosts(
            env_manager=env_manager,
            mcp_manager=mcp_manager,
            env_name=env_name,
            package_names=[pkg_name for pkg_name, _ in server_configs],
            hosts=hosts,
            no_backup=no_backup,
            dry_run=False,
            reporter=None,  # Don't add again, we already have consequences
        )

        # Report results
        reporter.report_result()

        if success_count == total_operations:
            return EXIT_SUCCESS
        elif success_count > 0:
            return EXIT_ERROR
        else:
            return EXIT_ERROR

    except ValueError as e:
        reporter.report_error(str(e))
        return EXIT_ERROR