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, VSCodemain
parent
2f387d3417
commit
06f271926b
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue