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();
};
}
}