Skip to content

Commit bdfa4c5

Browse files
author
LittleCoinCoin
committed
fix(cli): resolve critical UnboundLocalError in hatch package add
- Fix variable initialization for registry packages by properly handling both local and registry package types - Add comprehensive PackageService creation for all package types with proper error handling - Restore functionality for registry package installations that were failing with UnboundLocalError - Enhance dependency discovery and configuration for both add and sync commands - Add consistent environment tracking updates for all packages (main + dependencies) Resolves critical bug preventing MCP package installations from registry. Ensures both local directory packages and registry packages work correctly. Implements proper dependency chain configuration across all target hosts.
1 parent 17d1cc3 commit bdfa4c5

File tree

1 file changed

+191
-64
lines changed

1 file changed

+191
-64
lines changed

hatch/cli_hatch.py

Lines changed: 191 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1453,38 +1453,111 @@ def main():
14531453
hosts = parse_host_list(args.host)
14541454
env_name = args.env or env_manager.get_current_environment()
14551455

1456-
# Is it a path or a name?
1456+
package_name = args.package_path_or_name
1457+
package_service = None
1458+
1459+
# Check if it's a local package path
14571460
pkg_path = Path(args.package_path_or_name)
14581461
if pkg_path.exists() and pkg_path.is_dir():
1462+
# Local package - load metadata from directory
14591463
with open(pkg_path / "hatch_metadata.json", 'r') as f:
14601464
metadata = json.load(f)
1461-
package_name = metadata['name']
1465+
package_service = PackageService(metadata)
1466+
package_name = package_service.get_field('name')
14621467
else:
1463-
package_name = args.package_path_or_name
1464-
1465-
# Get MCP server configuration for the newly added package
1466-
server_config = get_package_mcp_server_config(env_manager, env_name, package_name)
1468+
# Registry package - get metadata from environment manager
1469+
try:
1470+
env_data = env_manager.get_environment_data(env_name)
1471+
if env_data:
1472+
# Find the package in the environment
1473+
for pkg in env_data.packages:
1474+
if pkg.name == package_name:
1475+
# Create a minimal metadata structure for PackageService
1476+
metadata = {
1477+
"name": pkg.name,
1478+
"version": pkg.version,
1479+
"dependencies": {} # Will be populated if needed
1480+
}
1481+
package_service = PackageService(metadata)
1482+
break
1483+
1484+
if package_service is None:
1485+
print(f"Warning: Could not find package '{package_name}' in environment '{env_name}'. Skipping dependency analysis.")
1486+
package_service = None
1487+
except Exception as e:
1488+
print(f"Warning: Could not load package metadata for '{package_name}': {e}. Skipping dependency analysis.")
1489+
package_service = None
1490+
1491+
# Get dependency names if we have package service
1492+
package_names = []
1493+
if package_service:
1494+
# Get Hatch dependencies
1495+
dependencies = package_service.get_dependencies()
1496+
hatch_deps = dependencies.get('hatch', [])
1497+
package_names = [dep.get('name') for dep in hatch_deps if dep.get('name')]
1498+
1499+
# Resolve local dependency paths to actual names
1500+
for i in range(len(package_names)):
1501+
dep_path = Path(package_names[i])
1502+
if dep_path.exists() and dep_path.is_dir():
1503+
try:
1504+
with open(dep_path / "hatch_metadata.json", 'r') as f:
1505+
dep_metadata = json.load(f)
1506+
dep_service = PackageService(dep_metadata)
1507+
package_names[i] = dep_service.get_field('name')
1508+
except Exception as e:
1509+
print(f"Warning: Could not resolve dependency path '{package_names[i]}': {e}")
1510+
1511+
# Add the main package to the list
1512+
package_names.append(package_name)
1513+
1514+
# Get MCP server configuration for all packages
1515+
server_configs = [get_package_mcp_server_config(env_manager, env_name, pkg_name) for pkg_name in package_names]
14671516

14681517
print(f"Configuring MCP server for package '{package_name}' on {len(hosts)} host(s)...")
14691518

