Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8ae595d
Update github-repo-stats.yml
dsarno Oct 10, 2025
83b16ea
Merge branch 'CoplayDev:main' into main
dsarno Oct 18, 2025
67be840
Merge branch 'CoplayDev:main' into main
dsarno Oct 21, 2025
f7ce27d
Merge branch 'CoplayDev:main' into main
dsarno Oct 24, 2025
83b9e47
Merge branch 'CoplayDev:main' into main
dsarno Oct 24, 2025
9dff8f1
Merge branch 'CoplayDev:main' into main
dsarno Oct 24, 2025
00fad91
Merge branch 'main' of https://github.com/CoplayDev/unity-mcp into main
dsarno Oct 27, 2025
74d35d3
Server: refine shutdown logic per bot feedback\n- Parameterize _force…
dsarno Oct 31, 2025
1bb280e
Revert "Server: refine shutdown logic per bot feedback\n- Parameteriz…
dsarno Oct 31, 2025
e6cc955
Merge branch 'CoplayDev:main' into main
dsarno Nov 1, 2025
532b30d
Merge branch 'CoplayDev:main' into main
dsarno Nov 5, 2025
5939d23
Merge branch 'CoplayDev:main' into main
dsarno Nov 6, 2025
9549dd4
Merge branch 'CoplayDev:main' into main
dsarno Nov 11, 2025
6ed7a35
Merge branch 'main' of https://github.com/CoplayDev/unity-mcp
dsarno Nov 25, 2025
aeceec3
Merge branch 'main' of github.com:dsarno/unity-mcp
dsarno Nov 25, 2025
2d7844c
fix: Add missing os and struct imports to port_discovery.py
dsarno Nov 25, 2025
d7cbbfb
Fix: Resolve absolute path for uvx to support Claude Desktop on macOS
dsarno Nov 26, 2025
e613607
fix: Add missing os and struct imports to port_discovery.py
dsarno Nov 25, 2025
d463aa5
Fix: Resolve absolute path for uvx to support Claude Desktop on macOS
dsarno Nov 26, 2025
b264bed
Merge branch 'fix/port-discovery-missing-imports' of https://github.c…
dsarno Nov 26, 2025
2850af8
Merge branch 'main' of https://github.com/CoplayDev/unity-mcp into fi…
dsarno Nov 26, 2025
293bd1f
fix: resolve Windows path issue in uv cache clearing (preserve .exe e…
dsarno Nov 26, 2025
7bfad3b
fix: wrap uvx in cmd /c for Windows clients to ensure PATH resolution
dsarno Nov 26, 2025
42b8eef
Fix: Increase port discovery timeout to 1.0s and fix uv cache cleanin…
dsarno Nov 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/github-repo-stats.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
name: github-repo-stats

on:
schedule:
# schedule:
# Run this once per day, towards the end of the day for keeping the most
# recent data point most meaningful (hours are interpreted in UTC).
- cron: "0 23 * * *"
#- cron: "0 23 * * *"
workflow_dispatch: # Allow for running this manually.

jobs:
Expand Down
26 changes: 21 additions & 5 deletions MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,31 @@ private static void PopulateUnityNode(JObject unity, string uvPath, McpClient cl
// Stdio mode: Use uvx command
var (uvxPath, fromUrl, packageName) = AssetPathUtility.GetUvxCommandParts();

unity["command"] = uvxPath;
var args = new List<string>();

var args = new List<string> { packageName };
if (!string.IsNullOrEmpty(fromUrl))
// Fix for Windows GUI apps (Claude Desktop, Cursor, etc.):
// Wrap in cmd /c to ensure PATH and environment are properly resolved.
if (UnityEngine.Application.platform == UnityEngine.RuntimePlatform.WindowsEditor)
{
unity["command"] = "cmd";
args.Add("/c");

// If uvxPath contains spaces, we might need to ensure it's treated as a command.
// But typically in JSON args, it's just the next argument.
args.Add(uvxPath);
}
Comment on lines +90 to +100
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: cmd /c argument handling will fail with spaces in paths.

The current implementation adds uvxPath as a separate argument after /c, but cmd /c expects the entire command line as a single string following the /c flag. This will fail on Windows when uvxPath contains spaces (e.g., "C:\Program Files\uvx\uvx.exe"), which is very common.

When the MCP client executes this, cmd will interpret only the first part before the space as the command, causing execution to fail.

Apply this diff to properly construct the command line for Windows:

-                // Fix for Windows GUI apps (Claude Desktop, Cursor, etc.): 
-                // Wrap in cmd /c to ensure PATH and environment are properly resolved.
                if (UnityEngine.Application.platform == UnityEngine.RuntimePlatform.WindowsEditor)
                {
-                    unity["command"] = "cmd";
-                    args.Add("/c");
-                    
-                    // If uvxPath contains spaces, we might need to ensure it's treated as a command.
-                    // But typically in JSON args, it's just the next argument.
-                    args.Add(uvxPath);
+                    unity["command"] = "cmd";
+                    
+                    // Build the full command line as a single string after /c
+                    // Quote uvxPath to handle spaces
+                    var cmdLine = $"\"{uvxPath}\"";
+                    if (!string.IsNullOrEmpty(fromUrl))
+                    {
+                        cmdLine += $" --from \"{fromUrl}\"";
+                    }
+                    cmdLine += $" {packageName} --transport stdio";
+                    
+                    args.Add("/c");
+                    args.Add(cmdLine);
+                    
+                    unity["args"] = JArray.FromObject(args.ToArray());
+                    
+                    // Skip the common arg building below for Windows
                }
                else
                {
                    unity["command"] = uvxPath;
-                }
-
-                if (!string.IsNullOrEmpty(fromUrl))
-                {
-                    args.Add("--from");
-                    args.Add(fromUrl);
-                }
-                
-                args.Add(packageName);
-                args.Add("--transport");
-                args.Add("stdio");
+                    
+                    if (!string.IsNullOrEmpty(fromUrl))
+                    {
+                        args.Add("--from");
+                        args.Add(fromUrl);
+                    }
+                    
+                    args.Add(packageName);
+                    args.Add("--transport");
+                    args.Add("stdio");
 
-                unity["args"] = JArray.FromObject(args.ToArray());
+                    unity["args"] = JArray.FromObject(args.ToArray());
+                }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs around lines 90-100, the
Windows branch incorrectly adds uvxPath as a separate arg after "cmd /c", which
breaks when uvxPath contains spaces; instead, construct a single command string
to follow /c by quoting the uvxPath (escaping any existing quotes) and appending
any additional process arguments separated by spaces, then add that single
combined string as the next argument after "/c" (remove the separate uvxPath add
and ensure the final quoted command preserves original argument spacing).

else
{
args.Insert(0, fromUrl);
args.Insert(0, "--from");
unity["command"] = uvxPath;
}

if (!string.IsNullOrEmpty(fromUrl))
{
args.Add("--from");
args.Add(fromUrl);
}

args.Add(packageName);
args.Add("--transport");
args.Add("stdio");

Expand Down
50 changes: 50 additions & 0 deletions MCPForUnity/Editor/Helpers/ExecPath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,56 @@ private static string ResolveClaudeFromNvm(string home)
catch { return null; }
}

