Merge pull request #87 from justinpbarnett/bugfix/python-server-status

Bugfix/python server status
main
Justin P Barnett 2025-04-09 09:12:24 -04:00 committed by GitHub
commit cbe9b3a2f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 82 additions and 52 deletions

View File

@ -33,15 +33,7 @@ namespace UnityMcpBridge.Editor.Helpers
} }
else else
{ {
string pyprojectPath = Path.Combine( string installedVersion = GetInstalledVersion();
saveLocation,
ServerFolder,
"src",
"pyproject.toml"
);
string installedVersion = ParseVersionFromPyproject(
File.ReadAllText(pyprojectPath)
);
string latestVersion = GetLatestVersion(); string latestVersion = GetLatestVersion();
if (IsNewerVersion(latestVersion, installedVersion)) if (IsNewerVersion(latestVersion, installedVersion))
@ -148,10 +140,24 @@ namespace UnityMcpBridge.Editor.Helpers
RunCommand("git", $"checkout {BranchName}", workingDirectory: location); RunCommand("git", $"checkout {BranchName}", workingDirectory: location);
} }
/// <summary>
/// Fetches the currently installed version from the local pyproject.toml file.
/// </summary>
public static string GetInstalledVersion()
{
string pyprojectPath = Path.Combine(
GetSaveLocation(),
ServerFolder,
"src",
"pyproject.toml"
);
return ParseVersionFromPyproject(File.ReadAllText(pyprojectPath));
}
/// <summary> /// <summary>
/// Fetches the latest version from the GitHub pyproject.toml file. /// Fetches the latest version from the GitHub pyproject.toml file.
/// </summary> /// </summary>
private static string GetLatestVersion() public static string GetLatestVersion()
{ {
using WebClient webClient = new(); using WebClient webClient = new();
string pyprojectContent = webClient.DownloadString(PyprojectUrl); string pyprojectContent = webClient.DownloadString(PyprojectUrl);
@ -188,7 +194,7 @@ namespace UnityMcpBridge.Editor.Helpers
/// <summary> /// <summary>
/// Compares two version strings to determine if the latest is newer. /// Compares two version strings to determine if the latest is newer.
/// </summary> /// </summary>
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[] latestParts = latest.Split('.').Select(int.Parse).ToArray();
int[] installedParts = installed.Split('.').Select(int.Parse).ToArray(); int[] installedParts = installed.Split('.').Select(int.Parse).ToArray();

View File

@ -1,10 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net.Sockets;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
@ -18,13 +14,11 @@ namespace UnityMcpBridge.Editor.Windows
{ {
private bool isUnityBridgeRunning = false; private bool isUnityBridgeRunning = false;
private Vector2 scrollPosition; private Vector2 scrollPosition;
private string claudeConfigStatus = "Not configured"; private string pythonServerInstallationStatus = "Not Installed";
private string cursorConfigStatus = "Not configured"; private Color pythonServerInstallationStatusColor = Color.red;
private string pythonServerStatus = "Not Connected";
private Color pythonServerStatusColor = Color.red;
private const int unityPort = 6400; // Hardcoded Unity port private const int unityPort = 6400; // Hardcoded Unity port
private const int mcpPort = 6500; // Hardcoded MCP port private const int mcpPort = 6500; // Hardcoded MCP port
private McpClients mcpClients = new(); private readonly McpClients mcpClients = new();
[MenuItem("Window/Unity MCP")] [MenuItem("Window/Unity MCP")]
public static void ShowWindow() public static void ShowWindow()
@ -34,7 +28,8 @@ namespace UnityMcpBridge.Editor.Windows
private void OnEnable() private void OnEnable()
{ {
// Check initial states UpdatePythonServerInstallationStatus();
isUnityBridgeRunning = UnityMcpBridge.IsRunning; isUnityBridgeRunning = UnityMcpBridge.IsRunning;
foreach (McpClient mcpClient in mcpClients.clients) foreach (McpClient mcpClient in mcpClients.clients)
{ {
@ -57,12 +52,39 @@ namespace UnityMcpBridge.Editor.Windows
}; };
} }
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 = "Newer Version Available";
pythonServerInstallationStatusColor = Color.yellow;
}
else
{
pythonServerInstallationStatus = "Up to Date";
pythonServerInstallationStatusColor = Color.green;
}
}
else
{
pythonServerInstallationStatus = "Not Installed";
pythonServerInstallationStatusColor = Color.red;
}
}
private void ConfigurationSection(McpClient mcpClient) private void ConfigurationSection(McpClient mcpClient)
{ {
// Calculate if we should use half-width layout // Calculate if we should use half-width layout
// Minimum width for half-width layout is 400 pixels // Minimum width for half-width layout is 400 pixels
bool useHalfWidth = position.width >= 800; 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 // Begin horizontal layout if using half-width
if (useHalfWidth && mcpClients.clients.IndexOf(mcpClient) % 2 == 0) if (useHalfWidth && mcpClients.clients.IndexOf(mcpClient) % 2 == 0)
@ -106,9 +128,11 @@ namespace UnityMcpBridge.Editor.Windows
EditorGUILayout.Space(8); EditorGUILayout.Space(8);
// Configure button with improved styling // Configure button with improved styling
GUIStyle buttonStyle = new(GUI.skin.button); GUIStyle buttonStyle = new(GUI.skin.button)
buttonStyle.padding = new RectOffset(15, 15, 5, 5); {
buttonStyle.margin = new RectOffset(10, 10, 5, 5); padding = new RectOffset(15, 15, 5, 5),
margin = new RectOffset(10, 10, 5, 5),
};
// Create muted button style for Manual Setup // Create muted button style for Manual Setup
GUIStyle mutedButtonStyle = new(buttonStyle); GUIStyle mutedButtonStyle = new(buttonStyle);
@ -156,7 +180,11 @@ namespace UnityMcpBridge.Editor.Windows
private void DrawStatusDot(Rect statusRect, Color statusColor) private void DrawStatusDot(Rect statusRect, Color statusColor)
{ {
Rect dotRect = new(statusRect.x + 6, statusRect.y + 4, 12, 12); 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; float radius = dotRect.width / 2;
// Draw the main dot // Draw the main dot
@ -191,14 +219,14 @@ namespace UnityMcpBridge.Editor.Windows
); );
EditorGUILayout.Space(10); EditorGUILayout.Space(10);
// Python Server Status Section // Python Server Installation Status Section
EditorGUILayout.BeginVertical(EditorStyles.helpBox); EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("Python Server Status", EditorStyles.boldLabel); EditorGUILayout.LabelField("Python Server Status", EditorStyles.boldLabel);
// Status indicator with colored dot // Status indicator with colored dot
var statusRect = EditorGUILayout.BeginHorizontal(GUILayout.Height(20)); Rect installStatusRect = EditorGUILayout.BeginHorizontal(GUILayout.Height(20));
DrawStatusDot(statusRect, pythonServerStatusColor); DrawStatusDot(installStatusRect, pythonServerInstallationStatusColor);
EditorGUILayout.LabelField(" " + pythonServerStatus); EditorGUILayout.LabelField(" " + pythonServerInstallationStatus);
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
EditorGUILayout.LabelField($"Unity Port: {unityPort}"); EditorGUILayout.LabelField($"Unity Port: {unityPort}");
@ -249,13 +277,13 @@ namespace UnityMcpBridge.Editor.Windows
private string WriteToConfig(string pythonDir, string configPath) private string WriteToConfig(string pythonDir, string configPath)
{ {
// Create configuration object for unityMCP // Create configuration object for unityMCP
var unityMCPConfig = new McpConfigServer McpConfigServer unityMCPConfig = new()
{ {
command = "uv", command = "uv",
args = new[] { "--directory", pythonDir, "run", "server.py" }, 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 // Read existing config if it exists
string existingJson = "{}"; string existingJson = "{}";
@ -267,16 +295,13 @@ namespace UnityMcpBridge.Editor.Windows
} }
catch (Exception e) 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 // Parse the existing JSON while preserving all properties
dynamic existingConfig = JsonConvert.DeserializeObject(existingJson); dynamic existingConfig = JsonConvert.DeserializeObject(existingJson);
if (existingConfig == null) existingConfig ??= new Newtonsoft.Json.Linq.JObject();
{
existingConfig = new Newtonsoft.Json.Linq.JObject();
}
// Ensure mcpServers object exists // Ensure mcpServers object exists
if (existingConfig.mcpServers == null) if (existingConfig.mcpServers == null)
@ -311,7 +336,7 @@ namespace UnityMcpBridge.Editor.Windows
string pythonDir = FindPackagePythonDirectory(); string pythonDir = FindPackagePythonDirectory();
// Create the manual configuration message // Create the manual configuration message
var jsonConfig = new McpConfig McpConfig jsonConfig = new()
{ {
mcpServers = new McpConfigServers mcpServers = new McpConfigServers
{ {
@ -323,7 +348,7 @@ namespace UnityMcpBridge.Editor.Windows
}, },
}; };
var jsonSettings = new JsonSerializerSettings { Formatting = Formatting.Indented }; JsonSerializerSettings jsonSettings = new() { Formatting = Formatting.Indented };
string manualConfigJson = JsonConvert.SerializeObject(jsonConfig, jsonSettings); string manualConfigJson = JsonConvert.SerializeObject(jsonConfig, jsonSettings);
ManualConfigEditorWindow.ShowWindow(configPath, manualConfigJson, mcpClient); ManualConfigEditorWindow.ShowWindow(configPath, manualConfigJson, mcpClient);
@ -331,17 +356,18 @@ namespace UnityMcpBridge.Editor.Windows
private string FindPackagePythonDirectory() private string FindPackagePythonDirectory()
{ {
string pythonDir = "/path/to/your/unity-mcp/Python"; string pythonDir = ServerInstaller.GetServerPath();
try try
{ {
// Try to find the package using Package Manager API // 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 while (!request.IsCompleted) { } // Wait for the request to complete
if (request.Status == UnityEditor.PackageManager.StatusCode.Success) 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") if (package.name == "com.justinpbarnett.unity-mcp")
{ {
@ -360,7 +386,7 @@ namespace UnityMcpBridge.Editor.Windows
} }
else if (request.Error != null) 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 // If not found via Package Manager, try manual approaches
@ -370,7 +396,7 @@ namespace UnityMcpBridge.Editor.Windows
Path.GetFullPath(Path.Combine(Application.dataPath, "unity-mcp", "Python")), 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"))) if (Directory.Exists(dir) && File.Exists(Path.Combine(dir, "server.py")))
{ {
@ -379,13 +405,11 @@ namespace UnityMcpBridge.Editor.Windows
} }
// If still not found, return the placeholder path // If still not found, return the placeholder path
UnityEngine.Debug.LogWarning( Debug.LogWarning("Could not find Python directory, using placeholder path");
"Could not find Python directory, using placeholder path"
);
} }
catch (Exception e) catch (Exception e)
{ {
UnityEngine.Debug.LogError($"Error finding package path: {e.Message}"); Debug.LogError($"Error finding package path: {e.Message}");
} }
return pythonDir; return pythonDir;
@ -453,7 +477,7 @@ namespace UnityMcpBridge.Editor.Windows
} }
ShowManualInstructionsWindow(configPath, mcpClient); ShowManualInstructionsWindow(configPath, mcpClient);
UnityEngine.Debug.LogError( Debug.LogError(
$"Failed to configure {mcpClient.name}: {e.Message}\n{e.StackTrace}" $"Failed to configure {mcpClient.name}: {e.Message}\n{e.StackTrace}"
); );
return $"Failed to configure {mcpClient.name}"; return $"Failed to configure {mcpClient.name}";
@ -471,7 +495,7 @@ namespace UnityMcpBridge.Editor.Windows
string pythonDir = FindPackagePythonDirectory(); string pythonDir = FindPackagePythonDirectory();
// Create the manual configuration message // Create the manual configuration message
var jsonConfig = new McpConfig McpConfig jsonConfig = new()
{ {
mcpServers = new McpConfigServers mcpServers = new McpConfigServers
{ {
@ -483,7 +507,7 @@ namespace UnityMcpBridge.Editor.Windows
}, },
}; };
var jsonSettings = new JsonSerializerSettings { Formatting = Formatting.Indented }; JsonSerializerSettings jsonSettings = new() { Formatting = Formatting.Indented };
string manualConfigJson = JsonConvert.SerializeObject(jsonConfig, jsonSettings); string manualConfigJson = JsonConvert.SerializeObject(jsonConfig, jsonSettings);
ManualConfigEditorWindow.ShowWindow(configPath, manualConfigJson, mcpClient); ManualConfigEditorWindow.ShowWindow(configPath, manualConfigJson, mcpClient);
@ -518,7 +542,7 @@ namespace UnityMcpBridge.Editor.Windows
} }
string configJson = File.ReadAllText(configPath); string configJson = File.ReadAllText(configPath);
var config = JsonConvert.DeserializeObject<McpConfig>(configJson); McpConfig config = JsonConvert.DeserializeObject<McpConfig>(configJson);
if (config?.mcpServers?.unityMCP != null) if (config?.mcpServers?.unityMCP != null)
{ {