14701519
# Configure on each host
14711520
success_count = 0
14721521
for host in hosts: # 'host', here, is a string
1473-
try:
1474-
result = mcp_manager.configure_server(
1475-
hostname=host,
1476-
server_config=server_config,
1477-
no_backup=False # Always backup when adding packages
1478-
)
1479-
1480-
if result.success:
1481-
print(f"✓ Configured {server_config.name} on {host}")
1482-
success_count += 1
1483-
else:
1484-
print(f"✗ Failed to configure {server_config.name} on {host}: {result.error_message}")
1485-
1486-
except Exception as e:
1487-
print(f"✗ Error configuring {server_config.name} on {host}: {e}")
1522+
host_success_count = 0
1523+
for i, server_config in enumerate(server_configs):
1524+
pkg_name = package_names[i]
1525+
try:
1526+
result = mcp_manager.configure_server(
1527+
hostname=host,
1528+
server_config=server_config,
1529+
no_backup=False # Always backup when adding packages
1530+
)
1531+
1532+
if result.success:
1533+
print(f"✓ Configured {server_config.name} ({pkg_name}) on {host}")
1534+
host_success_count += 1
1535+
1536+
# Update package metadata with host configuration tracking
1537+
try:
1538+
server_config_dict = {
1539+
"name": server_config.name,
1540+
"command": server_config.command,
1541+
"args": server_config.args
1542+
}
1543+
1544+
env_manager.update_package_host_configuration(
1545+
env_name=env_name,
1546+
package_name=pkg_name,
1547+
hostname=host,
1548+
server_config=server_config_dict
1549+
)
1550+
except Exception as e:
1551+
# Log but don't fail the configuration operation
1552+
print(f"[WARNING] Failed to update package metadata for {pkg_name}: {e}")
1553+
else:
1554+
print(f"✗ Failed to configure {server_config.name} ({pkg_name}) on {host}: {result.error_message}")
1555+
1556+
except Exception as e:
1557+
print(f"✗ Error configuring {server_config.name} ({pkg_name}) on {host}: {e}")
1558+
1559+
if host_success_count == len(server_configs):
1560+
success_count += 1
14881561

14891562
if success_count > 0:
14901563
print(f"MCP configuration completed: {success_count}/{len(hosts)} hosts configured")
@@ -1526,68 +1599,122 @@ def main():
15261599
hosts = parse_host_list(args.host)
15271600
env_name = args.env or env_manager.get_current_environment()
15281601

1529-
# Get MCP server configuration for the package
1530-
server_config = get_package_mcp_server_config(env_manager, env_name, args.package_name)
1602+
# Get all packages to sync (main package + dependencies)
1603+
package_names = [args.package_name]
1604+
1605+
# Try to get dependencies for the main package
1606+
try:
1607+
env_data = env_manager.get_environment_data(env_name)
1608+
if env_data:
1609+
# Find the main package in the environment
1610+
main_package = None
1611+
for pkg in env_data.packages:
1612+
if pkg.name == args.package_name:
1613+
main_package = pkg
1614+
break
1615+
1616+
if main_package:
1617+
# Create a minimal metadata structure for PackageService
1618+
metadata = {
1619+
"name": main_package.name,
1620+
"version": main_package.version,
1621+
"dependencies": {} # Will be populated if needed
1622+
}
1623+
package_service = PackageService(metadata)
1624+
1625+
# Get Hatch dependencies
1626+
dependencies = package_service.get_dependencies()
1627+
hatch_deps = dependencies.get('hatch', [])
1628+
dep_names = [dep.get('name') for dep in hatch_deps if dep.get('name')]
1629+
1630+
# Add dependencies to the sync list (before main package)
1631+
package_names = dep_names + [args.package_name]
1632+
else:
1633+
print(f"Warning: Package '{args.package_name}' not found in environment '{env_name}'. Syncing only the specified package.")
1634+
else:
1635+
print(f"Warning: Could not access environment '{env_name}'. Syncing only the specified package.")
1636+
except Exception as e:
1637+
print(f"Warning: Could not analyze dependencies for '{args.package_name}': {e}. Syncing only the specified package.")
1638+
1639+
# Get MCP server configurations for all packages
1640+
server_configs = []
1641+
for pkg_name in package_names:
1642+
try:
1643+
config = get_package_mcp_server_config(env_manager, env_name, pkg_name)
1644+
server_configs.append((pkg_name, config))
1645+
except Exception as e:
1646+
print(f"Warning: Could not get MCP configuration for package '{pkg_name}': {e}")
1647+
1648+
if not server_configs:
1649+
print(f"Error: No MCP server configurations found for package '{args.package_name}' or its dependencies")
1650+
return 1
15311651

15321652
if args.dry_run:
1533-
print(f"[DRY RUN] Would synchronize MCP server for package '{args.package_name}' to hosts: {[h for h in hosts]}")
1534-
print(f"[DRY RUN] Server config: {server_config.name} -> {' '.join(server_config.args)}")
1653+
print(f"[DRY RUN] Would synchronize MCP servers for {len(server_configs)} package(s) to hosts: {[h for h in hosts]}")
1654+
for pkg_name, config in server_configs:
1655+
print(f"[DRY RUN] - {pkg_name}: {config.name} -> {' '.join(config.args)}")
15351656
return 0
15361657