// Resolve uvx absolute path. Pref -> env -> common locations -> PATH.
internal static string ResolveUvx()
{
try
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) ?? string.Empty;
string[] candidates =
{
Path.Combine(home, ".local", "bin", "uvx"),
Path.Combine(home, ".cargo", "bin", "uvx"),
"/usr/local/bin/uvx",
"/opt/homebrew/bin/uvx",
"/usr/bin/uvx"
};
foreach (string c in candidates) { if (File.Exists(c)) return c; }

#if UNITY_EDITOR_OSX || UNITY_EDITOR_LINUX
return Which("uvx", "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin");
#else
return null;
#endif
}

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
#if UNITY_EDITOR_WIN
string userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);

string[] candidates =
{
Path.Combine(userProfile, ".cargo", "bin", "uvx.exe"),
Path.Combine(localAppData, "uv", "uvx.exe"),
Path.Combine(userProfile, "uv", "uvx.exe"),
};
foreach (string c in candidates) { if (File.Exists(c)) return c; }

string fromWhere = Where("uvx.exe") ?? Where("uvx");
if (!string.IsNullOrEmpty(fromWhere)) return fromWhere;
#endif
return null;
}
}
catch { }

return null;
}

// Explicitly set the Claude CLI absolute path override in EditorPrefs
internal static void SetClaudeCliPath(string absolutePath)
{
Expand Down
7 changes: 7 additions & 0 deletions MCPForUnity/Editor/Services/PathResolverService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ public string GetUvxPath()
McpLog.Debug("No uvx path override found, falling back to default command");
}

