@@ -777,6 +777,91 @@ def _is_reloading_response(resp: object) -> bool:
777777 return _extract_response_reason (resp ) == "reloading"
778778
779779
780+ def _is_editor_offline_error (exc : BaseException ) -> bool :
781+ """Return True for connection failures that mean the Unity Editor is offline."""
782+ text = str (exc ).lower ()
783+ if not text :
784+ return False
785+
786+ offline_markers = (
787+ "no unity editor instances found" ,
788+ "failed to connect to unity instance" ,
789+ "could not connect to unity" ,
790+ "connection refused" ,
791+ "actively refused" ,
792+ "no connection could be made" ,
793+ )
794+ return any (marker in text for marker in offline_markers )
795+
796+
797+ def _editor_offline_response (detail : str , retry_after_ms : int = 1000 ) -> MCPResponse :
798+ return MCPResponse (
799+ success = False ,
800+ error = "editor_offline" ,
801+ message = "Unity Editor is offline or the MCP bridge is not ready." ,
802+ hint = "retry" ,
803+ data = {
804+ "reason" : "editor_offline" ,
805+ "retry_after_ms" : int (retry_after_ms ),
806+ "detail" : detail ,
807+ },
808+ )
809+
810+
811+ def _get_editor_reconnect_max_wait_s () -> float :
812+ raw_value = os .environ .get ("UNITY_MCP_EDITOR_RECONNECT_MAX_WAIT_S" , "2.0" )
813+ try :
814+ wait_s = float (raw_value )
815+ except ValueError :
816+ logger .warning (
817+ "Invalid UNITY_MCP_EDITOR_RECONNECT_MAX_WAIT_S=%r, using default 2.0" ,
818+ raw_value ,
819+ )
820+ wait_s = 2.0
821+ return max (0.0 , min (wait_s , 30.0 ))
822+
823+
824+ def _get_connection_with_editor_reconnect (
825+ instance_id : str | None ,
826+ command_type : str ,
827+ ) -> UnityConnection | MCPResponse :
828+ """Resolve a Unity connection, waiting briefly for editor restart recovery."""
829+ max_wait_s = _get_editor_reconnect_max_wait_s ()
830+ deadline = time .monotonic () + max_wait_s
831+ last_error : BaseException | None = None
832+ attempt = 0
833+
834+ while True :
835+ try :
836+ return get_unity_connection (instance_id )
837+ except Exception as exc :
838+ if not _is_editor_offline_error (exc ):
839+ raise
840+
841+ last_error = exc
842+ now = time .monotonic ()
843+ if max_wait_s <= 0 or now >= deadline :
844+ logger .info (
845+ "Unity editor offline for command=%s instance=%s: %s" ,
846+ command_type ,
847+ instance_id or "default" ,
848+ exc ,
849+ )
850+ return _editor_offline_response (str (last_error ))
851+
852+ attempt += 1
853+ remaining_s = max (0.0 , deadline - now )
854+ sleep_s = min (remaining_s , 0.25 * (2 ** min (attempt - 1 , 2 )))
855+ logger .debug (
856+ "Unity editor offline; waiting %.3fs before reconnect attempt %d for command=%s instance=%s" ,
857+ sleep_s ,
858+ attempt + 1 ,
859+ command_type ,
860+ instance_id or "default" ,
861+ )
862+ time .sleep (sleep_s )
863+
864+
780865def send_command_with_retry (
781866 command_type : str ,
782867 params : dict [str , Any ],
@@ -806,7 +891,10 @@ def send_command_with_retry(
806891 t_retry_start = time .time ()
807892 logger .info ("[TIMING-STDIO] send_command_with_retry START command=%s" , command_type )
808893 t_get_conn = time .time ()
809- conn = get_unity_connection (instance_id )
894+ conn_or_response = _get_connection_with_editor_reconnect (instance_id , command_type )
895+ if isinstance (conn_or_response , MCPResponse ):
896+ return conn_or_response
897+ conn = conn_or_response
810898 logger .info ("[TIMING-STDIO] get_unity_connection took %.3fs command=%s" , time .time () - t_get_conn , command_type )
811899 if max_retries is None :
812900 max_retries = getattr (config , "reload_max_retries" , 40 )
0 commit comments