feat(editor): 2x2 layout (Server/Bridge | Clients/Validation), Auto-Setup with Connected ✓ state; add Debug Logs toggle and gate verbose logs

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
main
David Sarno 2025-08-08 11:23:45 -07:00
parent 2f387d3417
commit 06f271926b
3 changed files with 160 additions and 80 deletions

View File

@ -9,6 +9,58 @@ namespace UnityMcpBridge.Editor.Data
{ {
public List<McpClient> clients = new() public List<McpClient> 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() new()
{ {
name = "Claude Desktop", name = "Claude Desktop",
@ -27,36 +79,7 @@ namespace UnityMcpBridge.Editor.Data
mcpType = McpTypes.ClaudeDesktop, mcpType = McpTypes.ClaudeDesktop,
configStatus = "Not Configured", configStatus = "Not Configured",
}, },
new() // 5) VSCode GitHub Copilot
{
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",
},
new() new()
{ {
name = "VSCode GitHub Copilot", name = "VSCode GitHub Copilot",
@ -77,24 +100,6 @@ namespace UnityMcpBridge.Editor.Data
mcpType = McpTypes.VSCode, mcpType = McpTypes.VSCode,
configStatus = "Not Configured", 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 // Initialize status enums after construction

View File

@ -45,17 +45,10 @@ namespace UnityMcpBridge.Editor
try 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(); currentUnityPort = PortManager.GetPortWithFallback();
Start();
listener = new TcpListener(IPAddress.Loopback, currentUnityPort);
listener.Start();
isRunning = true;
isAutoConnectMode = true; isAutoConnectMode = true;
Debug.Log($"UnityMcpBridge auto-connected on port {currentUnityPort}");
Task.Run(ListenerLoop);
EditorApplication.update += ProcessCommands;
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -226,11 +219,12 @@ namespace UnityMcpBridge.Editor
try try
{ {
// Mark as stopping early to avoid accept logging during disposal
isRunning = false;
// Mark heartbeat one last time before stopping // Mark heartbeat one last time before stopping
WriteHeartbeat(false); WriteHeartbeat(false);
listener?.Stop(); listener?.Stop();
listener = null; listener = null;
isRunning = false;
EditorApplication.update -= ProcessCommands; EditorApplication.update -= ProcessCommands;
Debug.Log("UnityMcpBridge stopped."); Debug.Log("UnityMcpBridge stopped.");
} }
@ -261,6 +255,14 @@ namespace UnityMcpBridge.Editor
// Fire and forget each client connection // Fire and forget each client connection
_ = HandleClientAsync(client); _ = HandleClientAsync(client);
} }
catch (ObjectDisposedException)
{
// Listener was disposed during stop/reload; exit quietly
if (!isRunning)
{
break;
}
}
catch (Exception ex) catch (Exception ex)
{ {
if (isRunning) if (isRunning)

View File

@ -29,6 +29,7 @@ namespace UnityMcpBridge.Editor.Windows
private bool lastClientRegisteredOk; private bool lastClientRegisteredOk;
private bool lastBridgeVerifiedOk; private bool lastBridgeVerifiedOk;
private string pythonDirOverride = null; private string pythonDirOverride = null;
private bool debugLogsEnabled;
// Script validation settings // Script validation settings
private int validationLevelIndex = 1; // Default to Standard private int validationLevelIndex = 1; // Default to Standard
@ -56,6 +57,7 @@ namespace UnityMcpBridge.Editor.Windows
// Refresh bridge status // Refresh bridge status
isUnityBridgeRunning = UnityMcpBridge.IsRunning; isUnityBridgeRunning = UnityMcpBridge.IsRunning;
autoRegisterEnabled = EditorPrefs.GetBool("UnityMCP.AutoRegisterEnabled", true); autoRegisterEnabled = EditorPrefs.GetBool("UnityMCP.AutoRegisterEnabled", true);
debugLogsEnabled = EditorPrefs.GetBool("UnityMCP.DebugLogs", false);
foreach (McpClient mcpClient in mcpClients.clients) foreach (McpClient mcpClient in mcpClients.clients)
{ {
CheckMcpConfiguration(mcpClient); CheckMcpConfiguration(mcpClient);
@ -148,14 +150,50 @@ namespace UnityMcpBridge.Editor.Windows
// Header // Header
DrawHeader(); DrawHeader();
// Single-column streamlined layout // Compute equal column widths for uniform layout
DrawServerStatusSection(); float horizontalSpacing = 2f;
EditorGUILayout.Space(6); float outerPadding = 20f; // approximate padding
DrawBridgeSection(); // 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); EditorGUILayout.Space(10);
DrawUnifiedClientConfiguration();
EditorGUILayout.Space(10); // Second row: MCP Client Configuration (left) and Script Validation (right)
DrawValidationSection(); 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(); EditorGUILayout.EndScrollView();
} }
@ -205,21 +243,12 @@ namespace UnityMcpBridge.Editor.Windows
EditorGUILayout.Space(5); EditorGUILayout.Space(5);
// Connection mode and Setup controls // Connection mode
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
bool isAutoMode = UnityMcpBridge.IsAutoConnectMode(); bool isAutoMode = UnityMcpBridge.IsAutoConnectMode();
GUIStyle modeStyle = new GUIStyle(EditorStyles.miniLabel) { fontSize = 11 }; GUIStyle modeStyle = new GUIStyle(EditorStyles.miniLabel) { fontSize = 11 };
EditorGUILayout.LabelField($"Mode: {(isAutoMode ? "Auto" : "Standard")}", modeStyle); EditorGUILayout.LabelField($"Mode: {(isAutoMode ? "Auto" : "Standard")}", modeStyle);
GUILayout.FlexibleSpace(); GUILayout.FlexibleSpace();
// Bind to Clients button
if (GUILayout.Button("Bind to Clients", GUILayout.Width(140), GUILayout.Height(24)))
{
RunSetupNow();
}
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
// Current ports display // Current ports display
@ -231,6 +260,27 @@ namespace UnityMcpBridge.Editor.Windows
EditorGUILayout.LabelField($"Ports: Unity {currentUnityPort}, MCP {mcpPort}", portStyle); EditorGUILayout.LabelField($"Ports: Unity {currentUnityPort}, MCP {mcpPort}", portStyle);
EditorGUILayout.Space(5); 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 // Removed redundant inline badges to streamline UI
// Troubleshooting helpers // Troubleshooting helpers
@ -318,7 +368,18 @@ namespace UnityMcpBridge.Editor.Windows
EditorGUILayout.Space(8); EditorGUILayout.Space(8);
string description = GetValidationLevelDescription(validationLevelIndex); string description = GetValidationLevelDescription(validationLevelIndex);
EditorGUILayout.HelpBox(description, MessageType.Info); 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(); EditorGUILayout.EndVertical();
} }
@ -870,7 +931,10 @@ namespace UnityMcpBridge.Editor.Windows
{ {
if (Directory.Exists(devPath) && File.Exists(Path.Combine(devPath, "server.py"))) 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; return devPath;
} }
} }
@ -931,7 +995,10 @@ namespace UnityMcpBridge.Editor.Windows
} }
// If still not found, return the placeholder path // 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) catch (Exception e)
{ {
@ -1319,7 +1386,10 @@ namespace UnityMcpBridge.Editor.Windows
} }
else if (!string.IsNullOrEmpty(errors)) else if (!string.IsNullOrEmpty(errors))
{ {
UnityEngine.Debug.LogWarning($"Claude MCP errors: {errors}"); if (debugLogsEnabled)
{
UnityEngine.Debug.LogWarning($"Claude MCP errors: {errors}");
}
} }
} }
catch (Exception e) catch (Exception e)
@ -1806,7 +1876,10 @@ namespace UnityMcpBridge.Editor.Windows
? mcpClient.windowsConfigPath ? mcpClient.windowsConfigPath
: mcpClient.linuxConfigPath; : mcpClient.linuxConfigPath;
UnityEngine.Debug.Log($"Checking Claude config at: {configPath}"); if (debugLogsEnabled)
{
UnityEngine.Debug.Log($"Checking Claude config at: {configPath}");
}
if (!File.Exists(configPath)) if (!File.Exists(configPath))
{ {