From cd8256f97afca29455c849525cde79c447c3660f Mon Sep 17 00:00:00 2001 From: David Sarno Date: Wed, 3 Dec 2025 23:26:43 -0800 Subject: [PATCH 01/10] Fix macOS port conflict: Disable SO_REUSEADDR and improve UI port display - StdioBridgeHost.cs: Disable ReuseAddress on macOS to force AddressAlreadyInUse exception on conflict. - PortManager.cs: Add connection check safety net for macOS. - McpConnectionSection.cs: Ensure UI displays the actual live port instead of just the requested one. --- MCPForUnity/Editor/Helpers/PortManager.cs | 26 ++++++++++++++++++- .../Transport/Transports/StdioBridgeHost.cs | 6 ++++- .../Connection/McpConnectionSection.cs | 15 ++++++----- 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/MCPForUnity/Editor/Helpers/PortManager.cs b/MCPForUnity/Editor/Helpers/PortManager.cs index e8e80c5c..de46fd8f 100644 --- a/MCPForUnity/Editor/Helpers/PortManager.cs +++ b/MCPForUnity/Editor/Helpers/PortManager.cs @@ -119,17 +119,41 @@ private static int FindAvailablePort() /// True if port is available public static bool IsPortAvailable(int port) { + // Start with quick loopback check try { var testListener = new TcpListener(IPAddress.Loopback, port); testListener.Start(); testListener.Stop(); - return true; } catch (SocketException) { return false; } + +#if UNITY_EDITOR_OSX + // On macOS, the OS might report the port as available (SO_REUSEADDR) even if another process + // is using it, unless we also check active connections or try a stricter bind. + // Double check by trying to Connect to it. If we CAN connect, it's NOT available. + try + { + using var client = new TcpClient(); + var connectTask = client.ConnectAsync(IPAddress.Loopback, port); + // If we connect successfully, someone is listening -> Not available + if (connectTask.Wait(50) && client.Connected) + { + if (IsDebugEnabled()) McpLog.Info($"[PortManager] Port {port} bind succeeded but connection also succeeded -> Not available (Conflict)."); + return false; + } + } + catch + { + // Connection failed -> likely available (or firewall blocked, but we assume available) + if (IsDebugEnabled()) McpLog.Info($"[PortManager] Port {port} connection failed -> likely available."); + } +#endif + + return true; } /// diff --git a/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs b/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs index ffecd2ef..fcf3171e 100644 --- a/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs +++ b/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs @@ -307,11 +307,13 @@ public static void Start() try { listener = new TcpListener(IPAddress.Loopback, currentUnityPort); +#if !UNITY_EDITOR_OSX listener.Server.SetSocketOption( SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true ); +#endif #if UNITY_EDITOR_WIN try { @@ -355,7 +357,7 @@ public static void Start() } catch { } - currentUnityPort = PortManager.GetPortWithFallback(); + currentUnityPort = PortManager.DiscoverNewPort(); if (IsDebugEnabled()) { @@ -370,11 +372,13 @@ public static void Start() } listener = new TcpListener(IPAddress.Loopback, currentUnityPort); +#if !UNITY_EDITOR_OSX listener.Server.SetSocketOption( SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true ); +#endif #if UNITY_EDITOR_WIN try { diff --git a/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs b/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs index 6bc47992..8f25dad8 100644 --- a/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs +++ b/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs @@ -173,6 +173,9 @@ public void UpdateConnectionStatus() statusIndicator.RemoveFromClassList("disconnected"); statusIndicator.AddToClassList("connected"); connectionToggleButton.text = "End Session"; + + // Force the UI to reflect the actual port being used + unityPortField.value = bridgeService.CurrentPort.ToString(); } else { @@ -185,12 +188,12 @@ public void UpdateConnectionStatus() healthIndicator.RemoveFromClassList("healthy"); healthIndicator.RemoveFromClassList("warning"); healthIndicator.AddToClassList("unknown"); - } - - int savedPort = EditorPrefs.GetInt(EditorPrefKeys.UnitySocketPort, 0); - if (savedPort == 0) - { - unityPortField.value = bridgeService.CurrentPort.ToString(); + + int savedPort = EditorPrefs.GetInt(EditorPrefKeys.UnitySocketPort, 0); + if (savedPort == 0) + { + unityPortField.value = bridgeService.CurrentPort.ToString(); + } } } From 5c83dec46c5c36be4a408039999912d37312a87a Mon Sep 17 00:00:00 2001 From: David Sarno Date: Wed, 3 Dec 2025 23:26:43 -0800 Subject: [PATCH 02/10] Fix macOS port conflict: Disable SO_REUSEADDR and improve UI port display - StdioBridgeHost.cs: Disable ReuseAddress on macOS to force AddressAlreadyInUse exception on conflict. - PortManager.cs: Add connection check safety net for macOS. - McpConnectionSection.cs: Ensure UI displays the actual live port instead of just the requested one. --- MCPForUnity/Editor/Helpers/PortManager.cs | 26 ++++++++++++++++++- .../Transport/Transports/StdioBridgeHost.cs | 6 ++++- .../Connection/McpConnectionSection.cs | 15 ++++++----- 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/MCPForUnity/Editor/Helpers/PortManager.cs b/MCPForUnity/Editor/Helpers/PortManager.cs index e8e80c5c..de46fd8f 100644 --- a/MCPForUnity/Editor/Helpers/PortManager.cs +++ b/MCPForUnity/Editor/Helpers/PortManager.cs @@ -119,17 +119,41 @@ private static int FindAvailablePort() /// True if port is available public static bool IsPortAvailable(int port) { + // Start with quick loopback check try { var testListener = new TcpListener(IPAddress.Loopback, port); testListener.Start(); testListener.Stop(); - return true; } catch (SocketException) { return false; } + +#if UNITY_EDITOR_OSX + // On macOS, the OS might report the port as available (SO_REUSEADDR) even if another process + // is using it, unless we also check active connections or try a stricter bind. + // Double check by trying to Connect to it. If we CAN connect, it's NOT available. + try + { + using var client = new TcpClient(); + var connectTask = client.ConnectAsync(IPAddress.Loopback, port); + // If we connect successfully, someone is listening -> Not available + if (connectTask.Wait(50) && client.Connected) + { + if (IsDebugEnabled()) McpLog.Info($"[PortManager] Port {port} bind succeeded but connection also succeeded -> Not available (Conflict)."); + return false; + } + } + catch + { + // Connection failed -> likely available (or firewall blocked, but we assume available) + if (IsDebugEnabled()) McpLog.Info($"[PortManager] Port {port} connection failed -> likely available."); + } +#endif + + return true; } /// diff --git a/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs b/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs index ffecd2ef..fcf3171e 100644 --- a/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs +++ b/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs @@ -307,11 +307,13 @@ public static void Start() try { listener = new TcpListener(IPAddress.Loopback, currentUnityPort); +#if !UNITY_EDITOR_OSX listener.Server.SetSocketOption( SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true ); +#endif #if UNITY_EDITOR_WIN try { @@ -355,7 +357,7 @@ public static void Start() } catch { } - currentUnityPort = PortManager.GetPortWithFallback(); + currentUnityPort = PortManager.DiscoverNewPort(); if (IsDebugEnabled()) { @@ -370,11 +372,13 @@ public static void Start() } listener = new TcpListener(IPAddress.Loopback, currentUnityPort); +#if !UNITY_EDITOR_OSX listener.Server.SetSocketOption( SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true ); +#endif #if UNITY_EDITOR_WIN try { diff --git a/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs b/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs index 6bc47992..8f25dad8 100644 --- a/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs +++ b/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs @@ -173,6 +173,9 @@ public void UpdateConnectionStatus() statusIndicator.RemoveFromClassList("disconnected"); statusIndicator.AddToClassList("connected"); connectionToggleButton.text = "End Session"; + + // Force the UI to reflect the actual port being used + unityPortField.value = bridgeService.CurrentPort.ToString(); } else { @@ -185,12 +188,12 @@ public void UpdateConnectionStatus() healthIndicator.RemoveFromClassList("healthy"); healthIndicator.RemoveFromClassList("warning"); healthIndicator.AddToClassList("unknown"); - } - - int savedPort = EditorPrefs.GetInt(EditorPrefKeys.UnitySocketPort, 0); - if (savedPort == 0) - { - unityPortField.value = bridgeService.CurrentPort.ToString(); + + int savedPort = EditorPrefs.GetInt(EditorPrefKeys.UnitySocketPort, 0); + if (savedPort == 0) + { + unityPortField.value = bridgeService.CurrentPort.ToString(); + } } } From b693edcaa9cc51bfec59d00595db0e96c49129eb Mon Sep 17 00:00:00 2001 From: David Sarno Date: Thu, 4 Dec 2025 12:05:12 -0800 Subject: [PATCH 03/10] Address CodeRabbit feedback: UX improvements and code quality fixes - McpConnectionSection.cs: Disable port field when session is running to prevent editing conflicts - StdioBridgeHost.cs: Refactor listener creation into helper method and update EditorPrefs on port fallback - unity_instance_middleware.py: Narrow exception handling and preserve SystemExit/KeyboardInterrupt - debug_request_context.py: Document that debug fields expose internal implementation details --- .../Transport/Transports/StdioBridgeHost.cs | 80 +++++++++---------- .../Connection/McpConnectionSection.cs | 3 + .../services/tools/debug_request_context.py | 1 + .../transport/unity_instance_middleware.py | 12 ++- 4 files changed, 51 insertions(+), 45 deletions(-) diff --git a/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs b/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs index fcf3171e..31de7311 100644 --- a/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs +++ b/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs @@ -306,28 +306,7 @@ public static void Start() { try { - listener = new TcpListener(IPAddress.Loopback, currentUnityPort); -#if !UNITY_EDITOR_OSX - listener.Server.SetSocketOption( - SocketOptionLevel.Socket, - SocketOptionName.ReuseAddress, - true - ); -#endif -#if UNITY_EDITOR_WIN - try - { - listener.ExclusiveAddressUse = false; - } - catch { } -#endif - try - { - listener.Server.LingerState = new LingerOption(true, 0); - } - catch (Exception) - { - } + listener = CreateConfiguredListener(currentUnityPort); listener.Start(); break; } @@ -359,6 +338,13 @@ public static void Start() currentUnityPort = PortManager.DiscoverNewPort(); + // Persist the new port so next time we start on this port + try + { + EditorPrefs.SetInt(EditorPrefKeys.UnitySocketPort, currentUnityPort); + } + catch { } + if (IsDebugEnabled()) { if (currentUnityPort == oldPort) @@ -371,28 +357,7 @@ public static void Start() } } - listener = new TcpListener(IPAddress.Loopback, currentUnityPort); -#if !UNITY_EDITOR_OSX - listener.Server.SetSocketOption( - SocketOptionLevel.Socket, - SocketOptionName.ReuseAddress, - true - ); -#endif -#if UNITY_EDITOR_WIN - try - { - listener.ExclusiveAddressUse = false; - } - catch { } -#endif - try - { - listener.Server.LingerState = new LingerOption(true, 0); - } - catch (Exception) - { - } + listener = CreateConfiguredListener(currentUnityPort); listener.Start(); break; } @@ -420,6 +385,33 @@ public static void Start() } } + private static TcpListener CreateConfiguredListener(int port) + { + var newListener = new TcpListener(IPAddress.Loopback, port); +#if !UNITY_EDITOR_OSX + newListener.Server.SetSocketOption( + SocketOptionLevel.Socket, + SocketOptionName.ReuseAddress, + true + ); +#endif +#if UNITY_EDITOR_WIN + try + { + newListener.ExclusiveAddressUse = false; + } + catch { } +#endif + try + { + newListener.Server.LingerState = new LingerOption(true, 0); + } + catch (Exception) + { + } + return newListener; + } + public static void Stop() { Task toWait = null; diff --git a/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs b/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs index 8f25dad8..ded7a5d0 100644 --- a/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs +++ b/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs @@ -176,6 +176,7 @@ public void UpdateConnectionStatus() // Force the UI to reflect the actual port being used unityPortField.value = bridgeService.CurrentPort.ToString(); + unityPortField.SetEnabled(false); } else { @@ -183,6 +184,8 @@ public void UpdateConnectionStatus() statusIndicator.RemoveFromClassList("connected"); statusIndicator.AddToClassList("disconnected"); connectionToggleButton.text = "Start Session"; + + unityPortField.SetEnabled(true); healthStatusLabel.text = HealthStatusUnknown; healthIndicator.RemoveFromClassList("healthy"); diff --git a/Server/src/services/tools/debug_request_context.py b/Server/src/services/tools/debug_request_context.py index 16f4e493..f6f2ae96 100644 --- a/Server/src/services/tools/debug_request_context.py +++ b/Server/src/services/tools/debug_request_context.py @@ -40,6 +40,7 @@ def debug_request_context(ctx: Context) -> dict[str, Any]: active_instance = middleware.get_active_instance(ctx) # Debugging middleware internals + # NOTE: These fields expose internal implementation details and may change between versions. with middleware._lock: all_keys = list(middleware._active_by_key.keys()) diff --git a/Server/src/transport/unity_instance_middleware.py b/Server/src/transport/unity_instance_middleware.py index fbc62853..8f3a0209 100644 --- a/Server/src/transport/unity_instance_middleware.py +++ b/Server/src/transport/unity_instance_middleware.py @@ -103,7 +103,7 @@ async def on_call_tool(self, context: MiddlewareContext, call_next): # We only need session_id for HTTP transport routing. # For stdio, we just need the instance ID. session_id = await PluginHub._resolve_session_id(active_instance) - except Exception as exc: + except (ConnectionError, ValueError, KeyError, TimeoutError) as exc: # If resolution fails, it means the Unity instance is not reachable via HTTP/WS. # If we are in stdio mode, this might still be fine if the user is just setting state? # But usually if PluginHub is configured, we expect it to work. @@ -115,6 +115,16 @@ async def on_call_tool(self, context: MiddlewareContext, call_next): exc, exc_info=True, ) + except Exception as exc: + # Re-raise unexpected system exceptions to avoid swallowing critical failures + if isinstance(exc, (SystemExit, KeyboardInterrupt)): + raise + logger.error( + "Unexpected error during PluginHub session resolution for %s: %s", + active_instance, + exc, + exc_info=True + ) ctx.set_state("unity_instance", active_instance) if session_id is not None: From fed3042ee52c8d04e3216a54da51a99065a976e4 Mon Sep 17 00:00:00 2001 From: dsarno Date: Thu, 4 Dec 2025 12:18:02 -0800 Subject: [PATCH 04/10] Fix: Harden Python detection on Windows to handle App Execution Aliases --- .../WindowsPlatformDetector.cs | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/MCPForUnity/Editor/Dependencies/PlatformDetectors/WindowsPlatformDetector.cs b/MCPForUnity/Editor/Dependencies/PlatformDetectors/WindowsPlatformDetector.cs index e4d7b925..f21d58ff 100644 --- a/MCPForUnity/Editor/Dependencies/PlatformDetectors/WindowsPlatformDetector.cs +++ b/MCPForUnity/Editor/Dependencies/PlatformDetectors/WindowsPlatformDetector.cs @@ -50,6 +50,16 @@ public override DependencyStatus DetectPython() } } + // Fallback: try to find python via uv + if (TryFindPythonViaUv(out version, out fullPath)) + { + status.IsAvailable = true; + status.Version = version; + status.Path = fullPath; + status.Details = $"Found Python {version} via uv"; + return status; + } + status.ErrorMessage = "Python not found in PATH"; status.Details = "Install Python 3.10+ and ensure it's added to PATH."; } @@ -86,6 +96,64 @@ public override string GetInstallationRecommendations() 3. MCP Server: Will be installed automatically by MCP for Unity Bridge"; } + private bool TryFindPythonViaUv(out string version, out string fullPath) + { + version = null; + fullPath = null; + + try + { + var psi = new ProcessStartInfo + { + FileName = "uv", // Assume uv is in path or user can't use this fallback + Arguments = "python list", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + + using var process = Process.Start(psi); + if (process == null) return false; + + string output = process.StandardOutput.ReadToEnd(); + process.WaitForExit(5000); + + if (process.ExitCode == 0 && !string.IsNullOrEmpty(output)) + { + var lines = output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + foreach (var line in lines) + { + // Look for installed python paths + // Format is typically: + // Skip lines with "" + if (line.Contains("")) continue; + + // The path is typically the last part of the line + var parts = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length >= 2) + { + string potentialPath = parts[parts.Length - 1]; + if (File.Exists(potentialPath) && + (potentialPath.EndsWith("python.exe") || potentialPath.EndsWith("python3.exe"))) + { + if (TryValidatePython(potentialPath, out version, out fullPath)) + { + return true; + } + } + } + } + } + } + catch + { + // Ignore errors if uv is not installed or fails + } + + return false; + } + private bool TryValidatePython(string pythonPath, out string version, out string fullPath) { version = null; From 11932f2720e43c13222167ca5ec277a811cf3529 Mon Sep 17 00:00:00 2001 From: dsarno Date: Thu, 4 Dec 2025 12:18:19 -0800 Subject: [PATCH 05/10] Refactor: Pre-resolve conflicts for McpConnectionSection and middleware --- .../Components/Connection/McpConnectionSection.cs | 3 +++ Server/src/transport/unity_instance_middleware.py | 12 +++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs b/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs index 8f25dad8..7c1fd9c3 100644 --- a/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs +++ b/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs @@ -176,6 +176,7 @@ public void UpdateConnectionStatus() // Force the UI to reflect the actual port being used unityPortField.value = bridgeService.CurrentPort.ToString(); + unityPortField.SetEnabled(false); } else { @@ -184,6 +185,8 @@ public void UpdateConnectionStatus() statusIndicator.AddToClassList("disconnected"); connectionToggleButton.text = "Start Session"; + unityPortField.SetEnabled(true); + healthStatusLabel.text = HealthStatusUnknown; healthIndicator.RemoveFromClassList("healthy"); healthIndicator.RemoveFromClassList("warning"); diff --git a/Server/src/transport/unity_instance_middleware.py b/Server/src/transport/unity_instance_middleware.py index fbc62853..5d2cb55f 100644 --- a/Server/src/transport/unity_instance_middleware.py +++ b/Server/src/transport/unity_instance_middleware.py @@ -103,7 +103,7 @@ async def on_call_tool(self, context: MiddlewareContext, call_next): # We only need session_id for HTTP transport routing. # For stdio, we just need the instance ID. session_id = await PluginHub._resolve_session_id(active_instance) - except Exception as exc: + except (ConnectionError, ValueError, KeyError, TimeoutError) as exc: # If resolution fails, it means the Unity instance is not reachable via HTTP/WS. # If we are in stdio mode, this might still be fine if the user is just setting state? # But usually if PluginHub is configured, we expect it to work. @@ -115,6 +115,16 @@ async def on_call_tool(self, context: MiddlewareContext, call_next): exc, exc_info=True, ) + except Exception as exc: + # Re-raise unexpected system exceptions to avoid swallowing critical failures + if isinstance(exc, (SystemExit, KeyboardInterrupt)): + raise + logger.error( + "PluginHub session resolution failed for %s: %s; leaving active_instance unchanged", + active_instance, + exc, + exc_info=True, + ) ctx.set_state("unity_instance", active_instance) if session_id is not None: From dde79a9f727bc61fe252b064abd5c17d9d74429f Mon Sep 17 00:00:00 2001 From: dsarno Date: Thu, 4 Dec 2025 12:31:52 -0800 Subject: [PATCH 06/10] Fix: Remove leftover merge conflict markers in StdioBridgeHost.cs --- .../Transport/Transports/StdioBridgeHost.cs | 50 ------------------- 1 file changed, 50 deletions(-) diff --git a/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs b/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs index ddaef4d2..31de7311 100644 --- a/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs +++ b/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs @@ -306,32 +306,7 @@ public static void Start() { try { -<<<<<<< HEAD - listener = new TcpListener(IPAddress.Loopback, currentUnityPort); -#if !UNITY_EDITOR_OSX - listener.Server.SetSocketOption( - SocketOptionLevel.Socket, - SocketOptionName.ReuseAddress, - true - ); -#endif -#if UNITY_EDITOR_WIN - try - { - listener.ExclusiveAddressUse = false; - } - catch { } -#endif - try - { - listener.Server.LingerState = new LingerOption(true, 0); - } - catch (Exception) - { - } -======= listener = CreateConfiguredListener(currentUnityPort); ->>>>>>> b693edcaa9cc51bfec59d00595db0e96c49129eb listener.Start(); break; } @@ -382,32 +357,7 @@ public static void Start() } } -<<<<<<< HEAD - listener = new TcpListener(IPAddress.Loopback, currentUnityPort); -#if !UNITY_EDITOR_OSX - listener.Server.SetSocketOption( - SocketOptionLevel.Socket, - SocketOptionName.ReuseAddress, - true - ); -#endif -#if UNITY_EDITOR_WIN - try - { - listener.ExclusiveAddressUse = false; - } - catch { } -#endif - try - { - listener.Server.LingerState = new LingerOption(true, 0); - } - catch (Exception) - { - } -======= listener = CreateConfiguredListener(currentUnityPort); ->>>>>>> b693edcaa9cc51bfec59d00595db0e96c49129eb listener.Start(); break; } From 9df1260b5fc80f410389fcd68c45bf3fc3b5f358 Mon Sep 17 00:00:00 2001 From: dsarno Date: Thu, 4 Dec 2025 12:52:40 -0800 Subject: [PATCH 07/10] fix: clarify create_script tool description regarding base64 encoding --- Server/src/services/tools/manage_script.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/src/services/tools/manage_script.py b/Server/src/services/tools/manage_script.py index 9eeeed26..41148682 100644 --- a/Server/src/services/tools/manage_script.py +++ b/Server/src/services/tools/manage_script.py @@ -371,7 +371,7 @@ async def _flip_async(): async def create_script( ctx: Context, path: Annotated[str, "Path under Assets/ to create the script at, e.g., 'Assets/Scripts/My.cs'"], - contents: Annotated[str, "Contents of the script to create. Note, this is Base64 encoded over transport."], + contents: Annotated[str, "Contents of the script to create (plain text C# code). The server handles Base64 encoding."], script_type: Annotated[str, "Script type (e.g., 'C#')"] | None = None, namespace: Annotated[str, "Namespace for the script"] | None = None, ) -> dict[str, Any]: From 222ea10ad029db5d035dc5c739f6a079aedd368f Mon Sep 17 00:00:00 2001 From: dsarno Date: Thu, 4 Dec 2025 13:04:48 -0800 Subject: [PATCH 08/10] fix: improve python detection on macOS by checking specific Homebrew paths --- .../MacOSPlatformDetector.cs | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/MCPForUnity/Editor/Dependencies/PlatformDetectors/MacOSPlatformDetector.cs b/MCPForUnity/Editor/Dependencies/PlatformDetectors/MacOSPlatformDetector.cs index a3ced1b7..6735f941 100644 --- a/MCPForUnity/Editor/Dependencies/PlatformDetectors/MacOSPlatformDetector.cs +++ b/MCPForUnity/Editor/Dependencies/PlatformDetectors/MacOSPlatformDetector.cs @@ -25,7 +25,35 @@ public override DependencyStatus DetectPython() try { - // Try running python directly first + // 1. Check explicit common Homebrew/system paths first to avoid PATH ambiguity + // This prioritizes Homebrew versions (often newer) over system versions + var candidatePaths = new[] + { + "/opt/homebrew/bin/python3", + "/usr/local/bin/python3", + "/opt/homebrew/bin/python3.13", + "/opt/homebrew/bin/python3.12", + "/opt/homebrew/bin/python3.11", + "/opt/homebrew/bin/python3.10", + "/usr/local/bin/python3.13", + "/usr/local/bin/python3.12", + "/usr/local/bin/python3.11", + "/usr/local/bin/python3.10" + }; + + foreach (var path in candidatePaths) + { + if (File.Exists(path) && TryValidatePython(path, out string v, out string p)) + { + status.IsAvailable = true; + status.Version = v; + status.Path = p; + status.Details = $"Found Python {v} at {p}"; + return status; + } + } + + // 2. Try running python directly from PATH (fallback) if (TryValidatePython("python3", out string version, out string fullPath) || TryValidatePython("python", out version, out fullPath)) { @@ -36,7 +64,7 @@ public override DependencyStatus DetectPython() return status; } - // Fallback: try 'which' command + // 3. Fallback: try 'which' command if (TryFindInPath("python3", out string pathResult) || TryFindInPath("python", out pathResult)) { @@ -50,8 +78,8 @@ public override DependencyStatus DetectPython() } } - status.ErrorMessage = "Python not found in PATH"; - status.Details = "Install Python 3.10+ and ensure it's added to PATH."; + status.ErrorMessage = "Python not found in PATH or standard locations"; + status.Details = "Install Python 3.10+ via Homebrew ('brew install python3') and ensure it's in your PATH."; } catch (Exception ex) { From 7bcc217654f54bc37376b4b75f8447b3c484ea74 Mon Sep 17 00:00:00 2001 From: dsarno Date: Thu, 4 Dec 2025 13:13:16 -0800 Subject: [PATCH 09/10] Fix duplicate SetEnabled call and improve macOS Python detection --- .../MacOSPlatformDetector.cs | 54 +++++-------------- .../Connection/McpConnectionSection.cs | 2 - 2 files changed, 13 insertions(+), 43 deletions(-) diff --git a/MCPForUnity/Editor/Dependencies/PlatformDetectors/MacOSPlatformDetector.cs b/MCPForUnity/Editor/Dependencies/PlatformDetectors/MacOSPlatformDetector.cs index 6735f941..0f9c6e11 100644 --- a/MCPForUnity/Editor/Dependencies/PlatformDetectors/MacOSPlatformDetector.cs +++ b/MCPForUnity/Editor/Dependencies/PlatformDetectors/MacOSPlatformDetector.cs @@ -25,59 +25,31 @@ public override DependencyStatus DetectPython() try { - // 1. Check explicit common Homebrew/system paths first to avoid PATH ambiguity - // This prioritizes Homebrew versions (often newer) over system versions - var candidatePaths = new[] - { - "/opt/homebrew/bin/python3", - "/usr/local/bin/python3", - "/opt/homebrew/bin/python3.13", - "/opt/homebrew/bin/python3.12", - "/opt/homebrew/bin/python3.11", - "/opt/homebrew/bin/python3.10", - "/usr/local/bin/python3.13", - "/usr/local/bin/python3.12", - "/usr/local/bin/python3.11", - "/usr/local/bin/python3.10" - }; - - foreach (var path in candidatePaths) + // 1. Try 'which' command with augmented PATH (prioritizing Homebrew) + if (TryFindInPath("python3", out string pathResult) || + TryFindInPath("python", out pathResult)) { - if (File.Exists(path) && TryValidatePython(path, out string v, out string p)) + if (TryValidatePython(pathResult, out string version, out string fullPath)) { status.IsAvailable = true; - status.Version = v; - status.Path = p; - status.Details = $"Found Python {v} at {p}"; + status.Version = version; + status.Path = fullPath; + status.Details = $"Found Python {version} at {fullPath}"; return status; } } - // 2. Try running python directly from PATH (fallback) - if (TryValidatePython("python3", out string version, out string fullPath) || - TryValidatePython("python", out version, out fullPath)) + // 2. Fallback: Try running python directly from PATH + if (TryValidatePython("python3", out string v, out string p) || + TryValidatePython("python", out v, out p)) { status.IsAvailable = true; - status.Version = version; - status.Path = fullPath; - status.Details = $"Found Python {version} in PATH"; + status.Version = v; + status.Path = p; + status.Details = $"Found Python {v} in PATH"; return status; } - // 3. Fallback: try 'which' command - if (TryFindInPath("python3", out string pathResult) || - TryFindInPath("python", out pathResult)) - { - if (TryValidatePython(pathResult, out version, out fullPath)) - { - status.IsAvailable = true; - status.Version = version; - status.Path = fullPath; - status.Details = $"Found Python {version} in PATH"; - return status; - } - } - status.ErrorMessage = "Python not found in PATH or standard locations"; status.Details = "Install Python 3.10+ via Homebrew ('brew install python3') and ensure it's in your PATH."; } diff --git a/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs b/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs index 6dc434cc..ded7a5d0 100644 --- a/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs +++ b/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs @@ -187,8 +187,6 @@ public void UpdateConnectionStatus() unityPortField.SetEnabled(true); - unityPortField.SetEnabled(true); - healthStatusLabel.text = HealthStatusUnknown; healthIndicator.RemoveFromClassList("healthy"); healthIndicator.RemoveFromClassList("warning"); From 0de71a35b9be26e7e6d2ef6c9d56201a55bf8cde Mon Sep 17 00:00:00 2001 From: dsarno Date: Thu, 4 Dec 2025 13:18:50 -0800 Subject: [PATCH 10/10] Fix port display not reverting to saved preference when session ends --- .../Windows/Components/Connection/McpConnectionSection.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs b/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs index ded7a5d0..9b2cc933 100644 --- a/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs +++ b/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs @@ -193,10 +193,9 @@ public void UpdateConnectionStatus() healthIndicator.AddToClassList("unknown"); int savedPort = EditorPrefs.GetInt(EditorPrefKeys.UnitySocketPort, 0); - if (savedPort == 0) - { - unityPortField.value = bridgeService.CurrentPort.ToString(); - } + unityPortField.value = (savedPort == 0 + ? bridgeService.CurrentPort + : savedPort).ToString(); } }