From 4c72309dc8669061c4bb25cbfde4836f61811c61 Mon Sep 17 00:00:00 2001 From: dsarno Date: Fri, 8 Aug 2025 15:09:18 -0700 Subject: [PATCH] Bridge logs: add bold blue UNITY-MCP prefix; gate PortManager logs behind Debug Logs toggle; improve Python and UV detection on Windows (flex versions, where.exe/Path scan); tidy installer messages --- .../Editor/Helpers/PackageInstaller.cs | 8 +- UnityMcpBridge/Editor/Helpers/PortManager.cs | 21 ++-- .../Editor/Helpers/ServerInstaller.cs | 118 +++++++++++++----- UnityMcpBridge/Editor/UnityMcpBridge.cs | 6 +- .../Editor/Windows/UnityMcpEditorWindow.cs | 110 +++++++++++----- 5 files changed, 190 insertions(+), 73 deletions(-) diff --git a/UnityMcpBridge/Editor/Helpers/PackageInstaller.cs b/UnityMcpBridge/Editor/Helpers/PackageInstaller.cs index 75cdb3b..ae420a2 100644 --- a/UnityMcpBridge/Editor/Helpers/PackageInstaller.cs +++ b/UnityMcpBridge/Editor/Helpers/PackageInstaller.cs @@ -25,18 +25,18 @@ namespace UnityMcpBridge.Editor.Helpers { try { - Debug.Log("Unity MCP: Installing Python server..."); + Debug.Log("UNITY-MCP: Installing Python server..."); ServerInstaller.EnsureServerInstalled(); // Mark as installed EditorPrefs.SetBool(InstallationFlagKey, true); - Debug.Log("Unity MCP: Python server installation completed successfully."); + Debug.Log("UNITY-MCP: Python server installation completed successfully."); } catch (System.Exception ex) { - Debug.LogError($"Unity MCP: Failed to install Python server: {ex.Message}"); - Debug.LogWarning("Unity MCP: You may need to manually install the Python server. Check the Unity MCP Editor Window for instructions."); + Debug.LogError($"UNITY-MCP: Failed to install Python server: {ex.Message}"); + Debug.LogWarning("UNITY-MCP: You may need to manually install the Python server. Check the Unity MCP Editor Window for instructions."); } } } diff --git a/UnityMcpBridge/Editor/Helpers/PortManager.cs b/UnityMcpBridge/Editor/Helpers/PortManager.cs index 9caeccc..376f916 100644 --- a/UnityMcpBridge/Editor/Helpers/PortManager.cs +++ b/UnityMcpBridge/Editor/Helpers/PortManager.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using UnityEditor; using System.Net; using System.Net.Sockets; using System.Security.Cryptography; @@ -15,6 +16,12 @@ namespace UnityMcpBridge.Editor.Helpers /// public static class PortManager { + private static bool IsDebugEnabled() + { + try { return EditorPrefs.GetBool("UnityMCP.DebugLogs", false); } + catch { return false; } + } + private const int DefaultPort = 6400; private const int MaxPortAttempts = 100; private const string RegistryFileName = "unity-mcp-port.json"; @@ -41,7 +48,7 @@ namespace UnityMcpBridge.Editor.Helpers string.Equals(storedConfig.project_path ?? string.Empty, Application.dataPath ?? string.Empty, StringComparison.OrdinalIgnoreCase) && IsPortAvailable(storedConfig.unity_port)) { - Debug.Log($"Using stored port {storedConfig.unity_port} for current project"); + if (IsDebugEnabled()) Debug.Log($"UNITY-MCP: Using stored port {storedConfig.unity_port} for current project"); return storedConfig.unity_port; } @@ -50,7 +57,7 @@ namespace UnityMcpBridge.Editor.Helpers { if (WaitForPortRelease(storedConfig.unity_port, 1500)) { - Debug.Log($"Stored port {storedConfig.unity_port} became available after short wait"); + if (IsDebugEnabled()) Debug.Log($"UNITY-MCP: Stored port {storedConfig.unity_port} became available after short wait"); return storedConfig.unity_port; } // Prefer sticking to the same port; let the caller handle bind retries/fallbacks @@ -71,7 +78,7 @@ namespace UnityMcpBridge.Editor.Helpers { int newPort = FindAvailablePort(); SavePort(newPort); - Debug.Log($"Discovered and saved new port: {newPort}"); + if (IsDebugEnabled()) Debug.Log($"UNITY-MCP: Discovered and saved new port: {newPort}"); return newPort; } @@ -84,18 +91,18 @@ namespace UnityMcpBridge.Editor.Helpers // Always try default port first if (IsPortAvailable(DefaultPort)) { - Debug.Log($"Using default port {DefaultPort}"); + if (IsDebugEnabled()) Debug.Log($"UNITY-MCP: Using default port {DefaultPort}"); return DefaultPort; } - Debug.Log($"Default port {DefaultPort} is in use, searching for alternative..."); + if (IsDebugEnabled()) Debug.Log($"UNITY-MCP: Default port {DefaultPort} is in use, searching for alternative..."); // Search for alternatives for (int port = DefaultPort + 1; port < DefaultPort + MaxPortAttempts; port++) { if (IsPortAvailable(port)) { - Debug.Log($"Found available port {port}"); + if (IsDebugEnabled()) Debug.Log($"UNITY-MCP: Found available port {port}"); return port; } } @@ -204,7 +211,7 @@ namespace UnityMcpBridge.Editor.Helpers string legacy = Path.Combine(GetRegistryDirectory(), RegistryFileName); File.WriteAllText(legacy, json); - Debug.Log($"Saved port {port} to storage"); + if (IsDebugEnabled()) Debug.Log($"UNITY-MCP: Saved port {port} to storage"); } catch (Exception ex) { diff --git a/UnityMcpBridge/Editor/Helpers/ServerInstaller.cs b/UnityMcpBridge/Editor/Helpers/ServerInstaller.cs index ed92786..03b753f 100644 --- a/UnityMcpBridge/Editor/Helpers/ServerInstaller.cs +++ b/UnityMcpBridge/Editor/Helpers/ServerInstaller.cs @@ -281,7 +281,7 @@ namespace UnityMcpBridge.Editor.Helpers return false; } - Debug.Log("Unity MCP: Python environment repaired successfully."); + Debug.Log("UNITY-MCP: Python environment repaired successfully."); return true; } catch (Exception ex) @@ -305,47 +305,100 @@ namespace UnityMcpBridge.Editor.Helpers catch { } string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) ?? string.Empty; - string[] candidates = + + // Platform-specific candidate lists + string[] candidates; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - "/opt/homebrew/bin/uv", - "/usr/local/bin/uv", - "/usr/bin/uv", - "/opt/local/bin/uv", - Path.Combine(home, ".local", "bin", "uv"), - "/opt/homebrew/opt/uv/bin/uv", - // Framework Python installs - "/Library/Frameworks/Python.framework/Versions/3.13/bin/uv", - "/Library/Frameworks/Python.framework/Versions/3.12/bin/uv", - // Fallback to PATH resolution by name - "uv" - }; + candidates = new[] + { + // Common per-user installs + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) ?? string.Empty, @"Programs\Python\Python313\Scripts\uv.exe"), + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) ?? string.Empty, @"Programs\Python\Python312\Scripts\uv.exe"), + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) ?? string.Empty, @"Programs\Python\Python311\Scripts\uv.exe"), + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) ?? string.Empty, @"Programs\Python\Python310\Scripts\uv.exe"), + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) ?? string.Empty, @"Python\Python313\Scripts\uv.exe"), + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) ?? string.Empty, @"Python\Python312\Scripts\uv.exe"), + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) ?? string.Empty, @"Python\Python311\Scripts\uv.exe"), + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) ?? string.Empty, @"Python\Python310\Scripts\uv.exe"), + // Program Files style installs (if a native installer was used) + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) ?? string.Empty, @"uv\uv.exe"), + // Try simple name resolution later via PATH + "uv.exe", + "uv" + }; + } + else + { + candidates = new[] + { + "/opt/homebrew/bin/uv", + "/usr/local/bin/uv", + "/usr/bin/uv", + "/opt/local/bin/uv", + Path.Combine(home, ".local", "bin", "uv"), + "/opt/homebrew/opt/uv/bin/uv", + // Framework Python installs + "/Library/Frameworks/Python.framework/Versions/3.13/bin/uv", + "/Library/Frameworks/Python.framework/Versions/3.12/bin/uv", + // Fallback to PATH resolution by name + "uv" + }; + } + foreach (string c in candidates) { try { - if (ValidateUvBinary(c)) return c; + if (File.Exists(c) && ValidateUvBinary(c)) return c; } catch { /* ignore */ } } - // Try which uv (explicit path) + // Use platform-appropriate which/where to resolve from PATH try { - var whichPsi = new System.Diagnostics.ProcessStartInfo + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - FileName = "/usr/bin/which", - Arguments = "uv", - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true - }; - using var wp = System.Diagnostics.Process.Start(whichPsi); - string output = wp.StandardOutput.ReadToEnd().Trim(); - wp.WaitForExit(3000); - if (wp.ExitCode == 0 && !string.IsNullOrEmpty(output) && File.Exists(output)) + var wherePsi = new System.Diagnostics.ProcessStartInfo + { + FileName = "where", + Arguments = "uv.exe", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + using var wp = System.Diagnostics.Process.Start(wherePsi); + string output = wp.StandardOutput.ReadToEnd().Trim(); + wp.WaitForExit(3000); + if (wp.ExitCode == 0 && !string.IsNullOrEmpty(output)) + { + foreach (var line in output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)) + { + string path = line.Trim(); + if (File.Exists(path) && ValidateUvBinary(path)) return path; + } + } + } + else { - if (ValidateUvBinary(output)) return output; + var whichPsi = new System.Diagnostics.ProcessStartInfo + { + FileName = "/usr/bin/which", + Arguments = "uv", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + using var wp = System.Diagnostics.Process.Start(whichPsi); + string output = wp.StandardOutput.ReadToEnd().Trim(); + wp.WaitForExit(3000); + if (wp.ExitCode == 0 && !string.IsNullOrEmpty(output) && File.Exists(output)) + { + if (ValidateUvBinary(output)) return output; + } } } catch { } @@ -359,8 +412,11 @@ namespace UnityMcpBridge.Editor.Helpers { try { - string candidate = Path.Combine(part, "uv"); - if (File.Exists(candidate) && ValidateUvBinary(candidate)) return candidate; + // Check both uv and uv.exe + string candidateUv = Path.Combine(part, "uv"); + string candidateUvExe = Path.Combine(part, "uv.exe"); + if (File.Exists(candidateUv) && ValidateUvBinary(candidateUv)) return candidateUv; + if (File.Exists(candidateUvExe) && ValidateUvBinary(candidateUvExe)) return candidateUvExe; } catch { } } diff --git a/UnityMcpBridge/Editor/UnityMcpBridge.cs b/UnityMcpBridge/Editor/UnityMcpBridge.cs index b7e4af2..89bae4e 100644 --- a/UnityMcpBridge/Editor/UnityMcpBridge.cs +++ b/UnityMcpBridge/Editor/UnityMcpBridge.cs @@ -128,7 +128,7 @@ namespace UnityMcpBridge.Editor // Don't restart if already running on a working port if (isRunning && listener != null) { - Debug.Log($"UnityMcpBridge already running on port {currentUnityPort}"); + Debug.Log($"UNITY-MCP: UnityMcpBridge already running on port {currentUnityPort}"); return; } @@ -194,7 +194,7 @@ namespace UnityMcpBridge.Editor isRunning = true; isAutoConnectMode = false; - Debug.Log($"UnityMcpBridge started on port {currentUnityPort}."); + Debug.Log($"UNITY-MCP: UnityMcpBridge started on port {currentUnityPort}."); Task.Run(ListenerLoop); EditorApplication.update += ProcessCommands; // Write initial heartbeat immediately @@ -226,7 +226,7 @@ namespace UnityMcpBridge.Editor listener?.Stop(); listener = null; EditorApplication.update -= ProcessCommands; - Debug.Log("UnityMcpBridge stopped."); + Debug.Log("UNITY-MCP: UnityMcpBridge stopped."); } catch (Exception ex) { diff --git a/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs b/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs index 408a63b..f85aa6f 100644 --- a/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs +++ b/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs @@ -1990,37 +1990,91 @@ namespace UnityMcpBridge.Editor.Windows { try { - // Common absolute paths - string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) ?? string.Empty; - string[] candidates = + // Windows-specific Python detection + if (Application.platform == RuntimePlatform.WindowsEditor) { - "/opt/homebrew/bin/python3", - "/usr/local/bin/python3", - "/usr/bin/python3", - "/opt/local/bin/python3", - Path.Combine(home, ".local", "bin", "python3"), - "/Library/Frameworks/Python.framework/Versions/3.13/bin/python3", - "/Library/Frameworks/Python.framework/Versions/3.12/bin/python3", - }; - foreach (string c in candidates) - { - if (File.Exists(c)) return true; - } + // Common Windows Python installation paths + string[] windowsCandidates = + { + @"C:\Python313\python.exe", + @"C:\Python312\python.exe", + @"C:\Python311\python.exe", + @"C:\Python310\python.exe", + @"C:\Python39\python.exe", + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Programs\Python\Python313\python.exe"), + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Programs\Python\Python312\python.exe"), + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Programs\Python\Python311\python.exe"), + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Programs\Python\Python310\python.exe"), + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Programs\Python\Python39\python.exe"), + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Python313\python.exe"), + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Python312\python.exe"), + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Python311\python.exe"), + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Python310\python.exe"), + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Python39\python.exe"), + }; + + foreach (string c in windowsCandidates) + { + if (File.Exists(c)) return true; + } - // Try 'which python3' - var psi = new ProcessStartInfo + // Try 'where python' command (Windows equivalent of 'which') + var psi = new ProcessStartInfo + { + FileName = "where", + Arguments = "python", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + using var p = Process.Start(psi); + string outp = p.StandardOutput.ReadToEnd().Trim(); + p.WaitForExit(2000); + if (p.ExitCode == 0 && !string.IsNullOrEmpty(outp)) + { + string[] lines = outp.Split('\n'); + foreach (string line in lines) + { + string trimmed = line.Trim(); + if (File.Exists(trimmed)) return true; + } + } + } + else { - FileName = "/usr/bin/which", - Arguments = "python3", - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true - }; - using var p = Process.Start(psi); - string outp = p.StandardOutput.ReadToEnd().Trim(); - p.WaitForExit(2000); - if (p.ExitCode == 0 && !string.IsNullOrEmpty(outp) && File.Exists(outp)) return true; + // macOS/Linux detection (existing code) + string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) ?? string.Empty; + string[] candidates = + { + "/opt/homebrew/bin/python3", + "/usr/local/bin/python3", + "/usr/bin/python3", + "/opt/local/bin/python3", + Path.Combine(home, ".local", "bin", "python3"), + "/Library/Frameworks/Python.framework/Versions/3.13/bin/python3", + "/Library/Frameworks/Python.framework/Versions/3.12/bin/python3", + }; + foreach (string c in candidates) + { + if (File.Exists(c)) return true; + } + + // Try 'which python3' + var psi = new ProcessStartInfo + { + FileName = "/usr/bin/which", + Arguments = "python3", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + using var p = Process.Start(psi); + string outp = p.StandardOutput.ReadToEnd().Trim(); + p.WaitForExit(2000); + if (p.ExitCode == 0 && !string.IsNullOrEmpty(outp) && File.Exists(outp)) return true; + } } catch { } return false;