Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 17 additions & 11 deletions UnityMcpBridge/Editor/Helpers/ServerInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -148,10 +140,24 @@ private static void InstallServer(string 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>
/// Fetches the latest version from the GitHub pyproject.toml file.
/// </summary>
private static string GetLatestVersion()
public static string GetLatestVersion()
{
using WebClient webClient = new();
string pyprojectContent = webClient.DownloadString(PyprojectUrl);
Expand Down Expand Up @@ -188,7 +194,7 @@ private static string ParseVersionFromPyproject(string content)
/// <summary>
/// Compares two version strings to determine if the latest is newer.
/// </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[] installedParts = installed.Split('.').Select(int.Parse).ToArray();
Expand Down
106 changes: 65 additions & 41 deletions UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using UnityEditor;
using UnityEngine;
Expand All @@ -18,13 +14,11 @@ 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 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()
Expand All @@ -34,7 +28,8 @@ public static void ShowWindow()

private void OnEnable()
{
// Check initial states
UpdatePythonServerInstallationStatus();

isUnityBridgeRunning = UnityMcpBridge.IsRunning;
foreach (McpClient mcpClient in mcpClients.clients)
{
Expand All @@ -57,12 +52,39 @@ 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 = "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)
{
// 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)
Expand Down Expand Up @@ -106,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);
Expand Down Expand Up @@ -156,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
Expand Down Expand Up @@ -191,14 +219,14 @@ 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, pythonServerStatusColor);
EditorGUILayout.LabelField(" " + pythonServerStatus);
Rect installStatusRect = EditorGUILayout.BeginHorizontal(GUILayout.Height(20));
DrawStatusDot(installStatusRect, pythonServerInstallationStatusColor);
EditorGUILayout.LabelField(" " + pythonServerInstallationStatus);
EditorGUILayout.EndHorizontal();

EditorGUILayout.LabelField($"Unity Port: {unityPort}");
Expand Down Expand Up @@ -249,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 = "{}";
Expand All @@ -267,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)
Expand Down Expand Up @@ -311,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
{
Expand All @@ -323,25 +348,26 @@ 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);
}

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")
{
Expand All @@ -360,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
Expand All @@ -370,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")))
{
Expand All @@ -379,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;
Expand Down Expand Up @@ -453,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}";
Expand All @@ -471,7 +495,7 @@ McpClient mcpClient
string pythonDir = FindPackagePythonDirectory();

// Create the manual configuration message
var jsonConfig = new McpConfig
McpConfig jsonConfig = new()
{
mcpServers = new McpConfigServers
{
Expand All @@ -483,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);
Expand Down Expand Up @@ -518,7 +542,7 @@ private void CheckMcpConfiguration(McpClient mcpClient)
}

string configJson = File.ReadAllText(configPath);
var config = JsonConvert.DeserializeObject<McpConfig>(configJson);
McpConfig config = JsonConvert.DeserializeObject<McpConfig>(configJson);

if (config?.mcpServers?.unityMCP != null)
{
Expand Down