diff --git a/CursorHelp.md b/CursorHelp.md new file mode 100644 index 00000000..0b8d6f01 --- /dev/null +++ b/CursorHelp.md @@ -0,0 +1,85 @@ +### Cursor/VSCode/Windsurf: UV path issue on Windows (diagnosis and fix) + +#### The issue +- Some Windows machines have multiple `uv.exe` locations. Our auto-config sometimes picked a less stable path, causing the MCP client to fail to launch the Unity MCP Server or for the path to be auto-rewritten on repaint/restart. + +#### Typical symptoms +- Cursor shows the UnityMCP server but never connects or reports it “can’t start.” +- Your `%USERPROFILE%\\.cursor\\mcp.json` flips back to a different `command` path when Unity or the Unity MCP window refreshes. + +#### Real-world example +- Wrong/fragile path (auto-picked): + - `C:\Users\mrken.local\bin\uv.exe` (malformed, not standard) + - `C:\Users\mrken\AppData\Local\Microsoft\WinGet\Packages\astral-sh.uv_Microsoft.Winget.Source_8wekyb3d8bbwe\uv.exe` +- Correct/stable path (works with Cursor): + - `C:\Users\mrken\AppData\Local\Microsoft\WinGet\Links\uv.exe` + +#### Quick fix (recommended) +1) In Unity: `Window > Unity MCP` → select your MCP client (Cursor or Windsurf) +2) If you see “uv Not Found,” click “Choose UV Install Location” and browse to: + - `C:\Users\\AppData\Local\Microsoft\WinGet\Links\uv.exe` +3) If uv is already found but wrong, still click “Choose UV Install Location” and select the `Links\uv.exe` path above. This saves a persistent override. +4) Click “Auto Configure” (or re-open the client) and restart Cursor. + +This sets an override stored in the Editor (key: `UnityMCP.UvPath`) so UnityMCP won’t auto-rewrite the config back to a different `uv.exe` later. + +#### Verify the fix +- Confirm global Cursor config is at: `%USERPROFILE%\\.cursor\\mcp.json` +- You should see something like: + +```json +{ + "mcpServers": { + "unityMCP": { + "command": "C:\\Users\\YOU\\AppData\\Local\\Microsoft\\WinGet\\Links\\uv.exe", + "args": [ + "--directory", + "C:\\Users\\YOU\\AppData\\Local\\Programs\\UnityMCP\\UnityMcpServer\\src", + "run", + "server.py" + ] + } + } +} +``` + +- Manually run the same command in PowerShell to confirm it launches: + +```powershell +"C:\Users\YOU\AppData\Local\Microsoft\WinGet\Links\uv.exe" --directory "C:\Users\YOU\AppData\Local\Programs\UnityMCP\UnityMcpServer\src" run server.py +``` + +If that runs without error, restart Cursor and it should connect. + +#### Why this happens +- On Windows, multiple `uv.exe` can exist (WinGet Packages path, a WinGet Links shim, Python Scripts, etc.). The Links shim is the most stable target for GUI apps to launch. +- Prior versions of the auto-config could pick the first found path and re-write config on refresh. Choosing a path via the MCP window pins a known‑good absolute path and prevents auto-rewrites. + +#### Extra notes +- Restart Cursor after changing `mcp.json`; it doesn’t always hot-reload that file. +- If you also have a project-scoped `.cursor\\mcp.json` in your Unity project folder, that file overrides the global one. + + +### Why pin the WinGet Links shim (and not the Packages path) + +- Windows often has multiple `uv.exe` installs and GUI clients (Cursor/Windsurf/VSCode) may launch with a reduced `PATH`. Using an absolute path is safer than `"command": "uv"`. +- WinGet publishes stable launch shims in these locations: + - User scope: `%LOCALAPPDATA%\Microsoft\WinGet\Links\uv.exe` + - Machine scope: `C:\Program Files\WinGet\Links\uv.exe` + These shims survive upgrades and are intended as the portable entrypoints. See the WinGet notes: [discussion](https://github.com/microsoft/winget-pkgs/discussions/184459) • [how to find installs](https://superuser.com/questions/1739292/how-to-know-where-winget-installed-a-program) +- The `Packages` root is where payloads live and can change across updates, so avoid pointing your config at it. + +Recommended practice + +- Prefer the WinGet Links shim paths above. If present, select one via “Choose UV Install Location”. +- If the unity window keeps rewriting to a different `uv.exe`, pick the Links shim again; Unity MCP saves a pinned override and will stop auto-rewrites. +- If neither Links path exists, a reasonable fallback is `~/.local/bin/uv.exe` (uv tools bin) or a Scoop shim, but Links is preferred for stability. + +References + +- WinGet portable Links: [GitHub discussion](https://github.com/microsoft/winget-pkgs/discussions/184459) +- WinGet install locations: [Super User](https://superuser.com/questions/1739292/how-to-know-where-winget-installed-a-program) +- GUI client PATH caveats (Cursor): [Cursor community thread](https://forum.cursor.com/t/mcp-feature-client-closed-fix/54651?page=4) +- uv tools install location (`~/.local/bin`): [Astral docs](https://docs.astral.sh/uv/concepts/tools/) + + diff --git a/UnityMcpBridge/Editor/Data/McpClients.cs b/UnityMcpBridge/Editor/Data/McpClients.cs index 9d9cbae5..e8c6fadf 100644 --- a/UnityMcpBridge/Editor/Data/McpClients.cs +++ b/UnityMcpBridge/Editor/Data/McpClients.cs @@ -71,8 +71,7 @@ public class McpClients ), linuxConfigPath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), - "Library", - "Application Support", + ".config", "Claude", "claude_desktop_config.json" ), @@ -91,8 +90,7 @@ public class McpClients ), linuxConfigPath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), - "Library", - "Application Support", + ".config", "Code", "User", "mcp.json" diff --git a/UnityMcpBridge/Editor/Helpers/ServerInstaller.cs b/UnityMcpBridge/Editor/Helpers/ServerInstaller.cs index a2c28fe5..aa02e7ab 100644 --- a/UnityMcpBridge/Editor/Helpers/ServerInstaller.cs +++ b/UnityMcpBridge/Editor/Helpers/ServerInstaller.cs @@ -88,7 +88,7 @@ private static string GetSaveLocation() // Use Application Support for a stable, user-writable location return Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), - "UnityMCP" + RootFolder ); } throw new Exception("Unsupported operating system."); @@ -126,6 +126,7 @@ private static bool TryGetEmbeddedServerSource(out string srcPath) return ServerPathResolver.TryFindEmbeddedServerSource(out srcPath); } + private static readonly string[] _skipDirs = { ".venv", "__pycache__", ".pytest_cache", ".mypy_cache", ".git" }; private static void CopyDirectoryRecursive(string sourceDir, string destinationDir) { Directory.CreateDirectory(destinationDir); @@ -140,8 +141,15 @@ private static void CopyDirectoryRecursive(string sourceDir, string destinationD foreach (string dirPath in Directory.GetDirectories(sourceDir)) { string dirName = Path.GetFileName(dirPath); + foreach (var skip in _skipDirs) + { + if (dirName.Equals(skip, StringComparison.OrdinalIgnoreCase)) + goto NextDir; + } + try { if ((File.GetAttributes(dirPath) & FileAttributes.ReparsePoint) != 0) continue; } catch { } string destSubDir = Path.Combine(destinationDir, dirName); CopyDirectoryRecursive(dirPath, destSubDir); + NextDir: ; } } @@ -270,7 +278,6 @@ internal static string FindUvPath() 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 @@ -301,10 +308,9 @@ internal static string FindUvPath() candidates = new[] { // Preferred: WinGet Links shims (stable entrypoints) + // Per-user shim (LOCALAPPDATA) → machine-wide shim (Program Files\WinGet\Links) 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"), diff --git a/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs b/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs index 234a3a09..9e42d7ff 100644 --- a/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs +++ b/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs @@ -630,15 +630,29 @@ private static bool IsCursorConfigured(string pythonDir) if (unity == null) return false; var args = unity.args; if (args == null) return false; - foreach (var a in args) + // Prefer exact extraction of the --directory value and compare normalized paths + string[] strArgs = ((System.Collections.Generic.IEnumerable)args) + .Select(x => x?.ToString() ?? string.Empty) + .ToArray(); + string dir = ExtractDirectoryArg(strArgs); + if (string.IsNullOrEmpty(dir)) return false; + return PathsEqual(dir, pythonDir); + } + catch { return false; } + } + + private static bool PathsEqual(string a, string b) + { + if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(b)) return false; + try + { + string na = System.IO.Path.GetFullPath(a.Trim()); + string nb = System.IO.Path.GetFullPath(b.Trim()); + if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) { - string s = (string)a; - if (!string.IsNullOrEmpty(s) && s.Contains(pythonDir, StringComparison.Ordinal)) - { - return true; - } + return string.Equals(na, nb, StringComparison.OrdinalIgnoreCase); } - return false; + return string.Equals(na, nb, StringComparison.Ordinal); } catch { return false; } } @@ -883,7 +897,7 @@ private void DrawClientConfigurationCompact(McpClient mcpClient) unityMCP = new { command = uvPath, - args = new[] { "--directory", pythonDir, "run", "server.py" } + args = new[] { "run", "--directory", pythonDir, "server.py" } } } } @@ -931,24 +945,65 @@ private void ToggleUnityBridge() Repaint(); } - private string WriteToConfig(string pythonDir, string configPath, McpClient mcpClient = null) + private static bool IsValidUv(string path) + { + return !string.IsNullOrEmpty(path) + && System.IO.Path.IsPathRooted(path) + && System.IO.File.Exists(path); + } + + private static bool ValidateUvBinarySafe(string path) + { + try + { + if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) return false; + var psi = new System.Diagnostics.ProcessStartInfo + { + FileName = path, + Arguments = "--version", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + using var p = System.Diagnostics.Process.Start(psi); + if (p == null) return false; + if (!p.WaitForExit(3000)) { try { p.Kill(); } catch { } return false; } + if (p.ExitCode != 0) return false; + string output = p.StandardOutput.ReadToEnd().Trim(); + return output.StartsWith("uv "); + } + catch { return false; } + } + + private static string ExtractDirectoryArg(string[] args) + { + if (args == null) return null; + for (int i = 0; i < args.Length - 1; i++) + { + if (string.Equals(args[i], "--directory", StringComparison.OrdinalIgnoreCase)) + { + return args[i + 1]; + } + } + return null; + } + + private static bool ArgsEqual(string[] a, string[] b) + { + if (a == null || b == null) return a == b; + if (a.Length != b.Length) return false; + for (int i = 0; i < a.Length; i++) + { + if (!string.Equals(a[i], b[i], StringComparison.Ordinal)) return false; + } + return true; + } + + private string WriteToConfig(string pythonDir, string configPath, McpClient mcpClient = null) { - string uvPath = FindUvPath(); - if (uvPath == null) - { - return "UV package manager not found. Please install UV first."; - } - - // Create configuration object for unityMCP - McpConfigServer unityMCPConfig = new() - { - command = uvPath, - args = new[] { "--directory", pythonDir, "run", "server.py" }, - }; - if (mcpClient?.mcpType == McpTypes.VSCode) - { - unityMCPConfig.type = "stdio"; - } + // 0) Respect explicit lock (hidden pref or UI toggle) + try { if (UnityEditor.EditorPrefs.GetBool("UnityMCP.LockCursorConfig", false)) return "Skipped (locked)"; } catch { } JsonSerializerSettings jsonSettings = new() { Formatting = Formatting.Indented }; @@ -989,123 +1044,88 @@ private string WriteToConfig(string pythonDir, string configPath, McpClient mcpC existingConfig = new Newtonsoft.Json.Linq.JObject(); } - // Handle different client types with a switch statement - //Comments: Interestingly, VSCode has mcp.servers.unityMCP while others have mcpServers.unityMCP, which is why we need to prevent this - switch (mcpClient?.mcpType) - { - case McpTypes.VSCode: - // VSCode-specific configuration (top-level "servers") - if (existingConfig.servers == null) - { - existingConfig.servers = new Newtonsoft.Json.Linq.JObject(); - } + // Determine existing entry references (command/args) + string existingCommand = null; + string[] existingArgs = null; + bool isVSCode = (mcpClient?.mcpType == McpTypes.VSCode); + try + { + if (isVSCode) + { + existingCommand = existingConfig?.servers?.unityMCP?.command?.ToString(); + existingArgs = existingConfig?.servers?.unityMCP?.args?.ToObject(); + } + else + { + existingCommand = existingConfig?.mcpServers?.unityMCP?.command?.ToString(); + existingArgs = existingConfig?.mcpServers?.unityMCP?.args?.ToObject(); + } + } + catch { } - // Add/update UnityMCP server in VSCode mcp.json - existingConfig.servers.unityMCP = - JsonConvert.DeserializeObject( - JsonConvert.SerializeObject(unityMCPConfig) - ); - break; + // 1) Start from existing, only fill gaps + string uvPath = (ValidateUvBinarySafe(existingCommand) ? existingCommand : FindUvPath()); + if (uvPath == null) return "UV package manager not found. Please install UV first."; - default: - // Standard MCP configuration (Claude Desktop, Cursor, etc.) - // Ensure mcpServers object exists - if (existingConfig.mcpServers == null) - { - existingConfig.mcpServers = new Newtonsoft.Json.Linq.JObject(); - } + string serverSrc = ExtractDirectoryArg(existingArgs); + bool serverValid = !string.IsNullOrEmpty(serverSrc) + && System.IO.File.Exists(System.IO.Path.Combine(serverSrc, "server.py")); + if (!serverValid) + { + serverSrc = ResolveServerSrc(); + } - // Add/update UnityMCP server in standard MCP settings - existingConfig.mcpServers.unityMCP = - JsonConvert.DeserializeObject( - JsonConvert.SerializeObject(unityMCPConfig) - ); - break; - } + // Hard-block PackageCache on Windows unless dev override is set + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + && serverSrc.IndexOf(@"\Library\PackageCache\", StringComparison.OrdinalIgnoreCase) >= 0 + && !UnityEditor.EditorPrefs.GetBool("UnityMCP.UseEmbeddedServer", false)) + { + serverSrc = ServerInstaller.GetServerPath(); + } - // If config already has a working absolute uv path, avoid rewriting it on refresh - try - { - if (mcpClient?.mcpType != McpTypes.ClaudeCode) - { - // Inspect existing command for stability (Windows absolute path that exists) - string existingCommand = null; - if (mcpClient?.mcpType == McpTypes.VSCode) - { - existingCommand = existingConfig?.servers?.unityMCP?.command?.ToString(); - } - else - { - existingCommand = existingConfig?.mcpServers?.unityMCP?.command?.ToString(); - } + // 2) Canonical args order + var newArgs = new[] { "run", "--directory", serverSrc, "server.py" }; - if (!string.IsNullOrEmpty(existingCommand)) - { - bool keep = false; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // Consider absolute, existing paths as stable; prefer WinGet Links - if (Path.IsPathRooted(existingCommand) && File.Exists(existingCommand)) - { - keep = true; - } - } - else - { - // On Unix, keep absolute existing path as well - if (Path.IsPathRooted(existingCommand) && File.Exists(existingCommand)) - { - keep = true; - } - } + // 3) Only write if changed + bool changed = !string.Equals(existingCommand, uvPath, StringComparison.Ordinal) + || !ArgsEqual(existingArgs, newArgs); + if (!changed) + { + return "Configured successfully"; // nothing to do + } - if (keep) - { - // 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) - ); - } - 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) - ); - } - string mergedKeep = JsonConvert.SerializeObject(existingConfig, jsonSettings); - File.WriteAllText(configPath, mergedKeep); - return "Configured successfully"; - } - } - } - } - catch { /* fall back to normal write */ } + // 4) Ensure containers exist and write back minimal changes + if (isVSCode) + { + 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.command = uvPath; + existingConfig.servers.unityMCP.args = Newtonsoft.Json.Linq.JArray.FromObject(newArgs); + existingConfig.servers.unityMCP.type = "stdio"; + } + 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.command = uvPath; + existingConfig.mcpServers.unityMCP.args = Newtonsoft.Json.Linq.JArray.FromObject(newArgs); + } - // Write the merged configuration back to file - string mergedJson = JsonConvert.SerializeObject(existingConfig, jsonSettings); - File.WriteAllText(configPath, mergedJson); + string mergedJson = JsonConvert.SerializeObject(existingConfig, jsonSettings); + string tmp = configPath + ".tmp"; + System.IO.File.WriteAllText(tmp, mergedJson, System.Text.Encoding.UTF8); + if (System.IO.File.Exists(configPath)) + System.IO.File.Replace(tmp, configPath, null); + else + System.IO.File.Move(tmp, configPath); + try + { + if (IsValidUv(uvPath)) UnityEditor.EditorPrefs.SetString("UnityMCP.UvPath", uvPath); + UnityEditor.EditorPrefs.SetString("UnityMCP.ServerSrc", serverSrc); + } + catch { } - return "Configured successfully"; + return "Configured successfully"; } private void ShowManualConfigurationInstructions( @@ -1147,7 +1167,7 @@ private void ShowManualInstructionsWindow(string configPath, McpClient mcpClient unityMCP = new { command = uvPathManual, - args = new[] { "--directory", pythonDir, "run", "server.py" }, + args = new[] { "run", "--directory", pythonDir, "server.py" }, type = "stdio" } } @@ -1171,7 +1191,7 @@ private void ShowManualInstructionsWindow(string configPath, McpClient mcpClient unityMCP = new McpConfigServer { command = uvPath, - args = new[] { "--directory", pythonDir, "run", "server.py" }, + args = new[] { "run", "--directory", pythonDir, "server.py" }, }, }, }; @@ -1182,9 +1202,38 @@ private void ShowManualInstructionsWindow(string configPath, McpClient mcpClient ManualConfigEditorWindow.ShowWindow(configPath, manualConfigJson, mcpClient); } - private string FindPackagePythonDirectory() + private static string ResolveServerSrc() + { + try + { + string remembered = UnityEditor.EditorPrefs.GetString("UnityMCP.ServerSrc", string.Empty); + if (!string.IsNullOrEmpty(remembered) && File.Exists(Path.Combine(remembered, "server.py"))) + { + return remembered; + } + + ServerInstaller.EnsureServerInstalled(); + string installed = ServerInstaller.GetServerPath(); + if (File.Exists(Path.Combine(installed, "server.py"))) + { + return installed; + } + + bool useEmbedded = UnityEditor.EditorPrefs.GetBool("UnityMCP.UseEmbeddedServer", false); + if (useEmbedded && ServerPathResolver.TryFindEmbeddedServerSource(out string embedded) + && File.Exists(Path.Combine(embedded, "server.py"))) + { + return embedded; + } + + return installed; + } + catch { return ServerInstaller.GetServerPath(); } + } + + private string FindPackagePythonDirectory() { - string pythonDir = ServerInstaller.GetServerPath(); + string pythonDir = ResolveServerSrc(); try { @@ -1211,17 +1260,25 @@ private string FindPackagePythonDirectory() } } - // Resolve via shared helper (handles local registry and older fallback) - if (ServerPathResolver.TryFindEmbeddedServerSource(out string embedded)) - { - return embedded; - } + // Resolve via shared helper (handles local registry and older fallback) only if dev override on + if (UnityEditor.EditorPrefs.GetBool("UnityMCP.UseEmbeddedServer", false)) + { + if (ServerPathResolver.TryFindEmbeddedServerSource(out string embedded)) + { + return embedded; + } + } - // If still not found, return the placeholder path - if (debugLogsEnabled) - { - UnityEngine.Debug.LogWarning("Could not find Python directory, using placeholder path"); - } + // Log only if the resolved path does not actually contain server.py + if (debugLogsEnabled) + { + bool hasServer = false; + try { hasServer = File.Exists(Path.Combine(pythonDir, "server.py")); } catch { } + if (!hasServer) + { + UnityEngine.Debug.LogWarning("Could not find Python directory with server.py; falling back to installed path"); + } + } } catch (Exception e) { @@ -1354,7 +1411,7 @@ McpClient mcpClient unityMCP = new McpConfigServer { command = uvPath, - args = new[] { "--directory", pythonDir, "run", "server.py" }, + args = new[] { "run", "--directory", pythonDir, "server.py" }, }, }, }; diff --git a/UnityMcpBridge/UnityMcpServer~/src/uv.lock b/UnityMcpBridge/UnityMcpServer~/src/uv.lock index bc3e54ca..de0cd446 100644 --- a/UnityMcpBridge/UnityMcpServer~/src/uv.lock +++ b/UnityMcpBridge/UnityMcpServer~/src/uv.lock @@ -1,6 +1,6 @@ version = 1 revision = 1 -requires-python = ">=3.12" +requires-python = ">=3.10" [[package]] name = "annotated-types" @@ -16,6 +16,7 @@ name = "anyio" version = "4.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "idna" }, { name = "sniffio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, @@ -55,6 +56,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674 }, +] + [[package]] name = "h11" version = "0.14.0" @@ -179,6 +192,33 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 } wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/bc/fed5f74b5d802cf9a03e83f60f18864e90e3aed7223adaca5ffb7a8d8d64/pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa", size = 1895938 }, + { url = "https://files.pythonhosted.org/packages/71/2a/185aff24ce844e39abb8dd680f4e959f0006944f4a8a0ea372d9f9ae2e53/pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c", size = 1815684 }, + { url = "https://files.pythonhosted.org/packages/c3/43/fafabd3d94d159d4f1ed62e383e264f146a17dd4d48453319fd782e7979e/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a", size = 1829169 }, + { url = "https://files.pythonhosted.org/packages/a2/d1/f2dfe1a2a637ce6800b799aa086d079998959f6f1215eb4497966efd2274/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5", size = 1867227 }, + { url = "https://files.pythonhosted.org/packages/7d/39/e06fcbcc1c785daa3160ccf6c1c38fea31f5754b756e34b65f74e99780b5/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c", size = 2037695 }, + { url = "https://files.pythonhosted.org/packages/7a/67/61291ee98e07f0650eb756d44998214231f50751ba7e13f4f325d95249ab/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7", size = 2741662 }, + { url = "https://files.pythonhosted.org/packages/32/90/3b15e31b88ca39e9e626630b4c4a1f5a0dfd09076366f4219429e6786076/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a", size = 1993370 }, + { url = "https://files.pythonhosted.org/packages/ff/83/c06d333ee3a67e2e13e07794995c1535565132940715931c1c43bfc85b11/pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236", size = 1996813 }, + { url = "https://files.pythonhosted.org/packages/7c/f7/89be1c8deb6e22618a74f0ca0d933fdcb8baa254753b26b25ad3acff8f74/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962", size = 2005287 }, + { url = "https://files.pythonhosted.org/packages/b7/7d/8eb3e23206c00ef7feee17b83a4ffa0a623eb1a9d382e56e4aa46fd15ff2/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9", size = 2128414 }, + { url = "https://files.pythonhosted.org/packages/4e/99/fe80f3ff8dd71a3ea15763878d464476e6cb0a2db95ff1c5c554133b6b83/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af", size = 2155301 }, + { url = "https://files.pythonhosted.org/packages/2b/a3/e50460b9a5789ca1451b70d4f52546fa9e2b420ba3bfa6100105c0559238/pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4", size = 1816685 }, + { url = "https://files.pythonhosted.org/packages/57/4c/a8838731cb0f2c2a39d3535376466de6049034d7b239c0202a64aaa05533/pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31", size = 1982876 }, + { url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421 }, + { url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998 }, + { url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167 }, + { url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071 }, + { url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244 }, + { url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470 }, + { url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291 }, + { url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613 }, + { url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355 }, + { url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661 }, + { url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261 }, + { url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361 }, + { url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484 }, + { url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102 }, { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 }, { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 }, { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 }, @@ -207,6 +247,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387 }, { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453 }, { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 }, + { url = "https://files.pythonhosted.org/packages/46/72/af70981a341500419e67d5cb45abe552a7c74b66326ac8877588488da1ac/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e", size = 1891159 }, + { url = "https://files.pythonhosted.org/packages/ad/3d/c5913cccdef93e0a6a95c2d057d2c2cba347815c845cda79ddd3c0f5e17d/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8", size = 1768331 }, + { url = "https://files.pythonhosted.org/packages/f6/f0/a3ae8fbee269e4934f14e2e0e00928f9346c5943174f2811193113e58252/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3", size = 1822467 }, + { url = "https://files.pythonhosted.org/packages/d7/7a/7bbf241a04e9f9ea24cd5874354a83526d639b02674648af3f350554276c/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f", size = 1979797 }, + { url = "https://files.pythonhosted.org/packages/4f/5f/4784c6107731f89e0005a92ecb8a2efeafdb55eb992b8e9d0a2be5199335/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133", size = 1987839 }, + { url = "https://files.pythonhosted.org/packages/6d/a7/61246562b651dff00de86a5f01b6e4befb518df314c54dec187a78d81c84/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc", size = 1998861 }, + { url = "https://files.pythonhosted.org/packages/86/aa/837821ecf0c022bbb74ca132e117c358321e72e7f9702d1b6a03758545e2/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50", size = 2116582 }, + { url = "https://files.pythonhosted.org/packages/81/b0/5e74656e95623cbaa0a6278d16cf15e10a51f6002e3ec126541e95c29ea3/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9", size = 2151985 }, + { url = "https://files.pythonhosted.org/packages/63/37/3e32eeb2a451fddaa3898e2163746b0cffbbdbb4740d38372db0490d67f3/pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151", size = 2004715 }, ] [[package]] @@ -247,6 +296,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 } wheels = [ @@ -342,6 +392,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568 } wheels = [ diff --git a/UnityMcpBridge/package.json b/UnityMcpBridge/package.json index 445f448b..a5d06510 100644 --- a/UnityMcpBridge/package.json +++ b/UnityMcpBridge/package.json @@ -1,6 +1,6 @@ { "name": "com.coplaydev.unity-mcp", - "version": "2.1.0", + "version": "2.1.1", "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/claude-chunk.md b/claude-chunk.md new file mode 100644 index 00000000..5857bf1f --- /dev/null +++ b/claude-chunk.md @@ -0,0 +1,51 @@ +### macOS: Claude CLI fails to start (dyld ICU library not loaded) + +- Symptoms + - Unity MCP error: “Failed to start Claude CLI. dyld: Library not loaded: /usr/local/opt/icu4c/lib/libicui18n.71.dylib …” + - Running `claude` in Terminal fails with missing `libicui18n.xx.dylib`. + +- Cause + - Homebrew Node (or the `claude` binary) was linked against an ICU version that’s no longer installed; dyld can’t find that dylib. + +- Fix options (pick one) + - Reinstall Homebrew Node (relinks to current ICU), then reinstall CLI: + ```bash + brew update + brew reinstall node + npm uninstall -g @anthropic-ai/claude-code + npm install -g @anthropic-ai/claude-code + ``` + - Use NVM Node (avoids Homebrew ICU churn): + ```bash + nvm install --lts + nvm use --lts + npm install -g @anthropic-ai/claude-code + # Unity MCP → Claude Code → Choose Claude Location → ~/.nvm/versions/node//bin/claude + ``` + - Use the native installer (puts claude in a stable path): + ```bash + # macOS/Linux + curl -fsSL https://claude.ai/install.sh | bash + # Unity MCP → Claude Code → Choose Claude Location → /opt/homebrew/bin/claude or ~/.local/bin/claude + ``` + +- After fixing + - In Unity MCP (Claude Code), click “Choose Claude Location” and select the working `claude` binary, then Register again. + +- More details + - See: Troubleshooting Unity MCP and Claude Code + +--- + +### FAQ (Claude Code) + +- Q: Unity can’t find `claude` even though Terminal can. + - A: macOS apps launched from Finder/Hub don’t inherit your shell PATH. In the Unity MCP window, click “Choose Claude Location” and select the absolute path (e.g., `/opt/homebrew/bin/claude` or `~/.nvm/versions/node//bin/claude`). + +- Q: I installed via NVM; where is `claude`? + - A: Typically `~/.nvm/versions/node//bin/claude`. Our UI also scans NVM versions and you can browse to it via “Choose Claude Location”. + +- Q: The Register button says “Claude Not Found”. + - A: Install the CLI or set the path. Click the orange “[HELP]” link in the Unity MCP window for step‑by‑step install instructions, then choose the binary location. + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..10fbb385 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "unity-mcp", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/package.json @@ -0,0 +1 @@ +{}