diff --git a/UnityMcpBridge/Editor/Helpers/ServerInstaller.cs b/UnityMcpBridge/Editor/Helpers/ServerInstaller.cs
index f4da790..304629a 100644
--- a/UnityMcpBridge/Editor/Helpers/ServerInstaller.cs
+++ b/UnityMcpBridge/Editor/Helpers/ServerInstaller.cs
@@ -1,9 +1,8 @@
using System;
using System.IO;
using System.Linq;
-using System.Net.Http;
+using System.Net;
using System.Runtime.InteropServices;
-using System.Text.RegularExpressions;
using UnityEngine;
namespace UnityMcpBridge.Editor.Helpers
@@ -11,240 +10,222 @@ namespace UnityMcpBridge.Editor.Helpers
public static class ServerInstaller
{
private const string PackageName = "unity-mcp-server";
- private const string BranchName = "feature/install-overhaul";
- private const string GitUrl =
- "git+https://github.com/justinpbarnett/unity-mcp.git@"
- + BranchName
- + "#subdirectory=UnityMcpServer";
+ private const string BranchName = "feature/install-overhaul"; // Adjust branch as needed
+ private const string GitUrl = "https://github.com/justinpbarnett/unity-mcp.git";
private const string PyprojectUrl =
"https://raw.githubusercontent.com/justinpbarnett/unity-mcp/"
+ BranchName
+ "/UnityMcpServer/pyproject.toml";
- // Typical uv installation paths per OS
- private static readonly string[] WindowsUvPaths = new[]
- {
- @"C:\Users\$USER$\.local\bin\uv.exe",
- @"C:\Program Files\uv\uv.exe",
- @"C:\Users\$USER$\AppData\Local\Programs\uv\uv.exe",
- };
-
- private static readonly string[] LinuxUvPaths = new[]
- {
- "/home/$USER$/.local/bin/uv",
- "/usr/local/bin/uv",
- "/usr/bin/uv",
- };
-
- private static readonly string[] MacUvPaths = new[]
- {
- "/Users/$USER$/.local/bin/uv",
- "/usr/local/bin/uv",
- "/opt/homebrew/bin/uv",
- };
-
+ ///
+ /// Ensures the unity-mcp-server is installed and up to date.
+ ///
public static void EnsureServerInstalled()
{
try
{
- string uvPath = FindUvExecutable();
- if (string.IsNullOrEmpty(uvPath))
- {
- throw new Exception(
- "Could not find 'uv' executable. Please ensure it is installed."
- );
- }
+ string saveLocation = GetSaveLocation();
+ Debug.Log($"Server save location: {saveLocation}");
- // Check if the package is installed
- System.Diagnostics.Process process = new();
- process.StartInfo.FileName = uvPath;
- process.StartInfo.Arguments = "pip show " + PackageName;
- process.StartInfo.UseShellExecute = false;
- process.StartInfo.RedirectStandardOutput = true;
- process.StartInfo.RedirectStandardError = true;
- process.Start();
- string output = process.StandardOutput.ReadToEnd();
- string error = process.StandardError.ReadToEnd();
- process.WaitForExit();
-
- if (process.ExitCode == 0)
+ if (!IsServerInstalled(saveLocation))
{
- // Package is installed, check version
- string installedVersion = GetVersionFromPipShow(output);
- string latestVersion = GetLatestVersionFromGitHub();
- if (new Version(installedVersion) < new Version(latestVersion))
- {
- Debug.Log(
- $"Updating {PackageName} from {installedVersion} to {latestVersion}..."
- );
- RunCommand(uvPath, "pip install --upgrade " + GitUrl);
- Debug.Log($"{PackageName} updated successfully.");
- }
- else
- {
- Debug.Log($"{PackageName} is up to date (version {installedVersion}).");
- }
- }
- else if (process.ExitCode == 1 && output.Contains("Package(s) not found"))
- {
- // Package not found, install it from GitHub
- Debug.Log("Installing " + PackageName + "...");
- RunCommand(uvPath, "pip install " + GitUrl);
- Debug.Log(PackageName + " installed successfully.");
+ Debug.Log("Server not found. Installing...");
+ InstallServer(saveLocation);
}
else
{
- throw new Exception(
- $"Command 'uv pip show {PackageName}' failed with exit code {process.ExitCode}. Output: {output} Error: {error}"
- );
+ Debug.Log("Server is installed. Checking version...");
+ string installedVersion = GetInstalledVersion(saveLocation);
+ string latestVersion = GetLatestVersion();
+
+ if (IsNewerVersion(latestVersion, installedVersion))
+ {
+ Debug.Log(
+ $"Newer version available ({latestVersion} > {installedVersion}). Updating..."
+ );
+ UpdateServer(saveLocation);
+ }
+ else
+ {
+ Debug.Log("Server is up to date.");
+ }
}
}
catch (Exception ex)
{
- Debug.LogError($"Failed to ensure {PackageName} is installed: {ex.Message}");
- Debug.LogWarning(
- "Please install "
- + PackageName
- + " manually using 'uv pip install "
- + GitUrl
- + "'."
- );
+ Debug.LogError($"Failed to ensure server installation: {ex.Message}");
}
}
- private static void RunCommand(string uvPath, string arguments)
+ ///
+ /// Gets the platform-specific save location for the server.
+ ///
+ private static string GetSaveLocation()
{
- System.Diagnostics.Process installProcess = new();
- installProcess.StartInfo.FileName = uvPath;
- installProcess.StartInfo.Arguments = arguments;
- installProcess.StartInfo.UseShellExecute = false;
- installProcess.StartInfo.RedirectStandardOutput = true;
- installProcess.StartInfo.RedirectStandardError = true;
- installProcess.Start();
- string installOutput = installProcess.StandardOutput.ReadToEnd();
- string installError = installProcess.StandardError.ReadToEnd();
- installProcess.WaitForExit();
-
- if (installProcess.ExitCode != 0)
- {
- throw new Exception(
- $"Command '{uvPath} {arguments}' failed. Output: {installOutput} Error: {installError}"
- );
- }
- }
-
- private static string FindUvExecutable()
- {
- string username = Environment.UserName;
- string[] uvPaths;
-
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
- uvPaths = WindowsUvPaths.Select(p => p.Replace("$USER$", username)).ToArray();
+ // Use a user-specific program directory under %USERPROFILE%\AppData\Local\Programs
+ return Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
+ "AppData",
+ "Local",
+ "Programs",
+ PackageName
+ );
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
- uvPaths = LinuxUvPaths.Select(p => p.Replace("$USER$", username)).ToArray();
+ return Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
+ "bin",
+ PackageName
+ );
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
- uvPaths = MacUvPaths.Select(p => p.Replace("$USER$", username)).ToArray();
- }
- else
- {
- throw new PlatformNotSupportedException("Unsupported operating system.");
+ string path = "/usr/local/bin";
+ if (!Directory.Exists(path) || !IsDirectoryWritable(path))
+ {
+ return Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
+ "Applications",
+ PackageName
+ );
+ }
+ return Path.Combine(path, PackageName);
}
+ throw new Exception("Unsupported operating system.");
+ }
- // First, try 'uv' directly from PATH
+ private static bool IsDirectoryWritable(string path)
+ {
try
{
- System.Diagnostics.Process process = new();
- process.StartInfo.FileName = "uv";
- process.StartInfo.Arguments = "--version";
- process.StartInfo.UseShellExecute = false;
- process.StartInfo.RedirectStandardOutput = true;
- process.StartInfo.RedirectStandardError = true;
- process.Start();
- process.WaitForExit(2000); // Wait up to 2 seconds
- if (process.ExitCode == 0)
- {
- return "uv"; // Found in PATH
- }
+ File.Create(Path.Combine(path, "test.txt")).Dispose();
+ File.Delete(Path.Combine(path, "test.txt"));
+ return true;
}
catch
{
- // Not in PATH, proceed to check specific locations
+ return false;
}
+ }
- // Check specific paths
- foreach (string path in uvPaths)
+ ///
+ /// Checks if the server is installed at the specified location.
+ ///
+ private static bool IsServerInstalled(string location)
+ {
+ return Directory.Exists(location) && File.Exists(Path.Combine(location, "version.txt"));
+ }
+
+ ///
+ /// Installs the server by cloning the repository and setting up dependencies.
+ ///
+ private static void InstallServer(string location)
+ {
+ // Clone the repository
+ RunCommand("git", $"clone -b {BranchName} {GitUrl} \"{location}\"");
+
+ // Set up virtual environment and install dependencies
+ string venvPath = Path.Combine(location, "venv");
+ RunCommand("python", $"-m venv \"{venvPath}\"");
+ string uvPath = Path.Combine(
+ venvPath,
+ RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "Scripts/uv.exe" : "bin/uv"
+ );
+ RunCommand(uvPath, "install");
+ }
+
+ ///
+ /// Retrieves the installed server version from version.txt.
+ ///
+ private static string GetInstalledVersion(string location)
+ {
+ string versionFile = Path.Combine(location, "version.txt");
+ return File.ReadAllText(versionFile).Trim();
+ }
+
+ ///
+ /// Fetches the latest version from the GitHub pyproject.toml file.
+ ///
+ private static string GetLatestVersion()
+ {
+ using var webClient = new WebClient();
+ string pyprojectContent = webClient.DownloadString(PyprojectUrl);
+ return ParseVersionFromPyproject(pyprojectContent);
+ }
+
+ ///
+ /// Updates the server by pulling the latest changes.
+ ///
+ private static void UpdateServer(string location)
+ {
+ RunCommand("git", $"-C \"{location}\" pull");
+ // Optionally reinstall dependencies if requirements changed
+ }
+
+ ///
+ /// Parses the version number from pyproject.toml content.
+ ///
+ private static string ParseVersionFromPyproject(string content)
+ {
+ foreach (var line in content.Split('\n'))
{
- if (File.Exists(path))
+ if (line.Trim().StartsWith("version ="))
{
- return path;
+ var parts = line.Split('=');
+ if (parts.Length == 2)
+ return parts[1].Trim().Trim('"');
}
}
-
- return null; // Not found
+ throw new Exception("Version not found in pyproject.toml");
}
- private static string GetVersionFromPipShow(string output)
+ ///
+ /// Compares two version strings to determine if the latest is newer.
+ ///
+ private static bool IsNewerVersion(string latest, string installed)
{
- string[] lines = output.Split('\n');
- foreach (string line in lines)
+ var latestParts = latest.Split('.').Select(int.Parse).ToArray();
+ var installedParts = installed.Split('.').Select(int.Parse).ToArray();
+ for (int i = 0; i < Math.Min(latestParts.Length, installedParts.Length); i++)
{
- if (line.StartsWith("Version:"))
+ if (latestParts[i] > installedParts[i])
+ return true;
+ if (latestParts[i] < installedParts[i])
+ return false;
+ }
+ return latestParts.Length > installedParts.Length;
+ }
+
+ ///
+ /// Runs a command-line process and handles output/errors.
+ ///
+ private static void RunCommand(string command, string arguments)
+ {
+ var process = new System.Diagnostics.Process
+ {
+ StartInfo = new System.Diagnostics.ProcessStartInfo
{
- return line["Version:".Length..].Trim();
- }
- }
- throw new Exception("Version not found in pip show output");
- }
-
- private static string GetLatestVersionFromGitHub()
- {
- using HttpClient client = new();
- client.DefaultRequestHeaders.Add("User-Agent", "UnityMcpBridge");
- string content = client.GetStringAsync(PyprojectUrl).Result;
- string pattern = @"version\s*=\s*""(.*?)""";
- Match match = Regex.Match(content, pattern);
- return match.Success
- ? match.Groups[1].Value
- : throw new Exception("Could not find version in pyproject.toml");
- }
-
- private static string GetUvNotFoundMessage()
- {
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ FileName = command,
+ Arguments = arguments,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ },
+ };
+ process.Start();
+ string output = process.StandardOutput.ReadToEnd();
+ string error = process.StandardError.ReadToEnd();
+ process.WaitForExit();
+ if (process.ExitCode != 0)
{
- return "uv not found in PATH or typical Windows locations.";
+ throw new Exception(
+ $"Command failed: {command} {arguments}\nOutput: {output}\nError: {error}"
+ );
}
- else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- {
- return "uv not found in PATH or typical Linux locations.";
- }
- else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
- {
- return "uv not found in PATH or typical macOS locations.";
- }
- return "uv not found on this platform.";
- }
-
- private static string GetInstallInstructions()
- {
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- return "Install uv with: powershell -c \"irm https://astral.sh/uv/install.ps1 | iex\" and ensure it's in your PATH.";
- }
- else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- {
- return "Install uv with: curl -LsSf https://astral.sh/uv/install.sh | sh and ensure it's in your PATH.";
- }
- else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
- {
- return "Install uv with: brew install uv or curl -LsSf https://astral.sh/uv/install.sh | sh and ensure it's in your PATH.";
- }
- return "Install uv following platform-specific instructions and add it to your PATH.";
}
}
}
diff --git a/UnityMcpBridge/Editor/UnityMcpBridge.cs b/UnityMcpBridge/Editor/UnityMcpBridge.cs
index ad6b6d8..9276c05 100644
--- a/UnityMcpBridge/Editor/UnityMcpBridge.cs
+++ b/UnityMcpBridge/Editor/UnityMcpBridge.cs
@@ -56,6 +56,8 @@ namespace UnityMcpBridge.Editor
public static void Start()
{
+ Stop();
+
try
{
ServerInstaller.EnsureServerInstalled();
@@ -70,9 +72,6 @@ namespace UnityMcpBridge.Editor
return;
}
- // Stop any existing listener to free the port
- Stop();
-
try
{
listener = new TcpListener(IPAddress.Loopback, unityPort);