using System; using System.Threading.Tasks; using MCPForUnity.Editor.Constants; using MCPForUnity.Editor.Helpers; using MCPForUnity.Editor.Services.Transport; using MCPForUnity.Editor.Windows; using UnityEditor; using UnityEngine; namespace MCPForUnity.Editor.Services { /// /// Automatically starts the HTTP MCP bridge on editor load when the user has opted in /// via the "Auto-Start on Editor Load" toggle in Advanced Settings. /// This complements HttpBridgeReloadHandler (which only resumes after domain reloads). /// [InitializeOnLoad] internal static class HttpAutoStartHandler { private const string SessionInitKey = "HttpAutoStartHandler.SessionInitialized"; static HttpAutoStartHandler() { // SessionState resets on editor process start but persists across domain reloads. // Only run once per session — let HttpBridgeReloadHandler handle reload-resume cases. if (SessionState.GetBool(SessionInitKey, false)) return; if (Application.isBatchMode && string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("UNITY_MCP_ALLOW_BATCH"))) { return; } // Only check lightweight EditorPrefs here — services like EditorConfigurationCache // and MCPServiceLocator may not be initialized yet on fresh editor launch. bool autoStartEnabled = EditorPrefs.GetBool(EditorPrefKeys.AutoStartOnLoad, false); if (!autoStartEnabled) return; SessionState.SetBool(SessionInitKey, true); // Delay to let the editor and services finish initialization. EditorApplication.delayCall += OnEditorReady; } private static void OnEditorReady() { try { bool autoStartEnabled = EditorPrefs.GetBool(EditorPrefKeys.AutoStartOnLoad, false); if (!autoStartEnabled) return; bool useHttp = EditorConfigurationCache.Instance.UseHttpTransport; if (!useHttp) return; // Don't auto-start if bridge is already running. if (MCPServiceLocator.TransportManager.IsRunning(TransportMode.Http)) return; _ = AutoStartAsync(); } catch (Exception ex) { McpLog.Debug($"[HTTP Auto-Start] Deferred check failed: {ex.Message}"); } } private static async Task AutoStartAsync() { try { bool isLocal = !HttpEndpointUtility.IsRemoteScope(); if (isLocal) { // For HTTP Local: launch the server process first, then connect the bridge. // This mirrors what the UI "Start Server" button does. if (!HttpEndpointUtility.IsHttpLocalUrlAllowedForLaunch( HttpEndpointUtility.GetLocalBaseUrl(), out string policyError)) { McpLog.Debug($"[HTTP Auto-Start] Local URL blocked by security policy: {policyError}"); return; } // Check if server is already reachable (e.g. user started it externally). if (!MCPServiceLocator.Server.IsLocalHttpServerReachable()) { bool serverStarted = MCPServiceLocator.Server.StartLocalHttpServer(quiet: true); if (!serverStarted) { McpLog.Warn("[HTTP Auto-Start] Failed to start local HTTP server"); return; } } // Wait for the server to become reachable, then connect. await WaitForServerAndConnectAsync(); } else { // For HTTP Remote: server is external, just connect the bridge. await ConnectBridgeAsync(); } } catch (Exception ex) { McpLog.Warn($"[HTTP Auto-Start] Failed: {ex.Message}"); } } /// /// Waits for the local HTTP server to accept connections, then connects the bridge. /// Mirrors TryAutoStartSessionAsync in McpConnectionSection. /// private static async Task WaitForServerAndConnectAsync() { const int maxAttempts = 30; var shortDelay = TimeSpan.FromMilliseconds(500); var longDelay = TimeSpan.FromSeconds(3); for (int attempt = 0; attempt < maxAttempts; attempt++) { // Abort if user changed settings while we were waiting. if (!EditorPrefs.GetBool(EditorPrefKeys.AutoStartOnLoad, false)) return; if (!EditorConfigurationCache.Instance.UseHttpTransport) return; if (MCPServiceLocator.TransportManager.IsRunning(TransportMode.Http)) return; bool reachable = MCPServiceLocator.Server.IsLocalHttpServerReachable(); if (reachable) { bool started = await MCPServiceLocator.Bridge.StartAsync(); if (started) { McpLog.Info("[HTTP Auto-Start] Bridge started successfully"); MCPForUnityEditorWindow.RequestHealthVerification(); return; } } else if (attempt >= 20 && (attempt - 20) % 3 == 0) { // Last-resort: try connecting even if not detected (process detection may fail). bool started = await MCPServiceLocator.Bridge.StartAsync(); if (started) { McpLog.Info("[HTTP Auto-Start] Bridge started successfully (late connect)"); MCPForUnityEditorWindow.RequestHealthVerification(); return; } } var delay = attempt < 6 ? shortDelay : longDelay; try { await Task.Delay(delay); } catch { return; } } McpLog.Warn("[HTTP Auto-Start] Server did not become reachable after launch"); } /// /// Connects the bridge directly (for remote HTTP where the server is already running). /// private static async Task ConnectBridgeAsync() { bool started = await MCPServiceLocator.Bridge.StartAsync(); if (started) { McpLog.Info("[HTTP Auto-Start] Bridge started successfully (remote)"); MCPForUnityEditorWindow.RequestHealthVerification(); } else { McpLog.Warn("[HTTP Auto-Start] Failed to connect to remote HTTP server"); } } } }