From b2f65f2ffc3127ec13628471f48a6cb9e525c908 Mon Sep 17 00:00:00 2001 From: Justin Barnett Date: Wed, 9 Apr 2025 07:42:43 -0400 Subject: [PATCH 1/3] added public helper methods for project versions --- .../Editor/Helpers/ServerInstaller.cs | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/UnityMcpBridge/Editor/Helpers/ServerInstaller.cs b/UnityMcpBridge/Editor/Helpers/ServerInstaller.cs index 7f22c41a..9d5682f4 100644 --- a/UnityMcpBridge/Editor/Helpers/ServerInstaller.cs +++ b/UnityMcpBridge/Editor/Helpers/ServerInstaller.cs @@ -33,15 +33,7 @@ public static void EnsureServerInstalled() } else { - string pyprojectPath = Path.Combine( - saveLocation, - ServerFolder, - "src", - "pyproject.toml" - ); - string installedVersion = ParseVersionFromPyproject( - File.ReadAllText(pyprojectPath) - ); + string installedVersion = GetInstalledVersion(); string latestVersion = GetLatestVersion(); if (IsNewerVersion(latestVersion, installedVersion)) @@ -148,10 +140,24 @@ private static void InstallServer(string location) RunCommand("git", $"checkout {BranchName}", workingDirectory: location); } + /// + /// Fetches the currently installed version from the local pyproject.toml file. + /// + public static string GetInstalledVersion() + { + string pyprojectPath = Path.Combine( + GetSaveLocation(), + ServerFolder, + "src", + "pyproject.toml" + ); + return ParseVersionFromPyproject(File.ReadAllText(pyprojectPath)); + } + /// /// Fetches the latest version from the GitHub pyproject.toml file. /// - private static string GetLatestVersion() + public static string GetLatestVersion() { using WebClient webClient = new(); string pyprojectContent = webClient.DownloadString(PyprojectUrl); @@ -188,7 +194,7 @@ private static string ParseVersionFromPyproject(string content) /// /// Compares two version strings to determine if the latest is newer. /// - private static bool IsNewerVersion(string latest, string installed) + public static bool IsNewerVersion(string latest, string installed) { int[] latestParts = latest.Split('.').Select(int.Parse).ToArray(); int[] installedParts = installed.Split('.').Select(int.Parse).ToArray(); From 52c75f95219be029c5dbf93eea53951fdfeead9b Mon Sep 17 00:00:00 2001 From: Justin Barnett Date: Wed, 9 Apr 2025 07:43:01 -0400 Subject: [PATCH 2/3] added project install status and re-added connection check --- .../Editor/Windows/UnityMcpEditorWindow.cs | 88 +++++++++++++++++-- 1 file changed, 80 insertions(+), 8 deletions(-) diff --git a/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs b/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs index c9ebd769..d7128eb4 100644 --- a/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs +++ b/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; using System.Net.Sockets; using System.Runtime.InteropServices; using System.Text; @@ -18,13 +21,14 @@ public class UnityMcpEditorWindow : EditorWindow { private bool isUnityBridgeRunning = false; private Vector2 scrollPosition; - private string claudeConfigStatus = "Not configured"; - private string cursorConfigStatus = "Not configured"; - private string pythonServerStatus = "Not Connected"; - private Color pythonServerStatusColor = Color.red; + private string pythonServerInstallationStatus = "Not Installed"; + private Color pythonServerInstallationStatusColor = Color.red; + private string pythonServerConnectionStatus = "Not Connected"; + private Color pythonServerConnectionStatusColor = Color.red; + private DateTime lastConnectionCheck; private const int unityPort = 6400; // Hardcoded Unity port private const int mcpPort = 6500; // Hardcoded MCP port - private McpClients mcpClients = new(); + private readonly McpClients mcpClients = new(); [MenuItem("Window/Unity MCP")] public static void ShowWindow() @@ -34,7 +38,9 @@ public static void ShowWindow() private void OnEnable() { - // Check initial states + UpdatePythonServerInstallationStatus(); + UpdatePythonServerConnectionStatus(); + isUnityBridgeRunning = UnityMcpBridge.IsRunning; foreach (McpClient mcpClient in mcpClients.clients) { @@ -42,6 +48,16 @@ private void OnEnable() } } + private void Update() + { + if (lastConnectionCheck.AddSeconds(2) < DateTime.Now) + { + UpdatePythonServerConnectionStatus(); + + lastConnectionCheck = DateTime.Now; + } + } + private Color GetStatusColor(McpStatus status) { // Return appropriate color based on the status enum @@ -57,6 +73,62 @@ private Color GetStatusColor(McpStatus status) }; } + private void UpdatePythonServerInstallationStatus() + { + string serverPath = ServerInstaller.GetServerPath(); + + if (File.Exists(Path.Combine(serverPath, "server.py"))) + { + string installedVersion = ServerInstaller.GetInstalledVersion(); + string latestVersion = ServerInstaller.GetLatestVersion(); + + if (ServerInstaller.IsNewerVersion(latestVersion, installedVersion)) + { + pythonServerInstallationStatus = "Up to Date"; + pythonServerInstallationStatusColor = Color.green; + } + else + { + pythonServerInstallationStatus = "Newer Version Available"; + pythonServerInstallationStatusColor = Color.yellow; + } + } + else + { + pythonServerInstallationStatus = "Not Installed"; + pythonServerInstallationStatusColor = Color.red; + } + } + + private void UpdatePythonServerConnectionStatus() + { + IPGlobalProperties ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties(); + TcpConnectionInformation[] tcpConnections = + ipGlobalProperties.GetActiveTcpConnections(); + IPEndPoint[] tcpListeners = ipGlobalProperties.GetActiveTcpListeners(); + + // Check if the port is in use by any active listener + bool isListenerActive = tcpListeners.Any(static endpoint => endpoint.Port == mcpPort); + + // Optionally, check if the port is in use by any active connection + bool isConnectionActive = tcpConnections.Any(static connection => + connection.LocalEndPoint.Port == mcpPort + || connection.RemoteEndPoint.Port == mcpPort + ); + + // Return true if either a listener or connection is using the port + if (isListenerActive || isConnectionActive) + { + pythonServerConnectionStatus = "Connected"; + pythonServerConnectionStatusColor = Color.green; + } + else + { + pythonServerConnectionStatus = "Not Connected"; + pythonServerConnectionStatusColor = Color.red; + } + } + private void ConfigurationSection(McpClient mcpClient) { // Calculate if we should use half-width layout @@ -197,8 +269,8 @@ private void OnGUI() // Status indicator with colored dot var statusRect = EditorGUILayout.BeginHorizontal(GUILayout.Height(20)); - DrawStatusDot(statusRect, pythonServerStatusColor); - EditorGUILayout.LabelField(" " + pythonServerStatus); + DrawStatusDot(statusRect, pythonServerInstallationStatusColor); + EditorGUILayout.LabelField(" " + pythonServerInstallationStatus); EditorGUILayout.EndHorizontal(); EditorGUILayout.LabelField($"Unity Port: {unityPort}"); From 4a3f4e73015d90b8e2117521571d81687c64034a Mon Sep 17 00:00:00 2001 From: Justin Barnett Date: Wed, 9 Apr 2025 09:10:21 -0400 Subject: [PATCH 3/3] linting and removed unused properties --- .../Editor/Windows/UnityMcpEditorWindow.cs | 120 ++++++------------ 1 file changed, 36 insertions(+), 84 deletions(-) diff --git a/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs b/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs index d7128eb4..c794fb71 100644 --- a/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs +++ b/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs @@ -1,13 +1,6 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Net; -using System.Net.NetworkInformation; -using System.Net.Sockets; using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; using Newtonsoft.Json; using UnityEditor; using UnityEngine; @@ -23,9 +16,6 @@ public class UnityMcpEditorWindow : EditorWindow private Vector2 scrollPosition; private string pythonServerInstallationStatus = "Not Installed"; private Color pythonServerInstallationStatusColor = Color.red; - private string pythonServerConnectionStatus = "Not Connected"; - private Color pythonServerConnectionStatusColor = Color.red; - private DateTime lastConnectionCheck; private const int unityPort = 6400; // Hardcoded Unity port private const int mcpPort = 6500; // Hardcoded MCP port private readonly McpClients mcpClients = new(); @@ -39,7 +29,6 @@ public static void ShowWindow() private void OnEnable() { UpdatePythonServerInstallationStatus(); - UpdatePythonServerConnectionStatus(); isUnityBridgeRunning = UnityMcpBridge.IsRunning; foreach (McpClient mcpClient in mcpClients.clients) @@ -48,16 +37,6 @@ private void OnEnable() } } - private void Update() - { - if (lastConnectionCheck.AddSeconds(2) < DateTime.Now) - { - UpdatePythonServerConnectionStatus(); - - lastConnectionCheck = DateTime.Now; - } - } - private Color GetStatusColor(McpStatus status) { // Return appropriate color based on the status enum @@ -84,13 +63,13 @@ private void UpdatePythonServerInstallationStatus() if (ServerInstaller.IsNewerVersion(latestVersion, installedVersion)) { - pythonServerInstallationStatus = "Up to Date"; - pythonServerInstallationStatusColor = Color.green; + pythonServerInstallationStatus = "Newer Version Available"; + pythonServerInstallationStatusColor = Color.yellow; } else { - pythonServerInstallationStatus = "Newer Version Available"; - pythonServerInstallationStatusColor = Color.yellow; + pythonServerInstallationStatus = "Up to Date"; + pythonServerInstallationStatusColor = Color.green; } } else @@ -100,41 +79,12 @@ private void UpdatePythonServerInstallationStatus() } } - private void UpdatePythonServerConnectionStatus() - { - IPGlobalProperties ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties(); - TcpConnectionInformation[] tcpConnections = - ipGlobalProperties.GetActiveTcpConnections(); - IPEndPoint[] tcpListeners = ipGlobalProperties.GetActiveTcpListeners(); - - // Check if the port is in use by any active listener - bool isListenerActive = tcpListeners.Any(static endpoint => endpoint.Port == mcpPort); - - // Optionally, check if the port is in use by any active connection - bool isConnectionActive = tcpConnections.Any(static connection => - connection.LocalEndPoint.Port == mcpPort - || connection.RemoteEndPoint.Port == mcpPort - ); - - // Return true if either a listener or connection is using the port - if (isListenerActive || isConnectionActive) - { - pythonServerConnectionStatus = "Connected"; - pythonServerConnectionStatusColor = Color.green; - } - else - { - pythonServerConnectionStatus = "Not Connected"; - pythonServerConnectionStatusColor = Color.red; - } - } - private void ConfigurationSection(McpClient mcpClient) { // Calculate if we should use half-width layout // Minimum width for half-width layout is 400 pixels bool useHalfWidth = position.width >= 800; - float sectionWidth = useHalfWidth ? position.width / 2 - 15 : position.width - 20; + float sectionWidth = useHalfWidth ? (position.width / 2) - 15 : position.width - 20; // Begin horizontal layout if using half-width if (useHalfWidth && mcpClients.clients.IndexOf(mcpClient) % 2 == 0) @@ -178,9 +128,11 @@ private void ConfigurationSection(McpClient mcpClient) EditorGUILayout.Space(8); // Configure button with improved styling - GUIStyle buttonStyle = new(GUI.skin.button); - buttonStyle.padding = new RectOffset(15, 15, 5, 5); - buttonStyle.margin = new RectOffset(10, 10, 5, 5); + GUIStyle buttonStyle = new(GUI.skin.button) + { + padding = new RectOffset(15, 15, 5, 5), + margin = new RectOffset(10, 10, 5, 5), + }; // Create muted button style for Manual Setup GUIStyle mutedButtonStyle = new(buttonStyle); @@ -228,7 +180,11 @@ private void ConfigurationSection(McpClient mcpClient) private void DrawStatusDot(Rect statusRect, Color statusColor) { Rect dotRect = new(statusRect.x + 6, statusRect.y + 4, 12, 12); - Vector3 center = new(dotRect.x + dotRect.width / 2, dotRect.y + dotRect.height / 2, 0); + Vector3 center = new( + dotRect.x + (dotRect.width / 2), + dotRect.y + (dotRect.height / 2), + 0 + ); float radius = dotRect.width / 2; // Draw the main dot @@ -263,13 +219,13 @@ private void OnGUI() ); EditorGUILayout.Space(10); - // Python Server Status Section + // Python Server Installation Status Section EditorGUILayout.BeginVertical(EditorStyles.helpBox); EditorGUILayout.LabelField("Python Server Status", EditorStyles.boldLabel); // Status indicator with colored dot - var statusRect = EditorGUILayout.BeginHorizontal(GUILayout.Height(20)); - DrawStatusDot(statusRect, pythonServerInstallationStatusColor); + Rect installStatusRect = EditorGUILayout.BeginHorizontal(GUILayout.Height(20)); + DrawStatusDot(installStatusRect, pythonServerInstallationStatusColor); EditorGUILayout.LabelField(" " + pythonServerInstallationStatus); EditorGUILayout.EndHorizontal(); @@ -321,13 +277,13 @@ private void ToggleUnityBridge() private string WriteToConfig(string pythonDir, string configPath) { // Create configuration object for unityMCP - var unityMCPConfig = new McpConfigServer + McpConfigServer unityMCPConfig = new() { command = "uv", args = new[] { "--directory", pythonDir, "run", "server.py" }, }; - var jsonSettings = new JsonSerializerSettings { Formatting = Formatting.Indented }; + JsonSerializerSettings jsonSettings = new() { Formatting = Formatting.Indented }; // Read existing config if it exists string existingJson = "{}"; @@ -339,16 +295,13 @@ private string WriteToConfig(string pythonDir, string configPath) } catch (Exception e) { - UnityEngine.Debug.LogWarning($"Error reading existing config: {e.Message}."); + Debug.LogWarning($"Error reading existing config: {e.Message}."); } } // Parse the existing JSON while preserving all properties dynamic existingConfig = JsonConvert.DeserializeObject(existingJson); - if (existingConfig == null) - { - existingConfig = new Newtonsoft.Json.Linq.JObject(); - } + existingConfig ??= new Newtonsoft.Json.Linq.JObject(); // Ensure mcpServers object exists if (existingConfig.mcpServers == null) @@ -383,7 +336,7 @@ private void ShowManualInstructionsWindow(string configPath, McpClient mcpClient string pythonDir = FindPackagePythonDirectory(); // Create the manual configuration message - var jsonConfig = new McpConfig + McpConfig jsonConfig = new() { mcpServers = new McpConfigServers { @@ -395,7 +348,7 @@ private void ShowManualInstructionsWindow(string configPath, McpClient mcpClient }, }; - var jsonSettings = new JsonSerializerSettings { Formatting = Formatting.Indented }; + JsonSerializerSettings jsonSettings = new() { Formatting = Formatting.Indented }; string manualConfigJson = JsonConvert.SerializeObject(jsonConfig, jsonSettings); ManualConfigEditorWindow.ShowWindow(configPath, manualConfigJson, mcpClient); @@ -403,17 +356,18 @@ private void ShowManualInstructionsWindow(string configPath, McpClient mcpClient private string FindPackagePythonDirectory() { - string pythonDir = "/path/to/your/unity-mcp/Python"; + string pythonDir = ServerInstaller.GetServerPath(); try { // Try to find the package using Package Manager API - var request = UnityEditor.PackageManager.Client.List(); + UnityEditor.PackageManager.Requests.ListRequest request = + UnityEditor.PackageManager.Client.List(); while (!request.IsCompleted) { } // Wait for the request to complete if (request.Status == UnityEditor.PackageManager.StatusCode.Success) { - foreach (var package in request.Result) + foreach (UnityEditor.PackageManager.PackageInfo package in request.Result) { if (package.name == "com.justinpbarnett.unity-mcp") { @@ -432,7 +386,7 @@ private string FindPackagePythonDirectory() } else if (request.Error != null) { - UnityEngine.Debug.LogError("Failed to list packages: " + request.Error.message); + Debug.LogError("Failed to list packages: " + request.Error.message); } // If not found via Package Manager, try manual approaches @@ -442,7 +396,7 @@ private string FindPackagePythonDirectory() Path.GetFullPath(Path.Combine(Application.dataPath, "unity-mcp", "Python")), }; - foreach (var dir in possibleDirs) + foreach (string dir in possibleDirs) { if (Directory.Exists(dir) && File.Exists(Path.Combine(dir, "server.py"))) { @@ -451,13 +405,11 @@ private string FindPackagePythonDirectory() } // If still not found, return the placeholder path - UnityEngine.Debug.LogWarning( - "Could not find Python directory, using placeholder path" - ); + Debug.LogWarning("Could not find Python directory, using placeholder path"); } catch (Exception e) { - UnityEngine.Debug.LogError($"Error finding package path: {e.Message}"); + Debug.LogError($"Error finding package path: {e.Message}"); } return pythonDir; @@ -525,7 +477,7 @@ private string ConfigureMcpClient(McpClient mcpClient) } ShowManualInstructionsWindow(configPath, mcpClient); - UnityEngine.Debug.LogError( + Debug.LogError( $"Failed to configure {mcpClient.name}: {e.Message}\n{e.StackTrace}" ); return $"Failed to configure {mcpClient.name}"; @@ -543,7 +495,7 @@ McpClient mcpClient string pythonDir = FindPackagePythonDirectory(); // Create the manual configuration message - var jsonConfig = new McpConfig + McpConfig jsonConfig = new() { mcpServers = new McpConfigServers { @@ -555,7 +507,7 @@ McpClient mcpClient }, }; - var jsonSettings = new JsonSerializerSettings { Formatting = Formatting.Indented }; + JsonSerializerSettings jsonSettings = new() { Formatting = Formatting.Indented }; string manualConfigJson = JsonConvert.SerializeObject(jsonConfig, jsonSettings); ManualConfigEditorWindow.ShowWindow(configPath, manualConfigJson, mcpClient); @@ -590,7 +542,7 @@ private void CheckMcpConfiguration(McpClient mcpClient) } string configJson = File.ReadAllText(configPath); - var config = JsonConvert.DeserializeObject(configJson); + McpConfig config = JsonConvert.DeserializeObject(configJson); if (config?.mcpServers?.unityMCP != null) {