// Auto-discovery of absolute path
string discovered = ExecPath.ResolveUvx();
if (!string.IsNullOrEmpty(discovered))
{
return discovered;
}

return "uvx";
}

Expand Down
28 changes: 26 additions & 2 deletions MCPForUnity/Editor/Services/ServerManagementService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,30 @@ namespace MCPForUnity.Editor.Services
/// </summary>
public class ServerManagementService : IServerManagementService
{
/// <summary>
/// Convert a uvx path to a uv path by replacing "uvx" with "uv" while preserving the extension
/// </summary>
private string ConvertUvxToUv(string uvxPath)
{
if (string.IsNullOrEmpty(uvxPath))
return uvxPath;

// Handle case-insensitive replacement of "uvx" with "uv"
// This works for paths like:
// - /usr/bin/uvx -> /usr/bin/uv
// - C:\path\to\uvx.exe -> C:\path\to\uv.exe
// - uvx -> uv

int lastIndex = uvxPath.LastIndexOf("uvx", StringComparison.OrdinalIgnoreCase);
if (lastIndex >= 0)
{
return uvxPath.Substring(0, lastIndex) + "uv" + uvxPath.Substring(lastIndex + 3);
}

// Fallback: if "uvx" not found, try removing last character (original behavior)
return uvxPath.Length > 0 ? uvxPath.Remove(uvxPath.Length - 1, 1) : uvxPath;
}
Comment on lines +17 to +39
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Questionable fallback behavior could produce invalid paths.

The fallback on line 38 removes the last character when "uvx" is not found in the path. This is problematic:

