diff --git a/MCPForUnity/Editor/MenuItems/MCPForUnityMenu.cs b/MCPForUnity/Editor/MenuItems/MCPForUnityMenu.cs index 32fde720..10f05248 100644 --- a/MCPForUnity/Editor/MenuItems/MCPForUnityMenu.cs +++ b/MCPForUnity/Editor/MenuItems/MCPForUnityMenu.cs @@ -5,42 +5,25 @@ namespace MCPForUnity.Editor.MenuItems { - /// - /// Centralized menu items for MCP For Unity - /// public static class MCPForUnityMenu { - // ======================================== - // Main Menu Items - // ======================================== - - /// - /// Show the Setup Window - /// [MenuItem("Window/MCP For Unity/Setup Window", priority = 1)] public static void ShowSetupWindow() { SetupWindowService.ShowSetupWindow(); } - /// - /// Toggle the main MCP For Unity window - /// [MenuItem("Window/MCP For Unity/Toggle MCP Window %#m", priority = 2)] public static void ToggleMCPWindow() { - if (EditorWindow.HasOpenInstances()) + if (MCPForUnityEditorWindow.HasAnyOpenWindow()) { - foreach (var window in UnityEngine.Resources.FindObjectsOfTypeAll()) - { - window.Close(); - } + MCPForUnityEditorWindow.CloseAllOpenWindows(); } else { MCPForUnityEditorWindow.ShowWindow(); } } - } } diff --git a/MCPForUnity/Editor/Setup/SetupWindowService.cs b/MCPForUnity/Editor/Setup/SetupWindowService.cs index 2045ba45..99a037a2 100644 --- a/MCPForUnity/Editor/Setup/SetupWindowService.cs +++ b/MCPForUnity/Editor/Setup/SetupWindowService.cs @@ -18,7 +18,11 @@ public static class SetupWindowService { private const string SETUP_COMPLETED_KEY = EditorPrefKeys.SetupCompleted; private const string SETUP_DISMISSED_KEY = EditorPrefKeys.SetupDismissed; - private static bool _hasCheckedThisSession = false; + + // Use SessionState to persist "checked this editor session" across domain reloads. + // SessionState survives assembly reloads within the same Editor session, which prevents + // the setup window from reappearing after code reloads / playmode transitions. + private const string SessionCheckedKey = "MCPForUnity.SetupWindowCheckedThisEditorSession"; static SetupWindowService() { @@ -35,10 +39,12 @@ static SetupWindowService() /// private static void CheckSetupNeeded() { - if (_hasCheckedThisSession) + // Ensure we only run once per Editor session (survives domain reloads). + // This avoids showing the setup dialog repeatedly when scripts recompile or Play mode toggles. + if (SessionState.GetBool(SessionCheckedKey, false)) return; - _hasCheckedThisSession = true; + SessionState.SetBool(SessionCheckedKey, true); try { @@ -48,11 +54,16 @@ private static void CheckSetupNeeded() bool userOverrodeHttpUrl = EditorPrefs.HasKey(EditorPrefKeys.HttpBaseUrl); // In Asset Store builds with a remote default URL (and no user override), skip the local setup wizard. - if (!userOverrodeHttpUrl + if ( + !userOverrodeHttpUrl && McpDistribution.Settings.skipSetupWindowWhenRemoteDefault - && McpDistribution.Settings.IsRemoteDefault) + && McpDistribution.Settings.IsRemoteDefault + ) { - McpLog.Info("Skipping Setup Window because this distribution ships with a hosted MCP URL. Open Window/MCP For Unity/Setup Window if you want to configure a local runtime.", always: false); + McpLog.Info( + "Skipping Setup Window because this distribution ships with a hosted MCP URL. Open Window/MCP For Unity/Setup Window if you want to configure a local runtime.", + always: false + ); return; } @@ -66,7 +77,10 @@ private static void CheckSetupNeeded() } else { - McpLog.Info("Setup Window skipped - previously completed or dismissed", always: false); + McpLog.Info( + "Setup Window skipped - previously completed or dismissed", + always: false + ); } } catch (Exception ex) @@ -108,6 +122,5 @@ public static void MarkSetupDismissed() EditorPrefs.SetBool(SETUP_DISMISSED_KEY, true); McpLog.Info("Setup marked as dismissed"); } - } } diff --git a/MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs b/MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs index 1dfada10..ccc47ca9 100644 --- a/MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs +++ b/MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs @@ -1,14 +1,14 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using UnityEditor; -using UnityEngine; -using UnityEngine.UIElements; using MCPForUnity.Editor.Helpers; using MCPForUnity.Editor.Services; -using MCPForUnity.Editor.Windows.Components.Settings; -using MCPForUnity.Editor.Windows.Components.Connection; using MCPForUnity.Editor.Windows.Components.ClientConfig; +using MCPForUnity.Editor.Windows.Components.Connection; +using MCPForUnity.Editor.Windows.Components.Settings; +using UnityEditor; +using UnityEngine; +using UnityEngine.UIElements; namespace MCPForUnity.Editor.Windows { @@ -20,14 +20,47 @@ public class MCPForUnityEditorWindow : EditorWindow private McpClientConfigSection clientConfigSection; private static readonly HashSet OpenWindows = new(); + private bool guiCreated = false; public static void ShowWindow() { var window = GetWindow("MCP For Unity"); window.minSize = new Vector2(500, 600); } + + // Helper to check and manage open windows from other classes + public static bool HasAnyOpenWindow() + { + return OpenWindows.Count > 0; + } + + public static void CloseAllOpenWindows() + { + if (OpenWindows.Count == 0) + return; + + // Copy to array to avoid modifying the collection while iterating + var arr = new MCPForUnityEditorWindow[OpenWindows.Count]; + OpenWindows.CopyTo(arr); + foreach (var window in arr) + { + try + { + window?.Close(); + } + catch (Exception ex) + { + McpLog.Warn($"Error closing MCP window: {ex.Message}"); + } + } + } + public void CreateGUI() { + // Guard against repeated CreateGUI calls (e.g., domain reloads) + if (guiCreated) + return; + string basePath = AssetPathUtility.GetMcpPackageRootPath(); // Load main window UXML @@ -37,7 +70,9 @@ public void CreateGUI() if (visualTree == null) { - McpLog.Error($"Failed to load UXML at: {basePath}/Editor/Windows/MCPForUnityEditorWindow.uxml"); + McpLog.Error( + $"Failed to load UXML at: {basePath}/Editor/Windows/MCPForUnityEditorWindow.uxml" + ); return; } @@ -78,8 +113,10 @@ public void CreateGUI() var settingsRoot = settingsTree.Instantiate(); sectionsContainer.Add(settingsRoot); settingsSection = new McpSettingsSection(settingsRoot); - settingsSection.OnGitUrlChanged += () => clientConfigSection?.UpdateManualConfiguration(); - settingsSection.OnHttpServerCommandUpdateRequested += () => connectionSection?.UpdateHttpServerCommandDisplay(); + settingsSection.OnGitUrlChanged += () => + clientConfigSection?.UpdateManualConfiguration(); + settingsSection.OnHttpServerCommandUpdateRequested += () => + connectionSection?.UpdateHttpServerCommandDisplay(); } // Load and initialize Connection section @@ -91,7 +128,8 @@ public void CreateGUI() var connectionRoot = connectionTree.Instantiate(); sectionsContainer.Add(connectionRoot); connectionSection = new McpConnectionSection(connectionRoot); - connectionSection.OnManualConfigUpdateRequested += () => clientConfigSection?.UpdateManualConfiguration(); + connectionSection.OnManualConfigUpdateRequested += () => + clientConfigSection?.UpdateManualConfiguration(); } // Load and initialize Client Configuration section @@ -105,6 +143,8 @@ public void CreateGUI() clientConfigSection = new McpClientConfigSection(clientConfigRoot); } + guiCreated = true; + // Initial updates RefreshAllData(); } @@ -119,6 +159,7 @@ private void OnDisable() { EditorApplication.update -= OnEditorUpdate; OpenWindows.Remove(this); + guiCreated = false; } private void OnFocus() @@ -163,11 +204,11 @@ private void ScheduleHealthCheck() { EditorApplication.delayCall += async () => { - if (this == null) + if (this == null || connectionSection == null) { return; } - await connectionSection?.VerifyBridgeConnectionAsync(); + await connectionSection.VerifyBridgeConnectionAsync(); }; } }