diff --git a/MCPForUnity/Editor/Helpers/CodexConfigHelper.cs b/MCPForUnity/Editor/Helpers/CodexConfigHelper.cs
index a4728901..a2a48b2a 100644
--- a/MCPForUnity/Editor/Helpers/CodexConfigHelper.cs
+++ b/MCPForUnity/Editor/Helpers/CodexConfigHelper.cs
@@ -123,24 +123,61 @@ private static TomlTable TryParseToml(string toml)
}
}
+ ///
+ /// Checks if we should use remote uvx (Asset Store install without embedded server)
+ ///
+ private static bool ShouldUseRemoteUvx(string serverSrc)
+ {
+ // If no serverSrc provided, use remote
+ if (string.IsNullOrEmpty(serverSrc))
+ return true;
+
+ // If no embedded server exists, use remote
+ if (!ServerInstaller.HasEmbeddedServer())
+ return true;
+
+ return false;
+ }
+
///
/// Creates a TomlTable for the unityMCP server configuration
///
- /// Path to uv executable
- /// Path to server source directory
+ /// Path to uv executable (can be null for remote uvx)
+ /// Path to server source directory (can be null for remote uvx)
private static TomlTable CreateUnityMcpTable(string uvPath, string serverSrc)
{
var unityMCP = new TomlTable();
- unityMCP["command"] = new TomlString { Value = uvPath };
+ bool useRemote = ShouldUseRemoteUvx(serverSrc);
- var argsArray = new TomlArray();
- argsArray.Add(new TomlString { Value = "run" });
- argsArray.Add(new TomlString { Value = "--directory" });
- argsArray.Add(new TomlString { Value = serverSrc });
- argsArray.Add(new TomlString { Value = "server.py" });
- unityMCP["args"] = argsArray;
+ if (useRemote)
+ {
+ // Asset Store install - use remote uvx
+ string version = AssetPathUtility.GetPackageVersion();
+ string remoteUrl = $"git+https://github.com/CoplayDev/unity-mcp@v{version}#subdirectory=MCPForUnity/UnityMcpServer~/src";
+
+ unityMCP["command"] = new TomlString { Value = "uvx" };
+
+ var argsArray = new TomlArray();
+ argsArray.Add(new TomlString { Value = "--from" });
+ argsArray.Add(new TomlString { Value = remoteUrl });
+ argsArray.Add(new TomlString { Value = "mcp-for-unity" });
+ unityMCP["args"] = argsArray;
+ }
+ else
+ {
+ // Git/embedded install - use local path
+ unityMCP["command"] = new TomlString { Value = uvPath };
+
+ var argsArray = new TomlArray();
+ argsArray.Add(new TomlString { Value = "run" });
+ argsArray.Add(new TomlString { Value = "--directory" });
+ argsArray.Add(new TomlString { Value = serverSrc });
+ argsArray.Add(new TomlString { Value = "server.py" });
+ unityMCP["args"] = argsArray;
+ }
// Add Windows-specific environment configuration, see: https://github.com/CoplayDev/unity-mcp/issues/315
+ // Always include for Windows, even with remote uvx
var platformService = MCPServiceLocator.Platform;
if (platformService.IsWindows())
{
diff --git a/MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs b/MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs
index 5889e4f6..8126b07c 100644
--- a/MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs
+++ b/MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs
@@ -40,19 +40,50 @@ public static JObject ApplyUnityServerToExistingConfig(JObject root, string uvPa
return root;
}
+ ///
+ /// Checks if we should use remote uvx (Asset Store install without embedded server)
+ ///
+ private static bool ShouldUseRemoteUvx(string directory)
+ {
+ // If no directory provided, use remote
+ if (string.IsNullOrEmpty(directory))
+ return true;
+
+ // If no embedded server exists, use remote
+ if (!ServerInstaller.HasEmbeddedServer())
+ return true;
+
+ return false;
+ }
+
///
/// Centralized builder that applies all caveats consistently.
- /// - Sets command/args with provided directory
+ /// - Sets command/args with provided directory OR remote uvx for Asset Store
/// - Ensures env exists
/// - Adds type:"stdio" for VSCode
/// - Adds disabled:false for Windsurf/Kiro only when missing
///
private static void PopulateUnityNode(JObject unity, string uvPath, string directory, McpClient client, bool isVSCode)
{
- unity["command"] = uvPath;
+ // Check if we should use remote uvx (Asset Store without embedded server)
+ bool useRemote = ShouldUseRemoteUvx(directory);
- // For Cursor (non-VSCode) on macOS, prefer a no-spaces symlink path to avoid arg parsing issues in some runners
- string effectiveDir = directory;
+ if (useRemote)
+ {
+ // Asset Store install - use remote uvx
+ string version = AssetPathUtility.GetPackageVersion();
+ string remoteUrl = $"git+https://github.com/CoplayDev/unity-mcp@v{version}#subdirectory=MCPForUnity/UnityMcpServer~/src";
+
+ unity["command"] = "uvx";
+ unity["args"] = JArray.FromObject(new[] { "--from", remoteUrl, "mcp-for-unity" });
+ }
+ else
+ {
+ // Git/embedded install - use local path
+ unity["command"] = uvPath;
+
+ // For Cursor (non-VSCode) on macOS, prefer a no-spaces symlink path to avoid arg parsing issues in some runners
+ string effectiveDir = directory;
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
bool isCursor = !isVSCode && (client == null || client.mcpType != McpTypes.VSCode);
if (isCursor && !string.IsNullOrEmpty(directory))
@@ -92,7 +123,8 @@ private static void PopulateUnityNode(JObject unity, string uvPath, string direc
}
#endif
- unity["args"] = JArray.FromObject(new[] { "run", "--directory", effectiveDir, "server.py" });
+ unity["args"] = JArray.FromObject(new[] { "run", "--directory", effectiveDir, "server.py" });
+ }
if (isVSCode)
{
diff --git a/MCPForUnity/Editor/Helpers/McpConfigurationHelper.cs b/MCPForUnity/Editor/Helpers/McpConfigurationHelper.cs
index 96ad7ec2..98f5ce3e 100644
--- a/MCPForUnity/Editor/Helpers/McpConfigurationHelper.cs
+++ b/MCPForUnity/Editor/Helpers/McpConfigurationHelper.cs
@@ -93,19 +93,27 @@ public static string WriteMcpConfiguration(string pythonDir, string configPath,
}
catch { }
- // 1) Start from existing, only fill gaps (prefer trusted resolver)
- string uvPath = ServerInstaller.FindUvPath();
- // Optionally trust existingCommand if it looks like uv/uv.exe
- try
+ // 1) Check if we should use remote uvx (Asset Store without embedded server)
+ bool useRemote = !ServerInstaller.HasEmbeddedServer() || string.IsNullOrEmpty(pythonDir);
+
+ string uvPath = null;
+ if (!useRemote)
{
- var name = Path.GetFileName((existingCommand ?? string.Empty).Trim()).ToLowerInvariant();
- if ((name == "uv" || name == "uv.exe") && IsValidUvBinary(existingCommand))
+ // Git/embedded install - need UV path
+ uvPath = ServerInstaller.FindUvPath();
+ // Optionally trust existingCommand if it looks like uv/uv.exe
+ try
{
- uvPath = existingCommand;
+ var name = Path.GetFileName((existingCommand ?? string.Empty).Trim()).ToLowerInvariant();
+ if ((name == "uv" || name == "uv.exe") && IsValidUvBinary(existingCommand))
+ {
+ uvPath = existingCommand;
+ }
}
+ catch { }
+ if (uvPath == null) return "UV package manager not found. Please install UV first.";
}
- catch { }
- if (uvPath == null) return "UV package manager not found. Please install UV first.";
+
string serverSrc = ResolveServerDirectory(pythonDir, existingArgs);
// Ensure containers exist and write back configuration
@@ -165,20 +173,28 @@ public static string ConfigureCodexClient(string pythonDir, string configPath, M
CodexConfigHelper.TryParseCodexServer(existingToml, out existingCommand, out existingArgs);
}
- string uvPath = ServerInstaller.FindUvPath();
- try
+ // Check if we should use remote uvx (Asset Store without embedded server)
+ bool useRemote = !ServerInstaller.HasEmbeddedServer() || string.IsNullOrEmpty(pythonDir);
+
+ string uvPath = null;
+ if (!useRemote)
{
- var name = Path.GetFileName((existingCommand ?? string.Empty).Trim()).ToLowerInvariant();
- if ((name == "uv" || name == "uv.exe") && IsValidUvBinary(existingCommand))
+ // Git/embedded install - need UV path
+ uvPath = ServerInstaller.FindUvPath();
+ try
{
- uvPath = existingCommand;
+ var name = Path.GetFileName((existingCommand ?? string.Empty).Trim()).ToLowerInvariant();
+ if ((name == "uv" || name == "uv.exe") && IsValidUvBinary(existingCommand))
+ {
+ uvPath = existingCommand;
+ }
}
- }
- catch { }
+ catch { }
- if (uvPath == null)
- {
- return "UV package manager not found. Please install UV first.";
+ if (uvPath == null)
+ {
+ return "UV package manager not found. Please install UV first.";
+ }
}
string serverSrc = ResolveServerDirectory(pythonDir, existingArgs);
diff --git a/MCPForUnity/Editor/Helpers/ServerInstaller.cs b/MCPForUnity/Editor/Helpers/ServerInstaller.cs
index 10666342..f3064b96 100644
--- a/MCPForUnity/Editor/Helpers/ServerInstaller.cs
+++ b/MCPForUnity/Editor/Helpers/ServerInstaller.cs
@@ -866,103 +866,6 @@ private static bool ValidateUvBinary(string uvPath)
return false;
}
- ///
- /// Download and install server from GitHub release (Asset Store workflow)
- ///
- public static bool DownloadAndInstallServer()
- {
- string packageVersion = AssetPathUtility.GetPackageVersion();
- if (packageVersion == "unknown")
- {
- McpLog.Error("Cannot determine package version for download.");
- return false;
- }
-
- string downloadUrl = $"https://github.com/CoplayDev/unity-mcp/releases/download/v{packageVersion}/mcp-for-unity-server-v{packageVersion}.zip";
- string tempZip = Path.Combine(Path.GetTempPath(), $"mcp-server-v{packageVersion}.zip");
- string destRoot = Path.Combine(GetSaveLocation(), ServerFolder);
-
- try
- {
- EditorUtility.DisplayProgressBar("MCP for Unity", "Downloading server...", 0.3f);
-
- // Download
- using (var client = new WebClient())
- {
- client.DownloadFile(downloadUrl, tempZip);
- }
-
- EditorUtility.DisplayProgressBar("MCP for Unity", "Extracting server...", 0.7f);
-
- // Kill any running UV processes
- string destSrc = Path.Combine(destRoot, "src");
- TryKillUvForPath(destSrc);
-
- // Delete old installation
- if (Directory.Exists(destRoot))
- {
- if (!DeleteDirectoryWithRetry(destRoot, maxRetries: 5, delayMs: 1000))
- {
- McpLog.Warn($"Could not fully delete old server (files may be in use)");
- }
- }
-
- // Extract to temp location first
- string tempExtractDir = Path.Combine(Path.GetTempPath(), $"mcp-server-extract-{Guid.NewGuid()}");
- Directory.CreateDirectory(tempExtractDir);
-
- try
- {
- ZipFile.ExtractToDirectory(tempZip, tempExtractDir);
-
- // The ZIP contains UnityMcpServer~ folder, find it and move its contents
- string extractedServerFolder = Path.Combine(tempExtractDir, "UnityMcpServer~");
- Directory.CreateDirectory(destRoot);
- CopyDirectoryRecursive(extractedServerFolder, destRoot);
- }
- finally
- {
- // Cleanup temp extraction directory
- try
- {
- if (Directory.Exists(tempExtractDir))
- {
- Directory.Delete(tempExtractDir, recursive: true);
- }
- }
- catch (Exception ex)
- {
- McpLog.Warn($"Could not fully delete temp extraction directory: {ex.Message}");
- }
- }
-
- EditorUtility.ClearProgressBar();
- McpLog.Info($"Server v{packageVersion} downloaded and installed successfully!");
- return true;
- }
- catch (Exception ex)
- {
- EditorUtility.ClearProgressBar();
- McpLog.Error($"Failed to download server: {ex.Message}");
- EditorUtility.DisplayDialog(
- "Download Failed",
- $"Could not download server from GitHub.\n\n{ex.Message}\n\nPlease check your internet connection or try again later.",
- "OK"
- );
- return false;
- }
- finally
- {
- try
- {
- if (File.Exists(tempZip)) File.Delete(tempZip);
- }
- catch (Exception ex)
- {
- McpLog.Warn($"Could not delete temp zip file: {ex.Message}");
- }
- }
- }
///
/// Check if the package has an embedded server (Git install vs Asset Store)
diff --git a/MCPForUnity/Editor/Services/ClientConfigurationService.cs b/MCPForUnity/Editor/Services/ClientConfigurationService.cs
index 8a9c4caf..7fd3c263 100644
--- a/MCPForUnity/Editor/Services/ClientConfigurationService.cs
+++ b/MCPForUnity/Editor/Services/ClientConfigurationService.cs
@@ -27,7 +27,9 @@ public void ConfigureClient(McpClient client)
string pythonDir = MCPServiceLocator.Paths.GetMcpServerPath();
- if (pythonDir == null || !File.Exists(Path.Combine(pythonDir, "server.py")))
+ // For Asset Store installs without embedded server, pythonDir can be null (will use remote uvx)
+ bool useRemote = !ServerInstaller.HasEmbeddedServer();
+ if (!useRemote && (pythonDir == null || !File.Exists(Path.Combine(pythonDir, "server.py"))))
{
throw new InvalidOperationException("Server not found. Please use manual configuration or set server path in Advanced Settings.");
}
@@ -235,19 +237,35 @@ public void RegisterClaudeCode()
var pathService = MCPServiceLocator.Paths;
string pythonDir = pathService.GetMcpServerPath();
- if (string.IsNullOrEmpty(pythonDir))
- {
- throw new InvalidOperationException("Cannot register: Python directory not found");
- }
-
string claudePath = pathService.GetClaudeCliPath();
if (string.IsNullOrEmpty(claudePath))
{
throw new InvalidOperationException("Claude CLI not found. Please install Claude Code first.");
}
- string uvPath = pathService.GetUvPath() ?? "uv";
- string args = $"mcp add UnityMCP -- \"{uvPath}\" run --directory \"{pythonDir}\" server.py";
+ // Check if we should use remote uvx (Asset Store without embedded server)
+ bool useRemote = !ServerInstaller.HasEmbeddedServer();
+ string args;
+
+ if (useRemote)
+ {
+ // Asset Store install - use remote uvx
+ string version = AssetPathUtility.GetPackageVersion();
+ string remoteUrl = $"git+https://github.com/CoplayDev/unity-mcp@v{version}#subdirectory=MCPForUnity/UnityMcpServer~/src";
+ args = $"mcp add UnityMCP -- uvx --from {remoteUrl} mcp-for-unity";
+ }
+ else
+ {
+ // Git/embedded install - use local path
+ if (string.IsNullOrEmpty(pythonDir))
+ {
+ throw new InvalidOperationException("Cannot register: Python directory not found");
+ }
+
+ string uvPath = pathService.GetUvPath() ?? "uv";
+ args = $"mcp add UnityMCP -- \"{uvPath}\" run --directory \"{pythonDir}\" server.py";
+ }
+
string projectDir = Path.GetDirectoryName(Application.dataPath);
string pathPrepend = null;
@@ -368,17 +386,28 @@ public string GenerateConfigJson(McpClient client)
{
string pythonDir = MCPServiceLocator.Paths.GetMcpServerPath();
string uvPath = MCPServiceLocator.Paths.GetUvPath();
+ bool useRemote = !ServerInstaller.HasEmbeddedServer();
// Claude Code uses CLI commands, not JSON config
if (client.mcpType == McpTypes.ClaudeCode)
{
- if (string.IsNullOrEmpty(pythonDir) || string.IsNullOrEmpty(uvPath))
+ string registerCommand;
+ if (useRemote)
{
- return "# Error: Configuration not available - check paths in Advanced Settings";
+ // Asset Store install - use remote uvx
+ string version = AssetPathUtility.GetPackageVersion();
+ string remoteUrl = $"git+https://github.com/CoplayDev/unity-mcp@v{version}#subdirectory=MCPForUnity/UnityMcpServer~/src";
+ registerCommand = $"claude mcp add UnityMCP -- uvx --from {remoteUrl} mcp-for-unity";
+ }
+ else
+ {
+ // Git/embedded install - use local path
+ if (string.IsNullOrEmpty(pythonDir) || string.IsNullOrEmpty(uvPath))
+ {
+ return "# Error: Configuration not available - check paths in Advanced Settings";
+ }
+ registerCommand = $"claude mcp add UnityMCP -- \"{uvPath}\" run --directory \"{pythonDir}\" server.py";
}
-
- // Show the actual command that RegisterClaudeCode() uses
- string registerCommand = $"claude mcp add UnityMCP -- \"{uvPath}\" run --directory \"{pythonDir}\" server.py";
return "# Register the MCP server with Claude Code:\n" +
$"{registerCommand}\n\n" +
@@ -388,7 +417,8 @@ public string GenerateConfigJson(McpClient client)
"claude mcp list # Only works when claude is run in the project's directory";
}
- if (string.IsNullOrEmpty(pythonDir) || string.IsNullOrEmpty(uvPath))
+ // For other clients, check if we need paths
+ if (!useRemote && (string.IsNullOrEmpty(pythonDir) || string.IsNullOrEmpty(uvPath)))
return "{ \"error\": \"Configuration not available - check paths in Advanced Settings\" }";
try
diff --git a/MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs b/MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs
index b8fb3c3a..3702d54b 100644
--- a/MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs
+++ b/MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs
@@ -50,7 +50,6 @@ private enum ConnectionProtocol
private Button testConnectionButton;
private VisualElement serverStatusBanner;
private Label serverStatusMessage;
- private Button downloadServerButton;
private Button rebuildServerButton;
// Client UI Elements
@@ -218,7 +217,6 @@ private void CacheUIElements()
testConnectionButton = rootVisualElement.Q