@@ -1107,3 +1107,112 @@ def handle_mcp_remove_host(
11071107 except Exception as e :
11081108 print (f"Error removing host configuration: { e } " )
11091109 return EXIT_ERROR
1110+
1111+
1112+ def handle_mcp_sync (
1113+ from_env : Optional [str ] = None ,
1114+ from_host : Optional [str ] = None ,
1115+ to_hosts : Optional [str ] = None ,
1116+ servers : Optional [str ] = None ,
1117+ pattern : Optional [str ] = None ,
1118+ dry_run : bool = False ,
1119+ auto_approve : bool = False ,
1120+ no_backup : bool = False ,
1121+ ) -> int :
1122+ """Handle 'hatch mcp sync' command.
1123+
1124+ Synchronizes MCP server configurations from a source to target hosts.
1125+
1126+ Args:
1127+ from_env: Source environment name
1128+ from_host: Source host name
1129+ to_hosts: Comma-separated list of target hosts
1130+ servers: Comma-separated list of server names to sync
1131+ pattern: Pattern to filter servers
1132+ dry_run: If True, show what would be done without making changes
1133+ auto_approve: If True, skip confirmation prompt
1134+ no_backup: If True, skip creating backups
1135+
1136+ Returns:
1137+ int: EXIT_SUCCESS (0) on success, EXIT_ERROR (1) on failure
1138+ """
1139+ from hatch .cli .cli_utils import request_confirmation , parse_host_list
1140+
1141+ try :
1142+ # Parse target hosts
1143+ if not to_hosts :
1144+ print ("Error: Must specify --to-host" )
1145+ return EXIT_ERROR
1146+
1147+ target_hosts = parse_host_list (to_hosts )
1148+
1149+ # Parse server filters
1150+ server_list = None
1151+ if servers :
1152+ server_list = [s .strip () for s in servers .split ("," ) if s .strip ()]
1153+
1154+ if dry_run :
1155+ source_desc = (
1156+ f"environment '{ from_env } '" if from_env else f"host '{ from_host } '"
1157+ )
1158+ target_desc = f"hosts: { ', ' .join (target_hosts )} "
1159+ print (f"[DRY RUN] Would synchronize from { source_desc } to { target_desc } " )
1160+
1161+ if server_list :
1162+ print (f"[DRY RUN] Server filter: { ', ' .join (server_list )} " )
1163+ elif pattern :
1164+ print (f"[DRY RUN] Pattern filter: { pattern } " )
1165+
1166+ print (f"[DRY RUN] Backup: { 'Disabled' if no_backup else 'Enabled' } " )
1167+ return EXIT_SUCCESS
1168+
1169+ # Confirm operation unless auto-approved
1170+ source_desc = f"environment '{ from_env } '" if from_env else f"host '{ from_host } '"
1171+ target_desc = f"{ len (target_hosts )} host(s)"
1172+ if not request_confirmation (
1173+ f"Synchronize MCP configurations from { source_desc } to { target_desc } ?" ,
1174+ auto_approve ,
1175+ ):
1176+ print ("Operation cancelled." )
1177+ return EXIT_SUCCESS
1178+
1179+ # Perform synchronization
1180+ mcp_manager = MCPHostConfigurationManager ()
1181+ result = mcp_manager .sync_configurations (
1182+ from_env = from_env ,
1183+ from_host = from_host ,
1184+ to_hosts = target_hosts ,
1185+ servers = server_list ,
1186+ pattern = pattern ,
1187+ no_backup = no_backup ,
1188+ )
1189+
1190+ if result .success :
1191+ print (f"[SUCCESS] Synchronization completed" )
1192+ print (f" Servers synced: { result .servers_synced } " )
1193+ print (f" Hosts updated: { result .hosts_updated } " )
1194+
1195+ # Show detailed results
1196+ for res in result .results :
1197+ if res .success :
1198+ backup_info = (
1199+ f" (backup: { res .backup_path } )" if res .backup_path else ""
1200+ )
1201+ print (f" ✓ { res .hostname } { backup_info } " )
1202+ else :
1203+ print (f" ✗ { res .hostname } : { res .error_message } " )
1204+
1205+ return EXIT_SUCCESS
1206+ else :
1207+ print (f"[ERROR] Synchronization failed" )
1208+ for res in result .results :
1209+ if not res .success :
1210+ print (f" ✗ { res .hostname } : { res .error_message } " )
1211+ return EXIT_ERROR
1212+
1213+ except ValueError as e :
1214+ print (f"Error: { e } " )
1215+ return EXIT_ERROR
1216+ except Exception as e :
1217+ print (f"Error during synchronization: { e } " )
1218+ return EXIT_ERROR
0 commit comments