@@ -353,3 +353,194 @@ def remove_host_configuration(self, hostname: str, no_backup: bool = False) -> C
353353 hostname = hostname ,
354354 error_message = str (e )
355355 )
356+
357+ def sync_configurations (self ,
358+ from_env : Optional [str ] = None ,
359+ from_host : Optional [str ] = None ,
360+ to_hosts : Optional [List [str ]] = None ,
361+ servers : Optional [List [str ]] = None ,
362+ pattern : Optional [str ] = None ,
363+ no_backup : bool = False ) -> SyncResult :
364+ """Advanced synchronization with multiple source/target options.
365+
366+ Args:
367+ from_env (str, optional): Source environment name
368+ from_host (str, optional): Source host name
369+ to_hosts (List[str], optional): Target host names
370+ servers (List[str], optional): Specific server names to sync
371+ pattern (str, optional): Regex pattern for server selection
372+ no_backup (bool, optional): Skip backup creation. Defaults to False.
373+
374+ Returns:
375+ SyncResult: Result of the synchronization operation
376+
377+ Raises:
378+ ValueError: If source specification is invalid
379+ """
380+ import re
381+ from hatch .environment_manager import HatchEnvironmentManager
382+
383+ # Validate source specification
384+ if not from_env and not from_host :
385+ raise ValueError ("Must specify either from_env or from_host as source" )
386+ if from_env and from_host :
387+ raise ValueError ("Cannot specify both from_env and from_host as source" )
388+
389+ # Default to all available hosts if no targets specified
390+ if not to_hosts :
391+ to_hosts = [host .value for host in self .host_registry .detect_available_hosts ()]
392+
393+ try :
394+ # Resolve source data
395+ if from_env :
396+ # Get environment data
397+ env_manager = HatchEnvironmentManager ()
398+ env_data = env_manager .get_environment_data (from_env )
399+ if not env_data :
400+ return SyncResult (
401+ success = False ,
402+ results = [ConfigurationResult (
403+ success = False ,
404+ hostname = "" ,
405+ error_message = f"Environment '{ from_env } ' not found"
406+ )],
407+ servers_synced = 0 ,
408+ hosts_updated = 0
409+ )
410+
411+ # Extract servers from environment
412+ source_servers = {}
413+ for package in env_data .get_mcp_packages ():
414+ # Use package name as server name (single server per package)
415+ source_servers [package .name ] = package .configured_hosts
416+
417+ else : # from_host
418+ # Read host configuration
419+ try :
420+ host_type = MCPHostType (from_host )
421+ strategy = self .host_registry .get_strategy (host_type )
422+ host_config = strategy .read_configuration ()
423+
424+ # Extract servers from host configuration
425+ source_servers = {}
426+ for server_name , server_config in host_config .servers .items ():
427+ source_servers [server_name ] = {
428+ from_host : {"server_config" : server_config }
429+ }
430+
431+ except ValueError :
432+ return SyncResult (
433+ success = False ,
434+ results = [ConfigurationResult (
435+ success = False ,
436+ hostname = "" ,
437+ error_message = f"Invalid source host '{ from_host } '"
438+ )],
439+ servers_synced = 0 ,
440+ hosts_updated = 0
441+ )
442+
443+ # Apply server filtering
444+ if servers :
445+ # Filter by specific server names
446+ filtered_servers = {name : config for name , config in source_servers .items ()
447+ if name in servers }
448+ source_servers = filtered_servers
449+ elif pattern :
450+ # Filter by regex pattern
451+ regex = re .compile (pattern )
452+ filtered_servers = {name : config for name , config in source_servers .items ()
453+ if regex .match (name )}
454+ source_servers = filtered_servers
455+
456+ # Apply synchronization to target hosts
457+ results = []
458+ servers_synced = 0
459+
460+ for target_host in to_hosts :
461+ try :
462+ host_type = MCPHostType (target_host )
463+ strategy = self .host_registry .get_strategy (host_type )
464+
465+ # Read current target configuration
466+ current_config = strategy .read_configuration ()
467+
468+ # Create backup if requested
469+ backup_path = None
470+ if not no_backup and self .backup_manager :
471+ config_path = strategy .get_config_path ()
472+ if config_path and config_path .exists ():
473+ backup_result = self .backup_manager .create_backup (config_path , target_host )
474+ if backup_result .success :
475+ backup_path = backup_result .backup_path
476+
477+ # Add servers to target configuration
478+ host_servers_added = 0
479+ for server_name , server_hosts in source_servers .items ():
480+ # Find appropriate server config for this target host
481+ server_config = None
482+
483+ if from_env :
484+ # For environment source, look for host-specific config
485+ if target_host in server_hosts :
486+ server_config = server_hosts [target_host ]["server_config" ]
487+ elif "claude-desktop" in server_hosts :
488+ # Fallback to claude-desktop config for compatibility
489+ server_config = server_hosts ["claude-desktop" ]["server_config" ]
490+ else :
491+ # For host source, use the server config directly
492+ if from_host in server_hosts :
493+ server_config = server_hosts [from_host ]["server_config" ]
494+
495+ if server_config :
496+ current_config .add_server (server_name , server_config )
497+ host_servers_added += 1
498+
499+ # Write updated configuration
500+ success = strategy .write_configuration (current_config , no_backup = no_backup )
501+
502+ results .append (ConfigurationResult (
503+ success = success ,
504+ hostname = target_host ,
505+ backup_created = backup_path is not None ,
506+ backup_path = backup_path
507+ ))
508+
509+ if success :
510+ servers_synced += host_servers_added
511+
512+ except ValueError :
513+ results .append (ConfigurationResult (
514+ success = False ,
515+ hostname = target_host ,
516+ error_message = f"Invalid target host '{ target_host } '"
517+ ))
518+ except Exception as e :
519+ results .append (ConfigurationResult (
520+ success = False ,
521+ hostname = target_host ,
522+ error_message = str (e )
523+ ))
524+
525+ # Calculate summary statistics
526+ successful_results = [r for r in results if r .success ]
527+ hosts_updated = len (successful_results )
528+
529+ return SyncResult (
530+ success = hosts_updated > 0 ,
531+ results = results ,
532+ servers_synced = servers_synced ,
533+ hosts_updated = hosts_updated
534+ )
535+
536+ except Exception as e :
537+ return SyncResult (
538+ success = False ,
539+ results = [ConfigurationResult (
540+ success = False ,
541+ hostname = "" ,
542+ error_message = f"Synchronization failed: { str (e )} "
543+ )],
544+ servers_synced = 0 ,
545+ hosts_updated = 0
546+ )
0 commit comments