  • If a user configures an override path directly to "uv" (which doesn't contain "uvx"), the fallback would convert "uv" → "u" or "/usr/bin/uv" → "/usr/bin/uv" (removing 'v').
  • This arbitrary character removal could produce invalid command paths.
  • While the comment indicates this preserves "original behavior," that doesn't make it correct.

Consider handling this case more defensively:

Apply this diff to improve the fallback behavior:

-            // Fallback: if "uvx" not found, try removing last character (original behavior)
-            return uvxPath.Length > 0 ? uvxPath.Remove(uvxPath.Length - 1, 1) : uvxPath;
+            // Fallback: if "uvx" not found, assume the path is already pointing to "uv"
+            McpLog.Warn($"ConvertUvxToUv: 'uvx' not found in path '{uvxPath}', assuming it already points to 'uv' or is a custom override.");
+            return uvxPath;

Alternatively, if the original behavior was intentional for specific edge cases, document why with a comment explaining the use case.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In MCPForUnity/Editor/Services/ServerManagementService.cs around lines 17 to 39,
the fallback currently removes the last character when "uvx" isn’t found which
can produce invalid paths; change the fallback to be defensive by not mutating
the input—return uvxPath unchanged if "uvx" is not found (or, if you want to
preserve a conservative normalization, only strip a trailing 'x' when the path
literally ends with 'x' and you can be sure it's the extra char), and add a
short comment explaining why we no longer remove the last character to avoid
corrupting user-supplied paths.


/// <summary>
/// Clear the local uvx cache for the MCP server package
/// </summary>
Expand All @@ -23,7 +47,7 @@ public bool ClearUvxCache()
try
{
string uvxPath = MCPServiceLocator.Paths.GetUvxPath();
string uvCommand = uvxPath.Remove(uvxPath.Length - 1, 1);
string uvCommand = ConvertUvxToUv(uvxPath);

// Get the package name
string packageName = "mcp-for-unity";
Expand Down Expand Up @@ -65,7 +89,7 @@ private bool ExecuteUvCommand(string uvCommand, string args, out string stdout,
stderr = null;

string uvxPath = MCPServiceLocator.Paths.GetUvxPath();
string uvPath = uvxPath.Remove(uvxPath.Length - 1, 1);
string uvPath = ConvertUvxToUv(uvxPath);

if (!string.Equals(uvCommand, uvPath, StringComparison.OrdinalIgnoreCase))
{
Expand Down
4 changes: 4 additions & 0 deletions MCPForUnity/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ Notes:
- Help: [Fix MCP for Unity with Cursor, VS Code & Windsurf](https://github.com/CoplayDev/unity-mcp/wiki/1.-Fix-Unity-MCP-and-Cursor,-VSCode-&-Windsurf)
- Claude CLI not found:
- Help: [Fix MCP for Unity with Claude Code](https://github.com/CoplayDev/unity-mcp/wiki/2.-Fix-Unity-MCP-and-Claude-Code)
- Claude Desktop "spawn uvx ENOENT" error on macOS:
- Claude Desktop may not inherit your shell's PATH.
- The MCP for Unity plugin attempts to automatically resolve the absolute path to `uvx`.
- If this fails, use the "Choose UV Install Location" button in the MCP for Unity window to select your `uvx` executable (typically `~/.local/bin/uvx`), or manually update your Claude Desktop config to use the absolute path to `uvx`.

---

Expand Down
5 changes: 5 additions & 0 deletions Server/src/services/resources/unity_instances.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
from transport.plugin_hub import PluginHub
from transport.unity_transport import _is_http_transport

try:
pass
except: pass



@mcp_for_unity_resource(
uri="unity://instances",
Expand Down
7 changes: 5 additions & 2 deletions Server/src/transport/legacy/port_discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import glob
import json
import logging
import os
import struct
from datetime import datetime
from pathlib import Path
import socket
Expand All @@ -27,7 +29,7 @@ class PortDiscovery:
"""Handles port discovery from Unity Bridge registry"""
REGISTRY_FILE = "unity-mcp-port.json" # legacy single-project file
DEFAULT_PORT = 6400
CONNECT_TIMEOUT = 0.3 # seconds, keep this snappy during discovery
CONNECT_TIMEOUT = 1.0 # seconds, keep this snappy during discovery

@staticmethod
def get_registry_path() -> Path:
Expand Down Expand Up @@ -100,6 +102,7 @@ def _recv_exact(expected: int) -> bytes | None:
response = _recv_exact(response_length)
if response is None:
return False

return b'"message":"pong"' in response
except Exception as e:
logger.debug(f"Port probe failed for {port}: {e}")
Expand Down Expand Up @@ -306,7 +309,7 @@ def discover_all_unity_instances() -> list[UnityInstanceInfo]:

deduped_instances = [entry[0] for entry in sorted(
instances_by_port.values(), key=lambda item: item[1], reverse=True)]

logger.info(
f"Discovered {len(deduped_instances)} Unity instances (after de-duplication by port)")
return deduped_instances
4 changes: 2 additions & 2 deletions Server/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.