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

main
dsarno 2025-08-08 15:09:18 -07:00
parent f24e124c15
commit 4c72309dc8
5 changed files with 190 additions and 73 deletions

View File

@ -25,18 +25,18 @@ namespace UnityMcpBridge.Editor.Helpers
{ {
try try
{ {
Debug.Log("Unity MCP: Installing Python server..."); Debug.Log("<b><color=#2EA3FF>UNITY-MCP</color></b>: Installing Python server...");
ServerInstaller.EnsureServerInstalled(); ServerInstaller.EnsureServerInstalled();
// Mark as installed // Mark as installed
EditorPrefs.SetBool(InstallationFlagKey, true); EditorPrefs.SetBool(InstallationFlagKey, true);
Debug.Log("Unity MCP: Python server installation completed successfully."); Debug.Log("<b><color=#2EA3FF>UNITY-MCP</color></b>: Python server installation completed successfully.");
} }
catch (System.Exception ex) catch (System.Exception ex)
{ {
Debug.LogError($"Unity MCP: Failed to install Python server: {ex.Message}"); Debug.LogError($"<b><color=#2EA3FF>UNITY-MCP</color></b>: 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.LogWarning("<b><color=#2EA3FF>UNITY-MCP</color></b>: You may need to manually install the Python server. Check the Unity MCP Editor Window for instructions.");
} }
} }
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using UnityEditor;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Cryptography; using System.Security.Cryptography;
@ -15,6 +16,12 @@ namespace UnityMcpBridge.Editor.Helpers
/// </summary> /// </summary>
public static class PortManager 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 DefaultPort = 6400;
private const int MaxPortAttempts = 100; private const int MaxPortAttempts = 100;
private const string RegistryFileName = "unity-mcp-port.json"; 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) && string.Equals(storedConfig.project_path ?? string.Empty, Application.dataPath ?? string.Empty, StringComparison.OrdinalIgnoreCase) &&
IsPortAvailable(storedConfig.unity_port)) IsPortAvailable(storedConfig.unity_port))
{ {
Debug.Log($"Using stored port {storedConfig.unity_port} for current project"); if (IsDebugEnabled()) Debug.Log($"<b><color=#2EA3FF>UNITY-MCP</color></b>: Using stored port {storedConfig.unity_port} for current project");
return storedConfig.unity_port; return storedConfig.unity_port;
} }
@ -50,7 +57,7 @@ namespace UnityMcpBridge.Editor.Helpers
{ {
if (WaitForPortRelease(storedConfig.unity_port, 1500)) if (WaitForPortRelease(storedConfig.unity_port, 1500))
{ {
Debug.Log($"Stored port {storedConfig.unity_port} became available after short wait"); if (IsDebugEnabled()) Debug.Log($"<b><color=#2EA3FF>UNITY-MCP</color></b>: Stored port {storedConfig.unity_port} became available after short wait");
return storedConfig.unity_port; return storedConfig.unity_port;
} }
// Prefer sticking to the same port; let the caller handle bind retries/fallbacks // Prefer sticking to the same port; let the caller handle bind retries/fallbacks
@ -71,7 +78,7 @@ namespace UnityMcpBridge.Editor.Helpers
{ {
int newPort = FindAvailablePort(); int newPort = FindAvailablePort();
SavePort(newPort); SavePort(newPort);
Debug.Log($"Discovered and saved new port: {newPort}"); if (IsDebugEnabled()) Debug.Log($"<b><color=#2EA3FF>UNITY-MCP</color></b>: Discovered and saved new port: {newPort}");
return newPort; return newPort;
} }
@ -84,18 +91,18 @@ namespace UnityMcpBridge.Editor.Helpers
// Always try default port first // Always try default port first
if (IsPortAvailable(DefaultPort)) if (IsPortAvailable(DefaultPort))
{ {
Debug.Log($"Using default port {DefaultPort}"); if (IsDebugEnabled()) Debug.Log($"<b><color=#2EA3FF>UNITY-MCP</color></b>: Using default port {DefaultPort}");
return DefaultPort; return DefaultPort;
} }
Debug.Log($"Default port {DefaultPort} is in use, searching for alternative..."); if (IsDebugEnabled()) Debug.Log($"<b><color=#2EA3FF>UNITY-MCP</color></b>: Default port {DefaultPort} is in use, searching for alternative...");
// Search for alternatives // Search for alternatives
for (int port = DefaultPort + 1; port < DefaultPort + MaxPortAttempts; port++) for (int port = DefaultPort + 1; port < DefaultPort + MaxPortAttempts; port++)
{ {
if (IsPortAvailable(port)) if (IsPortAvailable(port))
{ {
Debug.Log($"Found available port {port}"); if (IsDebugEnabled()) Debug.Log($"<b><color=#2EA3FF>UNITY-MCP</color></b>: Found available port {port}");
return port; return port;
} }
} }
@ -204,7 +211,7 @@ namespace UnityMcpBridge.Editor.Helpers
string legacy = Path.Combine(GetRegistryDirectory(), RegistryFileName); string legacy = Path.Combine(GetRegistryDirectory(), RegistryFileName);
File.WriteAllText(legacy, json); File.WriteAllText(legacy, json);
Debug.Log($"Saved port {port} to storage"); if (IsDebugEnabled()) Debug.Log($"<b><color=#2EA3FF>UNITY-MCP</color></b>: Saved port {port} to storage");
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -281,7 +281,7 @@ namespace UnityMcpBridge.Editor.Helpers
return false; return false;
} }
Debug.Log("Unity MCP: Python environment repaired successfully."); Debug.Log("<b><color=#2EA3FF>UNITY-MCP</color></b>: Python environment repaired successfully.");
return true; return true;
} }
catch (Exception ex) catch (Exception ex)
@ -305,47 +305,100 @@ namespace UnityMcpBridge.Editor.Helpers
catch { } catch { }
string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) ?? string.Empty; 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", candidates = new[]
"/usr/local/bin/uv", {
"/usr/bin/uv", // Common per-user installs
"/opt/local/bin/uv", Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) ?? string.Empty, @"Programs\Python\Python313\Scripts\uv.exe"),
Path.Combine(home, ".local", "bin", "uv"), Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) ?? string.Empty, @"Programs\Python\Python312\Scripts\uv.exe"),
"/opt/homebrew/opt/uv/bin/uv", Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) ?? string.Empty, @"Programs\Python\Python311\Scripts\uv.exe"),
// Framework Python installs Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) ?? string.Empty, @"Programs\Python\Python310\Scripts\uv.exe"),
"/Library/Frameworks/Python.framework/Versions/3.13/bin/uv", Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) ?? string.Empty, @"Python\Python313\Scripts\uv.exe"),
"/Library/Frameworks/Python.framework/Versions/3.12/bin/uv", Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) ?? string.Empty, @"Python\Python312\Scripts\uv.exe"),
// Fallback to PATH resolution by name Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) ?? string.Empty, @"Python\Python311\Scripts\uv.exe"),
"uv" 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) foreach (string c in candidates)
{ {
try try
{ {
if (ValidateUvBinary(c)) return c; if (File.Exists(c) && ValidateUvBinary(c)) return c;
} }
catch { /* ignore */ } catch { /* ignore */ }
} }
// Try which uv (explicit path) // Use platform-appropriate which/where to resolve from PATH
try try
{ {
var whichPsi = new System.Diagnostics.ProcessStartInfo if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{ {
FileName = "/usr/bin/which", var wherePsi = new System.Diagnostics.ProcessStartInfo
Arguments = "uv", {
UseShellExecute = false, FileName = "where",
RedirectStandardOutput = true, Arguments = "uv.exe",
RedirectStandardError = true, UseShellExecute = false,
CreateNoWindow = true RedirectStandardOutput = true,
}; RedirectStandardError = true,
using var wp = System.Diagnostics.Process.Start(whichPsi); CreateNoWindow = true
string output = wp.StandardOutput.ReadToEnd().Trim(); };
wp.WaitForExit(3000); using var wp = System.Diagnostics.Process.Start(wherePsi);
if (wp.ExitCode == 0 && !string.IsNullOrEmpty(output) && File.Exists(output)) 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 { } catch { }
@ -359,8 +412,11 @@ namespace UnityMcpBridge.Editor.Helpers
{ {
try try
{ {
string candidate = Path.Combine(part, "uv"); // Check both uv and uv.exe
if (File.Exists(candidate) && ValidateUvBinary(candidate)) return candidate; 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 { } catch { }
} }

View File

@ -128,7 +128,7 @@ namespace UnityMcpBridge.Editor
// Don't restart if already running on a working port // Don't restart if already running on a working port
if (isRunning && listener != null) if (isRunning && listener != null)
{ {
Debug.Log($"UnityMcpBridge already running on port {currentUnityPort}"); Debug.Log($"<b><color=#2EA3FF>UNITY-MCP</color></b>: UnityMcpBridge already running on port {currentUnityPort}");
return; return;
} }
@ -194,7 +194,7 @@ namespace UnityMcpBridge.Editor
isRunning = true; isRunning = true;
isAutoConnectMode = false; isAutoConnectMode = false;
Debug.Log($"UnityMcpBridge started on port {currentUnityPort}."); Debug.Log($"<b><color=#2EA3FF>UNITY-MCP</color></b>: UnityMcpBridge started on port {currentUnityPort}.");
Task.Run(ListenerLoop); Task.Run(ListenerLoop);
EditorApplication.update += ProcessCommands; EditorApplication.update += ProcessCommands;
// Write initial heartbeat immediately // Write initial heartbeat immediately
@ -226,7 +226,7 @@ namespace UnityMcpBridge.Editor
listener?.Stop(); listener?.Stop();
listener = null; listener = null;
EditorApplication.update -= ProcessCommands; EditorApplication.update -= ProcessCommands;
Debug.Log("UnityMcpBridge stopped."); Debug.Log("<b><color=#2EA3FF>UNITY-MCP</color></b>: UnityMcpBridge stopped.");
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -1990,37 +1990,91 @@ namespace UnityMcpBridge.Editor.Windows
{ {
try try
{ {
// Common absolute paths // Windows-specific Python detection
string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) ?? string.Empty; if (Application.platform == RuntimePlatform.WindowsEditor)
string[] candidates =
{ {
"/opt/homebrew/bin/python3", // Common Windows Python installation paths
"/usr/local/bin/python3", string[] windowsCandidates =
"/usr/bin/python3", {
"/opt/local/bin/python3", @"C:\Python313\python.exe",
Path.Combine(home, ".local", "bin", "python3"), @"C:\Python312\python.exe",
"/Library/Frameworks/Python.framework/Versions/3.13/bin/python3", @"C:\Python311\python.exe",
"/Library/Frameworks/Python.framework/Versions/3.12/bin/python3", @"C:\Python310\python.exe",
}; @"C:\Python39\python.exe",
foreach (string c in candidates) Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Programs\Python\Python313\python.exe"),
{ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Programs\Python\Python312\python.exe"),
if (File.Exists(c)) return true; 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' // Try 'where python' command (Windows equivalent of 'which')
var psi = new ProcessStartInfo 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", // macOS/Linux detection (existing code)
Arguments = "python3", string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) ?? string.Empty;
UseShellExecute = false, string[] candidates =
RedirectStandardOutput = true, {
RedirectStandardError = true, "/opt/homebrew/bin/python3",
CreateNoWindow = true "/usr/local/bin/python3",
}; "/usr/bin/python3",
using var p = Process.Start(psi); "/opt/local/bin/python3",
string outp = p.StandardOutput.ReadToEnd().Trim(); Path.Combine(home, ".local", "bin", "python3"),
p.WaitForExit(2000); "/Library/Frameworks/Python.framework/Versions/3.13/bin/python3",
if (p.ExitCode == 0 && !string.IsNullOrEmpty(outp) && File.Exists(outp)) return true; "/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 { } catch { }
return false; return false;