15371658
# Confirm operation unless auto-approved
1659+
package_desc = f"package '{args.package_name}'" if len(server_configs) == 1 else f"{len(server_configs)} packages ('{args.package_name}' + dependencies)"
15381660
if not request_confirmation(
1539-
f"Synchronize MCP server for package '{args.package_name}' to {len(hosts)} host(s)?",
1661+
f"Synchronize MCP servers for {package_desc} to {len(hosts)} host(s)?",
15401662
args.auto_approve
15411663
):
15421664
print("Operation cancelled.")
15431665
return 0
15441666

1545-
# Perform synchronization to each host
1667+
# Perform synchronization to each host for all packages
1668+
total_operations = len(server_configs) * len(hosts)
15461669
success_count = 0
1547-
for host in hosts: # 'host', here, is a string
1548-
try:
1549-
result = mcp_manager.configure_server(
1550-
hostname=host,
1551-
server_config=server_config,
1552-
no_backup=args.no_backup
1553-
)
1554-
1555-
if result.success:
1556-
print(f"[SUCCESS] Successfully configured {server_config.name} on {host}")
1557-
success_count += 1
15581670

1559-
# Update package metadata with host configuration tracking
1560-
try:
1561-
server_config_dict = {
1562-
"name": server_config.name,
1563-
"command": server_config.command,
1564-
"args": server_config.args
1565-
}
1566-
1567-
env_manager.update_package_host_configuration(
1568-
env_name=env_name,
1569-
package_name=args.package_name,
1570-
hostname=host,
1571-
server_config=server_config_dict
1572-
)
1573-
except Exception as e:
1574-
# Log but don't fail the sync operation
1575-
print(f"[WARNING] Failed to update package metadata: {e}")
1576-
else:
1577-
print(f"[ERROR] Failed to configure {server_config.name} on {host}: {result.error_message}")
1578-
1579-
except Exception as e:
1580-
print(f"[ERROR] Error configuring {server_config.name} on {host}: {e}")
1671+
for host in hosts:
1672+
for pkg_name, server_config in server_configs:
1673+
try:
1674+
result = mcp_manager.configure_server(
1675+
hostname=host,
1676+
server_config=server_config,
1677+
no_backup=args.no_backup
1678+
)
1679+
1680+
if result.success:
1681+
print(f"[SUCCESS] Successfully configured {server_config.name} ({pkg_name}) on {host}")
1682+
success_count += 1
1683+
1684+
# Update package metadata with host configuration tracking
1685+
try:
1686+
server_config_dict = {
1687+
"name": server_config.name,
1688+
"command": server_config.command,
1689+
"args": server_config.args
1690+
}
1691+
1692+
env_manager.update_package_host_configuration(
1693+
env_name=env_name,
1694+
package_name=pkg_name,
1695+
hostname=host,
1696+
server_config=server_config_dict
1697+
)
1698+
except Exception as e:
1699+
# Log but don't fail the sync operation
1700+
print(f"[WARNING] Failed to update package metadata for {pkg_name}: {e}")
1701+
else:
1702+
print(f"[ERROR] Failed to configure {server_config.name} ({pkg_name}) on {host}: {result.error_message}")
1703+
1704+
except Exception as e:
1705+
print(f"[ERROR] Error configuring {server_config.name} ({pkg_name}) on {host}: {e}")
15811706

15821707
# Report results
1583-
if success_count == len(hosts):
1584-
print(f"Successfully synchronized package '{args.package_name}' to all {len(hosts)} host(s)")
1708+
if success_count == total_operations:
1709+
package_desc = f"package '{args.package_name}'" if len(server_configs) == 1 else f"{len(server_configs)} packages"
1710+
print(f"Successfully synchronized {package_desc} to all {len(hosts)} host(s)")
15851711
return 0
15861712
elif success_count > 0:
1587-
print(f"Partially synchronized package '{args.package_name}': {success_count}/{len(hosts)} hosts succeeded")
1713+
print(f"Partially synchronized: {success_count}/{total_operations} operations succeeded")
15881714
return 1
15891715
else:
1590-
print(f"Failed to synchronize package '{args.package_name}' to any hosts")
1716+
package_desc = f"package '{args.package_name}'" if len(server_configs) == 1 else f"{len(server_configs)} packages"
1717+
print(f"Failed to synchronize {package_desc} to any hosts")
15911718
return 1
15921719

15931720
except ValueError as e:

0 commit comments

Comments
 (0)