Merge pull request #210 from dsarno/fix/config-stability
Config stability: preserve user configs, prefer installed server, safer uv resolution, XDG paths (Linux)main
commit
cf8f5d4dd7
|
|
@ -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\<YOU>\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/)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -71,8 +71,7 @@ namespace UnityMcpBridge.Editor.Data
|
||||||
),
|
),
|
||||||
linuxConfigPath = Path.Combine(
|
linuxConfigPath = Path.Combine(
|
||||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||||
"Library",
|
".config",
|
||||||
"Application Support",
|
|
||||||
"Claude",
|
"Claude",
|
||||||
"claude_desktop_config.json"
|
"claude_desktop_config.json"
|
||||||
),
|
),
|
||||||
|
|
@ -91,8 +90,7 @@ namespace UnityMcpBridge.Editor.Data
|
||||||
),
|
),
|
||||||
linuxConfigPath = Path.Combine(
|
linuxConfigPath = Path.Combine(
|
||||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||||
"Library",
|
".config",
|
||||||
"Application Support",
|
|
||||||
"Code",
|
"Code",
|
||||||
"User",
|
"User",
|
||||||
"mcp.json"
|
"mcp.json"
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ namespace UnityMcpBridge.Editor.Helpers
|
||||||
// Use Application Support for a stable, user-writable location
|
// Use Application Support for a stable, user-writable location
|
||||||
return Path.Combine(
|
return Path.Combine(
|
||||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||||
"UnityMCP"
|
RootFolder
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
throw new Exception("Unsupported operating system.");
|
throw new Exception("Unsupported operating system.");
|
||||||
|
|
@ -126,6 +126,7 @@ namespace UnityMcpBridge.Editor.Helpers
|
||||||
return ServerPathResolver.TryFindEmbeddedServerSource(out 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)
|
private static void CopyDirectoryRecursive(string sourceDir, string destinationDir)
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(destinationDir);
|
Directory.CreateDirectory(destinationDir);
|
||||||
|
|
@ -140,8 +141,15 @@ namespace UnityMcpBridge.Editor.Helpers
|
||||||
foreach (string dirPath in Directory.GetDirectories(sourceDir))
|
foreach (string dirPath in Directory.GetDirectories(sourceDir))
|
||||||
{
|
{
|
||||||
string dirName = Path.GetFileName(dirPath);
|
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);
|
string destSubDir = Path.Combine(destinationDir, dirName);
|
||||||
CopyDirectoryRecursive(dirPath, destSubDir);
|
CopyDirectoryRecursive(dirPath, destSubDir);
|
||||||
|
NextDir: ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -270,7 +278,6 @@ namespace UnityMcpBridge.Editor.Helpers
|
||||||
string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) ?? string.Empty;
|
string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) ?? string.Empty;
|
||||||
string programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) ?? string.Empty;
|
string programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) ?? string.Empty;
|
||||||
string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) ?? 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
|
// Fast path: resolve from PATH first
|
||||||
try
|
try
|
||||||
|
|
@ -301,10 +308,9 @@ namespace UnityMcpBridge.Editor.Helpers
|
||||||
candidates = new[]
|
candidates = new[]
|
||||||
{
|
{
|
||||||
// Preferred: WinGet Links shims (stable entrypoints)
|
// 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(localAppData, "Microsoft", "WinGet", "Links", "uv.exe"),
|
||||||
Path.Combine(programFiles, "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
|
// Common per-user installs
|
||||||
Path.Combine(localAppData, @"Programs\Python\Python313\Scripts\uv.exe"),
|
Path.Combine(localAppData, @"Programs\Python\Python313\Scripts\uv.exe"),
|
||||||
|
|
|
||||||
|
|
@ -630,15 +630,29 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
if (unity == null) return false;
|
if (unity == null) return false;
|
||||||
var args = unity.args;
|
var args = unity.args;
|
||||||
if (args == null) return false;
|
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<object>)args)
|
||||||
string s = (string)a;
|
.Select(x => x?.ToString() ?? string.Empty)
|
||||||
if (!string.IsNullOrEmpty(s) && s.Contains(pythonDir, StringComparison.Ordinal))
|
.ToArray();
|
||||||
{
|
string dir = ExtractDirectoryArg(strArgs);
|
||||||
return true;
|
if (string.IsNullOrEmpty(dir)) return false;
|
||||||
|
return PathsEqual(dir, pythonDir);
|
||||||
}
|
}
|
||||||
|
catch { return false; }
|
||||||
}
|
}
|
||||||
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))
|
||||||
|
{
|
||||||
|
return string.Equals(na, nb, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
return string.Equals(na, nb, StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
catch { return false; }
|
catch { return false; }
|
||||||
}
|
}
|
||||||
|
|
@ -883,7 +897,7 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
unityMCP = new
|
unityMCP = new
|
||||||
{
|
{
|
||||||
command = uvPath,
|
command = uvPath,
|
||||||
args = new[] { "--directory", pythonDir, "run", "server.py" }
|
args = new[] { "run", "--directory", pythonDir, "server.py" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -931,24 +945,65 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
Repaint();
|
Repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string WriteToConfig(string pythonDir, string configPath, McpClient mcpClient = null)
|
private static bool IsValidUv(string path)
|
||||||
{
|
{
|
||||||
string uvPath = FindUvPath();
|
return !string.IsNullOrEmpty(path)
|
||||||
if (uvPath == null)
|
&& System.IO.Path.IsPathRooted(path)
|
||||||
{
|
&& System.IO.File.Exists(path);
|
||||||
return "UV package manager not found. Please install UV first.";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create configuration object for unityMCP
|
private static bool ValidateUvBinarySafe(string path)
|
||||||
McpConfigServer unityMCPConfig = new()
|
|
||||||
{
|
{
|
||||||
command = uvPath,
|
try
|
||||||
args = new[] { "--directory", pythonDir, "run", "server.py" },
|
{
|
||||||
|
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
|
||||||
};
|
};
|
||||||
if (mcpClient?.mcpType == McpTypes.VSCode)
|
using var p = System.Diagnostics.Process.Start(psi);
|
||||||
{
|
if (p == null) return false;
|
||||||
unityMCPConfig.type = "stdio";
|
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)
|
||||||
|
{
|
||||||
|
// 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 };
|
JsonSerializerSettings jsonSettings = new() { Formatting = Formatting.Indented };
|
||||||
|
|
||||||
|
|
@ -989,121 +1044,86 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
existingConfig = new Newtonsoft.Json.Linq.JObject();
|
existingConfig = new Newtonsoft.Json.Linq.JObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle different client types with a switch statement
|
// Determine existing entry references (command/args)
|
||||||
//Comments: Interestingly, VSCode has mcp.servers.unityMCP while others have mcpServers.unityMCP, which is why we need to prevent this
|
string existingCommand = null;
|
||||||
switch (mcpClient?.mcpType)
|
string[] existingArgs = null;
|
||||||
{
|
bool isVSCode = (mcpClient?.mcpType == McpTypes.VSCode);
|
||||||
case McpTypes.VSCode:
|
|
||||||
// VSCode-specific configuration (top-level "servers")
|
|
||||||
if (existingConfig.servers == null)
|
|
||||||
{
|
|
||||||
existingConfig.servers = new Newtonsoft.Json.Linq.JObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add/update UnityMCP server in VSCode mcp.json
|
|
||||||
existingConfig.servers.unityMCP =
|
|
||||||
JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JToken>(
|
|
||||||
JsonConvert.SerializeObject(unityMCPConfig)
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Standard MCP configuration (Claude Desktop, Cursor, etc.)
|
|
||||||
// Ensure mcpServers object exists
|
|
||||||
if (existingConfig.mcpServers == null)
|
|
||||||
{
|
|
||||||
existingConfig.mcpServers = new Newtonsoft.Json.Linq.JObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add/update UnityMCP server in standard MCP settings
|
|
||||||
existingConfig.mcpServers.unityMCP =
|
|
||||||
JsonConvert.DeserializeObject<Newtonsoft.Json.Linq.JToken>(
|
|
||||||
JsonConvert.SerializeObject(unityMCPConfig)
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If config already has a working absolute uv path, avoid rewriting it on refresh
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (mcpClient?.mcpType != McpTypes.ClaudeCode)
|
if (isVSCode)
|
||||||
{
|
|
||||||
// Inspect existing command for stability (Windows absolute path that exists)
|
|
||||||
string existingCommand = null;
|
|
||||||
if (mcpClient?.mcpType == McpTypes.VSCode)
|
|
||||||
{
|
{
|
||||||
existingCommand = existingConfig?.servers?.unityMCP?.command?.ToString();
|
existingCommand = existingConfig?.servers?.unityMCP?.command?.ToString();
|
||||||
|
existingArgs = existingConfig?.servers?.unityMCP?.args?.ToObject<string[]>();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
existingCommand = existingConfig?.mcpServers?.unityMCP?.command?.ToString();
|
existingCommand = existingConfig?.mcpServers?.unityMCP?.command?.ToString();
|
||||||
|
existingArgs = existingConfig?.mcpServers?.unityMCP?.args?.ToObject<string[]>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
// 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.";
|
||||||
|
|
||||||
|
string serverSrc = ExtractDirectoryArg(existingArgs);
|
||||||
|
bool serverValid = !string.IsNullOrEmpty(serverSrc)
|
||||||
|
&& System.IO.File.Exists(System.IO.Path.Combine(serverSrc, "server.py"));
|
||||||
|
if (!serverValid)
|
||||||
|
{
|
||||||
|
serverSrc = ResolveServerSrc();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(existingCommand))
|
// 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))
|
||||||
{
|
{
|
||||||
bool keep = false;
|
serverSrc = ServerInstaller.GetServerPath();
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
||||||
{
|
|
||||||
// Consider absolute, existing paths as stable; prefer WinGet Links
|
|
||||||
if (Path.IsPathRooted(existingCommand) && File.Exists(existingCommand))
|
|
||||||
{
|
|
||||||
keep = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2) Canonical args order
|
||||||
|
var newArgs = new[] { "run", "--directory", serverSrc, "server.py" };
|
||||||
|
|
||||||
|
// 3) Only write if changed
|
||||||
|
bool changed = !string.Equals(existingCommand, uvPath, StringComparison.Ordinal)
|
||||||
|
|| !ArgsEqual(existingArgs, newArgs);
|
||||||
|
if (!changed)
|
||||||
|
{
|
||||||
|
return "Configured successfully"; // nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
else
|
||||||
{
|
{
|
||||||
// On Unix, keep absolute existing path as well
|
if (existingConfig.mcpServers == null) existingConfig.mcpServers = new Newtonsoft.Json.Linq.JObject();
|
||||||
if (Path.IsPathRooted(existingCommand) && File.Exists(existingCommand))
|
if (existingConfig.mcpServers.unityMCP == null) existingConfig.mcpServers.unityMCP = new Newtonsoft.Json.Linq.JObject();
|
||||||
{
|
existingConfig.mcpServers.unityMCP.command = uvPath;
|
||||||
keep = true;
|
existingConfig.mcpServers.unityMCP.args = Newtonsoft.Json.Linq.JArray.FromObject(newArgs);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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<Newtonsoft.Json.Linq.JToken>(
|
|
||||||
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<Newtonsoft.Json.Linq.JToken>(
|
|
||||||
JsonConvert.SerializeObject(unityMCPConfig.args)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
string mergedKeep = JsonConvert.SerializeObject(existingConfig, jsonSettings);
|
|
||||||
File.WriteAllText(configPath, mergedKeep);
|
|
||||||
return "Configured successfully";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch { /* fall back to normal write */ }
|
|
||||||
|
|
||||||
// Write the merged configuration back to file
|
|
||||||
string mergedJson = JsonConvert.SerializeObject(existingConfig, jsonSettings);
|
string mergedJson = JsonConvert.SerializeObject(existingConfig, jsonSettings);
|
||||||
File.WriteAllText(configPath, mergedJson);
|
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";
|
||||||
}
|
}
|
||||||
|
|
@ -1147,7 +1167,7 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
unityMCP = new
|
unityMCP = new
|
||||||
{
|
{
|
||||||
command = uvPathManual,
|
command = uvPathManual,
|
||||||
args = new[] { "--directory", pythonDir, "run", "server.py" },
|
args = new[] { "run", "--directory", pythonDir, "server.py" },
|
||||||
type = "stdio"
|
type = "stdio"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1171,7 +1191,7 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
unityMCP = new McpConfigServer
|
unityMCP = new McpConfigServer
|
||||||
{
|
{
|
||||||
command = uvPath,
|
command = uvPath,
|
||||||
args = new[] { "--directory", pythonDir, "run", "server.py" },
|
args = new[] { "run", "--directory", pythonDir, "server.py" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -1182,9 +1202,38 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
ManualConfigEditorWindow.ShowWindow(configPath, manualConfigJson, mcpClient);
|
ManualConfigEditorWindow.ShowWindow(configPath, manualConfigJson, mcpClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
private string FindPackagePythonDirectory()
|
||||||
{
|
{
|
||||||
string pythonDir = ServerInstaller.GetServerPath();
|
string pythonDir = ResolveServerSrc();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -1211,16 +1260,24 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve via shared helper (handles local registry and older fallback)
|
// 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))
|
if (ServerPathResolver.TryFindEmbeddedServerSource(out string embedded))
|
||||||
{
|
{
|
||||||
return embedded;
|
return embedded;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If still not found, return the placeholder path
|
// Log only if the resolved path does not actually contain server.py
|
||||||
if (debugLogsEnabled)
|
if (debugLogsEnabled)
|
||||||
{
|
{
|
||||||
UnityEngine.Debug.LogWarning("Could not find Python directory, using placeholder path");
|
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)
|
catch (Exception e)
|
||||||
|
|
@ -1354,7 +1411,7 @@ namespace UnityMcpBridge.Editor.Windows
|
||||||
unityMCP = new McpConfigServer
|
unityMCP = new McpConfigServer
|
||||||
{
|
{
|
||||||
command = uvPath,
|
command = uvPath,
|
||||||
args = new[] { "--directory", pythonDir, "run", "server.py" },
|
args = new[] { "run", "--directory", pythonDir, "server.py" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
version = 1
|
version = 1
|
||||||
revision = 1
|
revision = 1
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.10"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "annotated-types"
|
name = "annotated-types"
|
||||||
|
|
@ -16,6 +16,7 @@ name = "anyio"
|
||||||
version = "4.9.0"
|
version = "4.9.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
||||||
{ name = "idna" },
|
{ name = "idna" },
|
||||||
{ name = "sniffio" },
|
{ name = "sniffio" },
|
||||||
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
{ 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 },
|
{ 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]]
|
[[package]]
|
||||||
name = "h11"
|
name = "h11"
|
||||||
version = "0.14.0"
|
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 }
|
sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 }
|
||||||
wheels = [
|
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/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/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 },
|
{ 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/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/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/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]]
|
[[package]]
|
||||||
|
|
@ -247,6 +296,7 @@ source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "markdown-it-py" },
|
{ name = "markdown-it-py" },
|
||||||
{ name = "pygments" },
|
{ 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 }
|
sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 }
|
||||||
wheels = [
|
wheels = [
|
||||||
|
|
@ -342,6 +392,7 @@ source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "click" },
|
{ name = "click" },
|
||||||
{ name = "h11" },
|
{ 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 }
|
sdist = { url = "https://files.pythonhosted.org/packages/4b/4d/938bd85e5bf2edeec766267a5015ad969730bb91e31b44021dfe8b22df6c/uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9", size = 76568 }
|
||||||
wheels = [
|
wheels = [
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "com.coplaydev.unity-mcp",
|
"name": "com.coplaydev.unity-mcp",
|
||||||
"version": "2.1.0",
|
"version": "2.1.1",
|
||||||
"displayName": "Unity MCP Bridge",
|
"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.",
|
"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",
|
"unity": "2020.3",
|
||||||
|
|
|
||||||
|
|
@ -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/<ver>/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/<ver>/bin/claude`).
|
||||||
|
|
||||||
|
- Q: I installed via NVM; where is `claude`?
|
||||||
|
- A: Typically `~/.nvm/versions/node/<ver>/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.
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"name": "unity-mcp",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
||||||
Loading…
Reference in New Issue