@@ -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+
770854def 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