From 06f271926ba36782bd51e68741dfa365fb36bd31 Mon Sep 17 00:00:00 2001 From: David Sarno Date: Fri, 8 Aug 2025 11:23:45 -0700 Subject: [PATCH] =?UTF-8?q?feat(editor):=202x2=20layout=20(Server/Bridge?= =?UTF-8?q?=20|=20Clients/Validation),=20Auto-Setup=20with=20Connected=20?= =?UTF-8?q?=E2=9C=93=20state;=20add=20Debug=20Logs=20toggle=20and=20gate?= =?UTF-8?q?=20verbose=20logs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix(bridge): reuse stored port in StartAutoConnect; guard listener stop to avoid ObjectDisposedException chore(clients): reorder dropdown to Cursor, Claude Code, Windsurf, Claude Desktop, VSCode --- UnityMcpBridge/Editor/Data/McpClients.cs | 101 ++++++++------- UnityMcpBridge/Editor/UnityMcpBridge.cs | 22 ++-- .../Editor/Windows/UnityMcpEditorWindow.cs | 117 ++++++++++++++---- 3 files changed, 160 insertions(+), 80 deletions(-) diff --git a/UnityMcpBridge/Editor/Data/McpClients.cs b/UnityMcpBridge/Editor/Data/McpClients.cs index aa97d33..362ecdc 100644 --- a/UnityMcpBridge/Editor/Data/McpClients.cs +++ b/UnityMcpBridge/Editor/Data/McpClients.cs @@ -9,6 +9,58 @@ namespace UnityMcpBridge.Editor.Data { public List clients = new() { + // 1) Cursor + new() + { + name = "Cursor", + windowsConfigPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + ".cursor", + "mcp.json" + ), + linuxConfigPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + ".cursor", + "mcp.json" + ), + mcpType = McpTypes.Cursor, + configStatus = "Not Configured", + }, + // 2) Claude Code + new() + { + name = "Claude Code", + windowsConfigPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + ".claude.json" + ), + linuxConfigPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + ".claude.json" + ), + mcpType = McpTypes.ClaudeCode, + configStatus = "Not Configured", + }, + // 3) Windsurf + new() + { + name = "Windsurf", + windowsConfigPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + ".codeium", + "windsurf", + "mcp_config.json" + ), + linuxConfigPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + ".codeium", + "windsurf", + "mcp_config.json" + ), + mcpType = McpTypes.Windsurf, + configStatus = "Not Configured", + }, + // 4) Claude Desktop new() { name = "Claude Desktop", @@ -27,36 +79,7 @@ namespace UnityMcpBridge.Editor.Data mcpType = McpTypes.ClaudeDesktop, configStatus = "Not Configured", }, - new() - { - name = "Claude Code", - windowsConfigPath = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), - ".claude.json" - ), - linuxConfigPath = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), - ".claude.json" - ), - mcpType = McpTypes.ClaudeCode, - configStatus = "Not Configured", - }, - new() - { - name = "Cursor", - windowsConfigPath = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), - ".cursor", - "mcp.json" - ), - linuxConfigPath = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), - ".cursor", - "mcp.json" - ), - mcpType = McpTypes.Cursor, - configStatus = "Not Configured", - }, + // 5) VSCode GitHub Copilot new() { name = "VSCode GitHub Copilot", @@ -77,24 +100,6 @@ namespace UnityMcpBridge.Editor.Data mcpType = McpTypes.VSCode, configStatus = "Not Configured", }, - new() - { - name = "Windsurf", - windowsConfigPath = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), - ".codeium", - "windsurf", - "mcp_config.json" - ), - linuxConfigPath = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), - ".codeium", - "windsurf", - "mcp_config.json" - ), - mcpType = McpTypes.Windsurf, - configStatus = "Not Configured", - }, }; // Initialize status enums after construction diff --git a/UnityMcpBridge/Editor/UnityMcpBridge.cs b/UnityMcpBridge/Editor/UnityMcpBridge.cs index 626849b..b7e4af2 100644 --- a/UnityMcpBridge/Editor/UnityMcpBridge.cs +++ b/UnityMcpBridge/Editor/UnityMcpBridge.cs @@ -45,17 +45,10 @@ namespace UnityMcpBridge.Editor try { - // Reuse stored project port when available to avoid port changes during setup + // Prefer stored project port and start using the robust Start() path (with retries/options) currentUnityPort = PortManager.GetPortWithFallback(); - - listener = new TcpListener(IPAddress.Loopback, currentUnityPort); - listener.Start(); - isRunning = true; + Start(); isAutoConnectMode = true; - - Debug.Log($"UnityMcpBridge auto-connected on port {currentUnityPort}"); - Task.Run(ListenerLoop); - EditorApplication.update += ProcessCommands; } catch (Exception ex) { @@ -226,11 +219,12 @@ namespace UnityMcpBridge.Editor try { + // Mark as stopping early to avoid accept logging during disposal + isRunning = false; // Mark heartbeat one last time before stopping WriteHeartbeat(false); listener?.Stop(); listener = null; - isRunning = false; EditorApplication.update -= ProcessCommands; Debug.Log("UnityMcpBridge stopped."); } @@ -261,6 +255,14 @@ namespace UnityMcpBridge.Editor // Fire and forget each client connection _ = HandleClientAsync(client); } + catch (ObjectDisposedException) + { + // Listener was disposed during stop/reload; exit quietly + if (!isRunning) + { + break; + } + } catch (Exception ex) { if (isRunning) diff --git a/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs b/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs index 34103c6..4a2a27a 100644 --- a/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs +++ b/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs @@ -29,6 +29,7 @@ namespace UnityMcpBridge.Editor.Windows private bool lastClientRegisteredOk; private bool lastBridgeVerifiedOk; private string pythonDirOverride = null; + private bool debugLogsEnabled; // Script validation settings private int validationLevelIndex = 1; // Default to Standard @@ -56,6 +57,7 @@ namespace UnityMcpBridge.Editor.Windows // Refresh bridge status isUnityBridgeRunning = UnityMcpBridge.IsRunning; autoRegisterEnabled = EditorPrefs.GetBool("UnityMCP.AutoRegisterEnabled", true); + debugLogsEnabled = EditorPrefs.GetBool("UnityMCP.DebugLogs", false); foreach (McpClient mcpClient in mcpClients.clients) { CheckMcpConfiguration(mcpClient); @@ -148,14 +150,50 @@ namespace UnityMcpBridge.Editor.Windows // Header DrawHeader(); - // Single-column streamlined layout - DrawServerStatusSection(); - EditorGUILayout.Space(6); - DrawBridgeSection(); + // Compute equal column widths for uniform layout + float horizontalSpacing = 2f; + float outerPadding = 20f; // approximate padding + // Make columns a bit less wide for a tighter layout + float computed = (position.width - outerPadding - horizontalSpacing) / 2f; + float colWidth = Mathf.Clamp(computed, 220f, 340f); + // Use fixed heights per row so paired panels match exactly + float topPanelHeight = 190f; + float bottomPanelHeight = 230f; + + // Top row: Server Status (left) and Unity Bridge (right) + EditorGUILayout.BeginHorizontal(); + { + EditorGUILayout.BeginVertical(GUILayout.Width(colWidth), GUILayout.Height(topPanelHeight)); + DrawServerStatusSection(); + EditorGUILayout.EndVertical(); + + EditorGUILayout.Space(horizontalSpacing); + + EditorGUILayout.BeginVertical(GUILayout.Width(colWidth), GUILayout.Height(topPanelHeight)); + DrawBridgeSection(); + EditorGUILayout.EndVertical(); + } + EditorGUILayout.EndHorizontal(); + EditorGUILayout.Space(10); - DrawUnifiedClientConfiguration(); - EditorGUILayout.Space(10); - DrawValidationSection(); + + // Second row: MCP Client Configuration (left) and Script Validation (right) + EditorGUILayout.BeginHorizontal(); + { + EditorGUILayout.BeginVertical(GUILayout.Width(colWidth), GUILayout.Height(bottomPanelHeight)); + DrawUnifiedClientConfiguration(); + EditorGUILayout.EndVertical(); + + EditorGUILayout.Space(horizontalSpacing); + + EditorGUILayout.BeginVertical(GUILayout.Width(colWidth), GUILayout.Height(bottomPanelHeight)); + DrawValidationSection(); + EditorGUILayout.EndVertical(); + } + EditorGUILayout.EndHorizontal(); + + // Minimal bottom padding + EditorGUILayout.Space(2); EditorGUILayout.EndScrollView(); } @@ -205,21 +243,12 @@ namespace UnityMcpBridge.Editor.Windows EditorGUILayout.Space(5); - // Connection mode and Setup controls + // Connection mode EditorGUILayout.BeginHorizontal(); - bool isAutoMode = UnityMcpBridge.IsAutoConnectMode(); GUIStyle modeStyle = new GUIStyle(EditorStyles.miniLabel) { fontSize = 11 }; EditorGUILayout.LabelField($"Mode: {(isAutoMode ? "Auto" : "Standard")}", modeStyle); - GUILayout.FlexibleSpace(); - - // Bind to Clients button - if (GUILayout.Button("Bind to Clients", GUILayout.Width(140), GUILayout.Height(24))) - { - RunSetupNow(); - } - EditorGUILayout.EndHorizontal(); // Current ports display @@ -231,6 +260,27 @@ namespace UnityMcpBridge.Editor.Windows EditorGUILayout.LabelField($"Ports: Unity {currentUnityPort}, MCP {mcpPort}", portStyle); EditorGUILayout.Space(5); + // Auto-Setup button below ports + string setupButtonText = (lastClientRegisteredOk && lastBridgeVerifiedOk) ? "Connected ✓" : "Auto-Setup"; + if (GUILayout.Button(setupButtonText, GUILayout.Height(24))) + { + RunSetupNow(); + } + EditorGUILayout.Space(4); + + // Debug logs toggle inside Server Status + using (new EditorGUILayout.HorizontalScope()) + { + GUILayout.FlexibleSpace(); + bool newDebug = EditorGUILayout.ToggleLeft("Show Debug Logs", debugLogsEnabled, GUILayout.Width(150)); + if (newDebug != debugLogsEnabled) + { + debugLogsEnabled = newDebug; + EditorPrefs.SetBool("UnityMCP.DebugLogs", debugLogsEnabled); + } + } + EditorGUILayout.Space(2); + // Removed redundant inline badges to streamline UI // Troubleshooting helpers @@ -318,7 +368,18 @@ namespace UnityMcpBridge.Editor.Windows EditorGUILayout.Space(8); string description = GetValidationLevelDescription(validationLevelIndex); EditorGUILayout.HelpBox(description, MessageType.Info); - EditorGUILayout.Space(5); + EditorGUILayout.Space(4); + // Inline debug logs toggle under Script Validation + EditorGUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + bool newDebug = EditorGUILayout.ToggleLeft("Show Debug Logs", debugLogsEnabled, GUILayout.Width(150)); + if (newDebug != debugLogsEnabled) + { + debugLogsEnabled = newDebug; + EditorPrefs.SetBool("UnityMCP.DebugLogs", debugLogsEnabled); + } + EditorGUILayout.EndHorizontal(); + EditorGUILayout.Space(2); EditorGUILayout.EndVertical(); } @@ -870,7 +931,10 @@ namespace UnityMcpBridge.Editor.Windows { if (Directory.Exists(devPath) && File.Exists(Path.Combine(devPath, "server.py"))) { - UnityEngine.Debug.Log($"Currently in development mode. Package: {devPath}"); + if (debugLogsEnabled) + { + UnityEngine.Debug.Log($"Currently in development mode. Package: {devPath}"); + } return devPath; } } @@ -931,7 +995,10 @@ namespace UnityMcpBridge.Editor.Windows } // If still not found, return the placeholder path - UnityEngine.Debug.LogWarning("Could not find Python directory, using placeholder path"); + if (debugLogsEnabled) + { + UnityEngine.Debug.LogWarning("Could not find Python directory, using placeholder path"); + } } catch (Exception e) { @@ -1319,7 +1386,10 @@ namespace UnityMcpBridge.Editor.Windows } else if (!string.IsNullOrEmpty(errors)) { - UnityEngine.Debug.LogWarning($"Claude MCP errors: {errors}"); + if (debugLogsEnabled) + { + UnityEngine.Debug.LogWarning($"Claude MCP errors: {errors}"); + } } } catch (Exception e) @@ -1806,7 +1876,10 @@ namespace UnityMcpBridge.Editor.Windows ? mcpClient.windowsConfigPath : mcpClient.linuxConfigPath; - UnityEngine.Debug.Log($"Checking Claude config at: {configPath}"); + if (debugLogsEnabled) + { + UnityEngine.Debug.Log($"Checking Claude config at: {configPath}"); + } if (!File.Exists(configPath)) {