From 4e1b905ea02e9ef3a4e010a2b7c4f24752c00c69 Mon Sep 17 00:00:00 2001 From: David Sarno Date: Wed, 13 Aug 2025 14:02:19 -0700 Subject: [PATCH] chore: bump version to 2.1.0; Windows uv resolver improvements; preserve existing uv command; Claude unregister UI fix; .ps1 handling; add generic mcp_source.py --- UnityMcpBridge/Editor/Helpers/ExecPath.cs | 10 ++- .../Editor/Helpers/ServerInstaller.cs | 79 ++++++++++--------- .../Editor/Windows/UnityMcpEditorWindow.cs | 19 ++++- UnityMcpBridge/package.json | 2 +- mcp_source.py | 2 +- 5 files changed, 69 insertions(+), 43 deletions(-) diff --git a/UnityMcpBridge/Editor/Helpers/ExecPath.cs b/UnityMcpBridge/Editor/Helpers/ExecPath.cs index 99dcf5a..e3a03b4 100644 --- a/UnityMcpBridge/Editor/Helpers/ExecPath.cs +++ b/UnityMcpBridge/Editor/Helpers/ExecPath.cs @@ -176,10 +176,16 @@ namespace UnityMcpBridge.Editor.Helpers stderr = string.Empty; try { + // Handle PowerShell scripts on Windows by invoking through powershell.exe + bool isPs1 = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && + file.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase); + var psi = new ProcessStartInfo { - FileName = file, - Arguments = args, + FileName = isPs1 ? "powershell.exe" : file, + Arguments = isPs1 + ? $"-NoProfile -ExecutionPolicy Bypass -File \"{file}\" {args}".Trim() + : args, WorkingDirectory = string.IsNullOrEmpty(workingDir) ? Environment.CurrentDirectory : workingDir, UseShellExecute = false, RedirectStandardOutput = true, diff --git a/UnityMcpBridge/Editor/Helpers/ServerInstaller.cs b/UnityMcpBridge/Editor/Helpers/ServerInstaller.cs index aa84589..a2c28fe 100644 --- a/UnityMcpBridge/Editor/Helpers/ServerInstaller.cs +++ b/UnityMcpBridge/Editor/Helpers/ServerInstaller.cs @@ -2,7 +2,6 @@ using System; using System.IO; using System.Runtime.InteropServices; using System.Text; -using System.Reflection; using UnityEditor; using UnityEngine; @@ -70,21 +69,19 @@ namespace UnityMcpBridge.Editor.Helpers { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - return Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), - "AppData", - "Local", - "Programs", - RootFolder - ); + var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) ?? string.Empty, "AppData", "Local"); + return Path.Combine(localAppData, "Programs", RootFolder); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - return Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), - "bin", - RootFolder - ); + var xdg = Environment.GetEnvironmentVariable("XDG_DATA_HOME"); + if (string.IsNullOrEmpty(xdg)) + { + xdg = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) ?? string.Empty, + ".local", "share"); + } + return Path.Combine(xdg, RootFolder); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { @@ -273,12 +270,41 @@ namespace UnityMcpBridge.Editor.Helpers string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) ?? string.Empty; string programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) ?? string.Empty; string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) ?? string.Empty; + string programData = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) ?? string.Empty; // optional fallback + + // Fast path: resolve from PATH first + try + { + var wherePsi = new System.Diagnostics.ProcessStartInfo + { + FileName = "where", + Arguments = "uv.exe", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + using var wp = System.Diagnostics.Process.Start(wherePsi); + string output = wp.StandardOutput.ReadToEnd().Trim(); + wp.WaitForExit(1500); + if (wp.ExitCode == 0 && !string.IsNullOrEmpty(output)) + { + foreach (var line in output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)) + { + string path = line.Trim(); + if (File.Exists(path) && ValidateUvBinary(path)) return path; + } + } + } + catch { } candidates = new[] { // Preferred: WinGet Links shims (stable entrypoints) Path.Combine(localAppData, "Microsoft", "WinGet", "Links", "uv.exe"), Path.Combine(programFiles, "WinGet", "Links", "uv.exe"), + // Optional low-priority fallback for atypical images + Path.Combine(programData, "Microsoft", "WinGet", "Links", "uv.exe"), // Common per-user installs Path.Combine(localAppData, @"Programs\Python\Python313\Scripts\uv.exe"), @@ -325,33 +351,10 @@ namespace UnityMcpBridge.Editor.Helpers catch { /* ignore */ } } - // Use platform-appropriate which/where to resolve from PATH + // Use platform-appropriate which/where to resolve from PATH (non-Windows handled here; Windows tried earlier) try { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - var wherePsi = new System.Diagnostics.ProcessStartInfo - { - FileName = "where", - Arguments = "uv.exe", - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true - }; - using var wp = System.Diagnostics.Process.Start(wherePsi); - string output = wp.StandardOutput.ReadToEnd().Trim(); - wp.WaitForExit(3000); - if (wp.ExitCode == 0 && !string.IsNullOrEmpty(output)) - { - foreach (var line in output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)) - { - string path = line.Trim(); - if (File.Exists(path) && ValidateUvBinary(path)) return path; - } - } - } - else + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { var whichPsi = new System.Diagnostics.ProcessStartInfo { diff --git a/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs b/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs index ab8ab32..234a3a0 100644 --- a/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs +++ b/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs @@ -1064,6 +1064,14 @@ namespace UnityMcpBridge.Editor.Windows // Merge without replacing the existing command if (mcpClient?.mcpType == McpTypes.VSCode) { + if (existingConfig.servers == null) + { + existingConfig.servers = new Newtonsoft.Json.Linq.JObject(); + } + if (existingConfig.servers.unityMCP == null) + { + existingConfig.servers.unityMCP = new Newtonsoft.Json.Linq.JObject(); + } existingConfig.servers.unityMCP.args = JsonConvert.DeserializeObject( JsonConvert.SerializeObject(unityMCPConfig.args) @@ -1071,6 +1079,14 @@ namespace UnityMcpBridge.Editor.Windows } else { + if (existingConfig.mcpServers == null) + { + existingConfig.mcpServers = new Newtonsoft.Json.Linq.JObject(); + } + if (existingConfig.mcpServers.unityMCP == null) + { + existingConfig.mcpServers.unityMCP = new Newtonsoft.Json.Linq.JObject(); + } existingConfig.mcpServers.unityMCP.args = JsonConvert.DeserializeObject( JsonConvert.SerializeObject(unityMCPConfig.args) @@ -1617,7 +1633,8 @@ namespace UnityMcpBridge.Editor.Windows UnityEngine.Debug.Log($"Successfully removed MCP server: {serverName}"); break; } - else if (!stderr.Contains("No MCP server found")) + else if (!string.IsNullOrEmpty(stderr) && + !stderr.Contains("No MCP server found", StringComparison.OrdinalIgnoreCase)) { // If it's not a "not found" error, log it and stop trying UnityEngine.Debug.LogWarning($"Error removing {serverName}: {stderr}"); diff --git a/UnityMcpBridge/package.json b/UnityMcpBridge/package.json index ba4add4..445f448 100644 --- a/UnityMcpBridge/package.json +++ b/UnityMcpBridge/package.json @@ -1,6 +1,6 @@ { "name": "com.coplaydev.unity-mcp", - "version": "2.0.2", + "version": "2.1.0", "displayName": "Unity MCP Bridge", "description": "A bridge that manages and communicates with the sister application, Unity MCP Server, which allows for communications with MCP Clients like Claude Desktop or Cursor.", "unity": "2020.3", diff --git a/mcp_source.py b/mcp_source.py index 15f2ff4..535dbae 100755 --- a/mcp_source.py +++ b/mcp_source.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """ Generic helper to switch the Unity MCP package source in a Unity project's -Packages/manifest.json without embedding any personal paths. +Packages/manifest.json. This is useful for switching between upstream and local repos while working on the MCP. Usage: python mcp_source.py [--manifest /abs/path/to/manifest.json] [--repo /abs/path/to/unity-mcp] [--choice 1|2|3]