Skip to content

Commit 80f67a1

Browse files
author
LittleCoinCoin
committed
feat(cli): implement hatch mcp sync command with advanced options
Add comprehensive sync command with full CLI integration: - Mutually exclusive source options: --from-env and --from-host - Required target specification: --to-host with multi-host support - Server filtering options: --servers and --pattern (mutually exclusive) - Standard operation flags: --dry-run, --auto-approve, --no-backup Command structure: hatch mcp sync [SOURCE] [TARGET] [FILTERS] [OPTIONS] Key features: - Argument parsing with proper validation and error messages - Integration with parse_host_list for host resolution - TTY-safe user confirmation using request_confirmation() - Detailed dry-run preview and progress reporting - Proper error handling and user feedback Fixes host list parsing issues by using correct string-returning function. Implements Phase 3f CLI requirements with intuitive user experience.
1 parent 9ed6ec6 commit 80f67a1

File tree

1 file changed

+117
-0
lines changed

1 file changed

+117
-0
lines changed

hatch/cli_hatch.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,90 @@ def handle_mcp_remove_host(host_name: str, no_backup: bool = False,
767767
print(f"Error removing host configuration: {e}")
768768
return 1
769769

770+
def handle_mcp_sync(from_env: Optional[str] = None,
771+
from_host: Optional[str] = None,
772+
to_hosts: Optional[str] = None,
773+
servers: Optional[str] = None,
774+
pattern: Optional[str] = None,
775+
dry_run: bool = False,
776+
auto_approve: bool = False,
777+
no_backup: bool = False) -> int:
778+
"""Handle 'hatch mcp sync' command."""
779+
try:
780+
# Parse target hosts
781+
if not to_hosts:
782+
print("Error: Must specify --to-host")
783+
return 1
784+
785+
target_hosts = parse_host_list(to_hosts)
786+
787+
# Parse server filters
788+
server_list = None
789+
if servers:
790+
server_list = [s.strip() for s in servers.split(',') if s.strip()]
791+
792+
if dry_run:
793+
source_desc = f"environment '{from_env}'" if from_env else f"host '{from_host}'"
794+
target_desc = f"hosts: {', '.join(target_hosts)}"
795+
print(f"[DRY RUN] Would synchronize from {source_desc} to {target_desc}")
796+
797+
if server_list:
798+
print(f"[DRY RUN] Server filter: {', '.join(server_list)}")
799+
elif pattern:
800+
print(f"[DRY RUN] Pattern filter: {pattern}")
801+
802+
print(f"[DRY RUN] Backup: {'Disabled' if no_backup else 'Enabled'}")
803+
return 0
804+
805+
# Confirm operation unless auto-approved
806+
source_desc = f"environment '{from_env}'" if from_env else f"host '{from_host}'"
807+
target_desc = f"{len(target_hosts)} host(s)"
808+
if not request_confirmation(
809+
f"Synchronize MCP configurations from {source_desc} to {target_desc}?",
810+
auto_approve
811+
):
812+
print("Operation cancelled.")
813+
return 0
814+
815+
# Perform synchronization
816+
mcp_manager = MCPHostConfigurationManager()
817+
result = mcp_manager.sync_configurations(
818+
from_env=from_env,
819+
from_host=from_host,
820+
to_hosts=target_hosts,
821+
servers=server_list,
822+
pattern=pattern,
823+
no_backup=no_backup
824+
)
825+
826+
if result.success:
827+
print(f"[SUCCESS] Synchronization completed")
828+
print(f" Servers synced: {result.servers_synced}")
829+
print(f" Hosts updated: {result.hosts_updated}")
830+
831+
# Show detailed results
832+
for res in result.results:
833+
if res.success:
834+
backup_info = f" (backup: {res.backup_path})" if res.backup_path else ""
835+
print(f" ✓ {res.hostname}{backup_info}")
836+
else:
837+
print(f" ✗ {res.hostname}: {res.error_message}")
838+
839+
return 0
840+
else:
841+
print(f"[ERROR] Synchronization failed")
842+
for res in result.results:
843+
if not res.success:
844+
print(f" ✗ {res.hostname}: {res.error_message}")
845+
return 1
846+
847+
except ValueError as e:
848+
print(f"Error: {e}")
849+
return 1
850+
except Exception as e:
851+
print(f"Error during synchronization: {e}")
852+
return 1
853+
770854
def main():
771855
"""Main entry point for Hatch CLI.
772856
@@ -954,6 +1038,27 @@ def main():
9541038
mcp_remove_host_parser.add_argument("--dry-run", action="store_true", help="Preview removal without execution")
9551039
mcp_remove_host_parser.add_argument("--auto-approve", action="store_true", help="Skip confirmation prompts")
9561040

1041+
# MCP synchronization command
1042+
mcp_sync_parser = mcp_subparsers.add_parser("sync", help="Synchronize MCP configurations between environments and hosts")
1043+
1044+
# Source options (mutually exclusive)
1045+
sync_source_group = mcp_sync_parser.add_mutually_exclusive_group(required=True)
1046+
sync_source_group.add_argument("--from-env", help="Source environment name")
1047+
sync_source_group.add_argument("--from-host", help="Source host platform")
1048+
1049+
# Target options
1050+
mcp_sync_parser.add_argument("--to-host", required=True, help="Target hosts (comma-separated or 'all')")
1051+
1052+
# Filter options (mutually exclusive)
1053+
sync_filter_group = mcp_sync_parser.add_mutually_exclusive_group()
1054+
sync_filter_group.add_argument("--servers", help="Specific server names to sync (comma-separated)")
1055+
sync_filter_group.add_argument("--pattern", help="Regex pattern for server selection")
1056+
1057+
# Standard options
1058+
mcp_sync_parser.add_argument("--dry-run", action="store_true", help="Preview synchronization without execution")
1059+
mcp_sync_parser.add_argument("--auto-approve", action="store_true", help="Skip confirmation prompts")
1060+
mcp_sync_parser.add_argument("--no-backup", action="store_true", help="Skip backup creation before synchronization")
1061+
9571062
# Package management commands
9581063
pkg_subparsers = subparsers.add_parser("package", help="Package management commands").add_subparsers(
9591064
dest="pkg_command", help="Package command to execute"
@@ -1468,6 +1573,18 @@ def main():
14681573
print("Unknown remove command")
14691574
return 1
14701575

1576+
elif args.mcp_command == "sync":
1577+
return handle_mcp_sync(
1578+
from_env=getattr(args, 'from_env', None),
1579+
from_host=getattr(args, 'from_host', None),
1580+
to_hosts=args.to_host,
1581+
servers=getattr(args, 'servers', None),
1582+
pattern=getattr(args, 'pattern', None),
1583+
dry_run=args.dry_run,
1584+
auto_approve=args.auto_approve,
1585+
no_backup=args.no_backup
1586+
)
1587+
14711588
else:
14721589
print("Unknown MCP command")
14731590
return 1

0 commit comments

Comments
 (0)