From cb59b08b5166778af5a67887c6dee80c14924196 Mon Sep 17 00:00:00 2001 From: David Sarno Date: Mon, 28 Jul 2025 10:45:07 -0700 Subject: [PATCH 1/4] Add Claude Code support with register/unregister toggle - Added Claude Code as new MCP client type - One-click registration via 'claude mcp add' command - Toggle button to unregister when already configured - Cross-platform support (Windows/macOS/Linux) - Auto-detects configuration in ~/.claude.json --- UnityMcpBridge/Editor/Data/McpClients.cs | 14 + UnityMcpBridge/Editor/Models/McpTypes.cs | 1 + .../Windows/ManualConfigEditorWindow.cs | 35 +- .../Editor/Windows/UnityMcpEditorWindow.cs | 368 ++++++++++++++++-- 4 files changed, 371 insertions(+), 47 deletions(-) diff --git a/UnityMcpBridge/Editor/Data/McpClients.cs b/UnityMcpBridge/Editor/Data/McpClients.cs index 2360ab1b..9b2d7c1b 100644 --- a/UnityMcpBridge/Editor/Data/McpClients.cs +++ b/UnityMcpBridge/Editor/Data/McpClients.cs @@ -63,6 +63,20 @@ public class McpClients mcpType = McpTypes.VSCode, configStatus = "Not Configured", }, + new() + { + name = "Claude Code", + windowsConfigPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + ".claude.json" + ), + linuxConfigPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + ".claude.json" + ), + mcpType = McpTypes.ClaudeCode, + configStatus = "Not Configured", + }, }; // Initialize status enums after construction diff --git a/UnityMcpBridge/Editor/Models/McpTypes.cs b/UnityMcpBridge/Editor/Models/McpTypes.cs index 0259b9c2..cb691a2a 100644 --- a/UnityMcpBridge/Editor/Models/McpTypes.cs +++ b/UnityMcpBridge/Editor/Models/McpTypes.cs @@ -5,6 +5,7 @@ public enum McpTypes ClaudeDesktop, Cursor, VSCode, + ClaudeCode, } } diff --git a/UnityMcpBridge/Editor/Windows/ManualConfigEditorWindow.cs b/UnityMcpBridge/Editor/Windows/ManualConfigEditorWindow.cs index 07b7da3f..89f60990 100644 --- a/UnityMcpBridge/Editor/Windows/ManualConfigEditorWindow.cs +++ b/UnityMcpBridge/Editor/Windows/ManualConfigEditorWindow.cs @@ -39,7 +39,7 @@ protected virtual void OnGUI() ); GUI.Label( new Rect(titleRect.x + 10, titleRect.y + 6, titleRect.width - 20, titleRect.height), - mcpClient.name + " Manual Configuration", + (mcpClient?.name ?? "Unknown") + " Manual Configuration", EditorStyles.boldLabel ); EditorGUILayout.Space(10); @@ -70,17 +70,17 @@ protected virtual void OnGUI() }; EditorGUILayout.LabelField( - "1. Open " + mcpClient.name + " config file by either:", + "1. Open " + (mcpClient?.name ?? "Unknown") + " config file by either:", instructionStyle ); - if (mcpClient.mcpType == McpTypes.ClaudeDesktop) + if (mcpClient?.mcpType == McpTypes.ClaudeDesktop) { EditorGUILayout.LabelField( " a) Going to Settings > Developer > Edit Config", instructionStyle ); } - else if (mcpClient.mcpType == McpTypes.Cursor) + else if (mcpClient?.mcpType == McpTypes.Cursor) { EditorGUILayout.LabelField( " a) Going to File > Preferences > Cursor Settings > MCP > Add new global MCP server", @@ -96,16 +96,23 @@ protected virtual void OnGUI() // Path section with improved styling EditorGUILayout.BeginVertical(EditorStyles.helpBox); string displayPath; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (mcpClient != null) { - displayPath = mcpClient.windowsConfigPath; - } - else if ( - RuntimeInformation.IsOSPlatform(OSPlatform.OSX) - || RuntimeInformation.IsOSPlatform(OSPlatform.Linux) - ) - { - displayPath = mcpClient.linuxConfigPath; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + displayPath = mcpClient.windowsConfigPath; + } + else if ( + RuntimeInformation.IsOSPlatform(OSPlatform.OSX) + || RuntimeInformation.IsOSPlatform(OSPlatform.Linux) + ) + { + displayPath = mcpClient.linuxConfigPath; + } + else + { + displayPath = configPath; + } } else { @@ -224,7 +231,7 @@ protected virtual void OnGUI() EditorGUILayout.Space(10); EditorGUILayout.LabelField( - "3. Save the file and restart " + mcpClient.name, + "3. Save the file and restart " + (mcpClient?.name ?? "Unknown"), instructionStyle ); diff --git a/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs b/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs index 3b1e69d5..80cd65b8 100644 --- a/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs +++ b/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; @@ -53,6 +54,16 @@ private void OnEnable() // Load validation level setting LoadValidationLevelSetting(); } + + private void OnFocus() + { + // Refresh configuration status when window gains focus + foreach (McpClient mcpClient in mcpClients.clients) + { + CheckMcpConfiguration(mcpClient); + } + Repaint(); + } private Color GetStatusColor(McpStatus status) { @@ -326,6 +337,23 @@ private void DrawClientConfigurationCompact(McpClient mcpClient) ConfigureMcpClient(mcpClient); } } + else if (mcpClient.mcpType == McpTypes.ClaudeCode) + { + bool isConfigured = mcpClient.status == McpStatus.Configured; + string buttonText = isConfigured ? "Unregister UnityMCP with Claude Code" : "Register with Claude Code"; + if (GUILayout.Button(buttonText, GUILayout.Height(32))) + { + if (isConfigured) + { + UnregisterWithClaudeCode(); + } + else + { + string pythonDir = FindPackagePythonDirectory(); + RegisterWithClaudeCode(pythonDir); + } + } + } else { if (GUILayout.Button($"Auto Configure", GUILayout.Height(32))) @@ -334,36 +362,39 @@ private void DrawClientConfigurationCompact(McpClient mcpClient) } } - if (GUILayout.Button("Manual Setup", GUILayout.Height(32))) + if (mcpClient.mcpType != McpTypes.ClaudeCode) { - string configPath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? mcpClient.windowsConfigPath - : mcpClient.linuxConfigPath; - - if (mcpClient.mcpType == McpTypes.VSCode) + if (GUILayout.Button("Manual Setup", GUILayout.Height(32))) { - string pythonDir = FindPackagePythonDirectory(); - var vscodeConfig = new + string configPath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? mcpClient.windowsConfigPath + : mcpClient.linuxConfigPath; + + if (mcpClient.mcpType == McpTypes.VSCode) { - mcp = new + string pythonDir = FindPackagePythonDirectory(); + var vscodeConfig = new { - servers = new + mcp = new { - unityMCP = new + servers = new { - command = "uv", - args = new[] { "--directory", pythonDir, "run", "server.py" } + unityMCP = new + { + command = "uv", + args = new[] { "--directory", pythonDir, "run", "server.py" } + } } } - } - }; - JsonSerializerSettings jsonSettings = new() { Formatting = Formatting.Indented }; - string manualConfigJson = JsonConvert.SerializeObject(vscodeConfig, jsonSettings); - VSCodeManualSetupWindow.ShowWindow(configPath, manualConfigJson); - } - else - { - ShowManualInstructionsWindow(configPath, mcpClient); + }; + JsonSerializerSettings jsonSettings = new() { Formatting = Formatting.Indented }; + string manualConfigJson = JsonConvert.SerializeObject(vscodeConfig, jsonSettings); + VSCodeManualSetupWindow.ShowWindow(configPath, manualConfigJson); + } + else + { + ShowManualInstructionsWindow(configPath, mcpClient); + } } } @@ -413,7 +444,7 @@ private string WriteToConfig(string pythonDir, string configPath, McpClient mcpC } catch (Exception e) { - Debug.LogWarning($"Error reading existing config: {e.Message}."); + UnityEngine.Debug.LogWarning($"Error reading existing config: {e.Message}."); } } @@ -546,12 +577,17 @@ private string FindPackagePythonDirectory() if (package.name == "com.justinpbarnett.unity-mcp") { string packagePath = package.resolvedPath; + + // Check for local package structure (UnityMcpServer/src) + string localPythonDir = Path.Combine(Path.GetDirectoryName(packagePath), "UnityMcpServer", "src"); + if (Directory.Exists(localPythonDir) && File.Exists(Path.Combine(localPythonDir, "server.py"))) + { + return localPythonDir; + } + + // Check for old structure (Python subdirectory) string potentialPythonDir = Path.Combine(packagePath, "Python"); - - if ( - Directory.Exists(potentialPythonDir) - && File.Exists(Path.Combine(potentialPythonDir, "server.py")) - ) + if (Directory.Exists(potentialPythonDir) && File.Exists(Path.Combine(potentialPythonDir, "server.py"))) { return potentialPythonDir; } @@ -560,13 +596,22 @@ private string FindPackagePythonDirectory() } else if (request.Error != null) { - Debug.LogError("Failed to list packages: " + request.Error.message); + UnityEngine.Debug.LogError("Failed to list packages: " + request.Error.message); } // If not found via Package Manager, try manual approaches - // First check for local installation + // Check for local development structure string[] possibleDirs = { + // Check in the Unity project's Packages folder (for local package development) + Path.GetFullPath(Path.Combine(Application.dataPath, "..", "Packages", "unity-mcp", "UnityMcpServer", "src")), + // Check relative to the Unity project (for development) + Path.GetFullPath(Path.Combine(Application.dataPath, "..", "unity-mcp", "UnityMcpServer", "src")), + // Check in user's home directory (common installation location) + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "unity-mcp", "UnityMcpServer", "src"), + // Check in Applications folder (macOS/Linux common location) + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Applications", "UnityMCP", "UnityMcpServer", "src"), + // Legacy Python folder structure Path.GetFullPath(Path.Combine(Application.dataPath, "unity-mcp", "Python")), }; @@ -579,11 +624,11 @@ private string FindPackagePythonDirectory() } // If still not found, return the placeholder path - Debug.LogWarning("Could not find Python directory, using placeholder path"); + UnityEngine.Debug.LogWarning("Could not find Python directory, using placeholder path"); } catch (Exception e) { - Debug.LogError($"Error finding package path: {e.Message}"); + UnityEngine.Debug.LogError($"Error finding package path: {e.Message}"); } return pythonDir; @@ -651,7 +696,7 @@ private string ConfigureMcpClient(McpClient mcpClient) } ShowManualInstructionsWindow(configPath, mcpClient); - Debug.LogError( + UnityEngine.Debug.LogError( $"Failed to configure {mcpClient.name}: {e.Message}\n{e.StackTrace}" ); return $"Failed to configure {mcpClient.name}"; @@ -735,6 +780,13 @@ private void CheckMcpConfiguration(McpClient mcpClient) { try { + // Special handling for Claude Code + if (mcpClient.mcpType == McpTypes.ClaudeCode) + { + CheckClaudeCodeConfiguration(mcpClient); + return; + } + string configPath; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { @@ -814,5 +866,255 @@ private void CheckMcpConfiguration(McpClient mcpClient) mcpClient.SetStatus(McpStatus.Error, e.Message); } } + + private void RegisterWithClaudeCode(string pythonDir) + { + string command; + string args; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + command = "claude"; + + // Try to find uv.exe in common locations + string uvPath = FindWindowsUvPath(); + + if (string.IsNullOrEmpty(uvPath)) + { + // Fallback to expecting uv in PATH + args = $"mcp add UnityMCP -- uv --directory \"{pythonDir}\" run server.py"; + } + else + { + args = $"mcp add UnityMCP -- \"{uvPath}\" --directory \"{pythonDir}\" run server.py"; + } + } + else + { + // Use full path to claude command + command = "/usr/local/bin/claude"; + args = $"mcp add UnityMCP -- uv --directory \"{pythonDir}\" run server.py"; + } + + try + { + // Get the Unity project directory (where the Assets folder is) + string unityProjectDir = Application.dataPath; + string projectDir = Path.GetDirectoryName(unityProjectDir); + + var psi = new ProcessStartInfo + { + FileName = command, + Arguments = args, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + WorkingDirectory = projectDir // Set working directory to Unity project directory + }; + + // Set PATH to include common binary locations + string currentPath = Environment.GetEnvironmentVariable("PATH") ?? ""; + string additionalPaths = "/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin"; + psi.EnvironmentVariables["PATH"] = $"{additionalPaths}:{currentPath}"; + + using var process = Process.Start(psi); + string output = process.StandardOutput.ReadToEnd(); + string errors = process.StandardError.ReadToEnd(); + process.WaitForExit(); + + + + // Check for success or already exists + if (output.Contains("Added stdio MCP server") || errors.Contains("already exists")) + { + // Force refresh the configuration status + var claudeClient = mcpClients.clients.FirstOrDefault(c => c.mcpType == McpTypes.ClaudeCode); + if (claudeClient != null) + { + CheckMcpConfiguration(claudeClient); + } + Repaint(); + + + } + else if (!string.IsNullOrEmpty(errors)) + { + UnityEngine.Debug.LogWarning($"Claude MCP errors: {errors}"); + } + } + catch (Exception e) + { + UnityEngine.Debug.LogError($"Claude CLI registration failed: {e.Message}"); + } + } + + private void UnregisterWithClaudeCode() + { + string command; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + command = "claude"; + } + else + { + // Use full path to claude command + command = "/usr/local/bin/claude"; + } + + try + { + // Get the Unity project directory (where the Assets folder is) + string unityProjectDir = Application.dataPath; + string projectDir = Path.GetDirectoryName(unityProjectDir); + + var psi = new ProcessStartInfo + { + FileName = command, + Arguments = "mcp remove UnityMCP", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + WorkingDirectory = projectDir // Set working directory to Unity project directory + }; + + // Set PATH to include common binary locations + string currentPath = Environment.GetEnvironmentVariable("PATH") ?? ""; + string additionalPaths = "/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin"; + psi.EnvironmentVariables["PATH"] = $"{additionalPaths}:{currentPath}"; + + using var process = Process.Start(psi); + string output = process.StandardOutput.ReadToEnd(); + string errors = process.StandardError.ReadToEnd(); + process.WaitForExit(); + + // Check for success + if (output.Contains("Removed MCP server") || process.ExitCode == 0) + { + // Force refresh the configuration status + var claudeClient = mcpClients.clients.FirstOrDefault(c => c.mcpType == McpTypes.ClaudeCode); + if (claudeClient != null) + { + CheckMcpConfiguration(claudeClient); + } + Repaint(); + + UnityEngine.Debug.Log("UnityMCP server successfully unregistered from Claude Code."); + } + else if (!string.IsNullOrEmpty(errors)) + { + UnityEngine.Debug.LogWarning($"Claude MCP removal errors: {errors}"); + } + } + catch (Exception e) + { + UnityEngine.Debug.LogError($"Claude CLI unregistration failed: {e.Message}"); + } + } + + private string FindWindowsUvPath() + { + string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + + // Check for different Python versions + string[] pythonVersions = { "Python313", "Python312", "Python311", "Python310", "Python39", "Python38" }; + + foreach (string version in pythonVersions) + { + string uvPath = Path.Combine(appData, version, "Scripts", "uv.exe"); + if (File.Exists(uvPath)) + { + return uvPath; + } + } + + // Check Program Files locations + string[] programFilesPaths = { + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Python"), + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Python"), + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Programs", "Python") + }; + + foreach (string basePath in programFilesPaths) + { + if (Directory.Exists(basePath)) + { + foreach (string dir in Directory.GetDirectories(basePath, "Python*")) + { + string uvPath = Path.Combine(dir, "Scripts", "uv.exe"); + if (File.Exists(uvPath)) + { + return uvPath; + } + } + } + } + + return null; // Will fallback to using 'uv' from PATH + } + + private void CheckClaudeCodeConfiguration(McpClient mcpClient) + { + try + { + // Get the Unity project directory to check project-specific config + string unityProjectDir = Application.dataPath; + string projectDir = Path.GetDirectoryName(unityProjectDir); + + // Read the global Claude config file + string configPath = mcpClient.linuxConfigPath; // ~/.claude.json + if (!File.Exists(configPath)) + { + mcpClient.SetStatus(McpStatus.NotConfigured); + return; + } + + string configJson = File.ReadAllText(configPath); + dynamic claudeConfig = JsonConvert.DeserializeObject(configJson); + + // Check for UnityMCP server in the mcpServers section (current format) + if (claudeConfig?.mcpServers != null) + { + var servers = claudeConfig.mcpServers; + if (servers.UnityMCP != null || servers.unityMCP != null) + { + // Found UnityMCP configured + mcpClient.SetStatus(McpStatus.Configured); + return; + } + } + + // Also check if there's a project-specific configuration for this Unity project (legacy format) + if (claudeConfig?.projects != null) + { + // Look for the project path in the config + foreach (var project in claudeConfig.projects) + { + string projectPath = project.Name; + if (projectPath == projectDir && project.Value?.mcpServers != null) + { + // Check for UnityMCP (case variations) + var servers = project.Value.mcpServers; + if (servers.UnityMCP != null || servers.unityMCP != null) + { + // Found UnityMCP configured for this project + mcpClient.SetStatus(McpStatus.Configured); + return; + } + } + } + } + + // No configuration found for this project + mcpClient.SetStatus(McpStatus.NotConfigured); + } + catch (Exception e) + { + UnityEngine.Debug.LogWarning($"Error checking Claude Code config: {e.Message}"); + mcpClient.SetStatus(McpStatus.Error, e.Message); + } + } } } From 2749b4e0c0ff61e3ae1061b8dab457b19a545fc5 Mon Sep 17 00:00:00 2001 From: David Sarno Date: Mon, 28 Jul 2025 11:55:08 -0700 Subject: [PATCH 2/4] feat: Add comprehensive UV error handling and installation guidance - Enhanced FindUvPath() to return null when UV is not found - Added detailed installation instructions for all platforms - Implemented null checks in all UV usage points - Added cross-platform path resolution for Windows, macOS, and Linux - Improved user experience with clear error messages instead of silent failures - Prevents 'spawn uv ENOENT' errors by using full paths and proper validation --- .../Editor/Windows/UnityMcpEditorWindow.cs | 104 +++++++++++++++++- 1 file changed, 100 insertions(+), 4 deletions(-) diff --git a/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs b/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs index 80cd65b8..9d63585e 100644 --- a/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs +++ b/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs @@ -373,6 +373,13 @@ private void DrawClientConfigurationCompact(McpClient mcpClient) if (mcpClient.mcpType == McpTypes.VSCode) { string pythonDir = FindPackagePythonDirectory(); + string uvPath = FindUvPath(); + if (uvPath == null) + { + UnityEngine.Debug.LogError("UV package manager not found. Cannot configure VSCode."); + return; + } + var vscodeConfig = new { mcp = new @@ -381,7 +388,7 @@ private void DrawClientConfigurationCompact(McpClient mcpClient) { unityMCP = new { - command = "uv", + command = uvPath, args = new[] { "--directory", pythonDir, "run", "server.py" } } } @@ -425,10 +432,16 @@ private void ToggleUnityBridge() 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 = "uv", + command = uvPath, args = new[] { "--directory", pythonDir, "run", "server.py" }, }; @@ -541,13 +554,20 @@ private void ShowManualInstructionsWindow(string configPath, McpClient mcpClient default: // Create standard MCP configuration for other clients + string uvPath = FindUvPath(); + if (uvPath == null) + { + UnityEngine.Debug.LogError("UV package manager not found. Cannot configure manual setup."); + return; + } + McpConfig jsonConfig = new() { mcpServers = new McpConfigServers { unityMCP = new McpConfigServer { - command = "uv", + command = uvPath, args = new[] { "--directory", pythonDir, "run", "server.py" }, }, }, @@ -714,13 +734,20 @@ McpClient mcpClient string pythonDir = FindPackagePythonDirectory(); // Create the manual configuration message + string uvPath = FindUvPath(); + if (uvPath == null) + { + UnityEngine.Debug.LogError("UV package manager not found. Cannot configure manual setup."); + return; + } + McpConfig jsonConfig = new() { mcpServers = new McpConfigServers { unityMCP = new McpConfigServer { - command = "uv", + command = uvPath, args = new[] { "--directory", pythonDir, "run", "server.py" }, }, }, @@ -1014,6 +1041,75 @@ private void UnregisterWithClaudeCode() } } + private string FindUvPath() + { + string uvPath = null; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + uvPath = FindWindowsUvPath(); + } + else + { + // macOS/Linux paths + string[] possiblePaths = { + "/Library/Frameworks/Python.framework/Versions/3.13/bin/uv", + "/usr/local/bin/uv", + "/opt/homebrew/bin/uv", + "/usr/bin/uv" + }; + + foreach (string path in possiblePaths) + { + if (File.Exists(path)) + { + uvPath = path; + break; + } + } + + // If not found in common locations, try to find via which command + if (uvPath == null) + { + try + { + var psi = new ProcessStartInfo + { + FileName = "which", + Arguments = "uv", + UseShellExecute = false, + RedirectStandardOutput = true, + CreateNoWindow = true + }; + + using var process = Process.Start(psi); + string output = process.StandardOutput.ReadToEnd().Trim(); + process.WaitForExit(); + + if (!string.IsNullOrEmpty(output) && File.Exists(output)) + { + uvPath = output; + } + } + catch + { + // Ignore errors + } + } + } + + if (uvPath == null) + { + UnityEngine.Debug.LogError("UV package manager not found! Please install UV first:\n" + + "• macOS/Linux: curl -LsSf https://astral.sh/uv/install.sh | sh\n" + + "• Windows: pip install uv\n" + + "• Or visit: https://docs.astral.sh/uv/getting-started/installation"); + return null; + } + + return uvPath; + } + private string FindWindowsUvPath() { string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); From 7dbb03b84ec493632e4e4f7825874567f7a5cbac Mon Sep 17 00:00:00 2001 From: Scriptwonder <1300285021@qq.com> Date: Mon, 28 Jul 2025 18:11:23 -0400 Subject: [PATCH 3/4] Update UnityMcpEditorWindow.cs Prevention for CLI to be found on Windows. --- .../Editor/Windows/UnityMcpEditorWindow.cs | 78 ++++++++++++++++++- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs b/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs index 9d63585e..46da78b1 100644 --- a/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs +++ b/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs @@ -901,7 +901,13 @@ private void RegisterWithClaudeCode(string pythonDir) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - command = "claude"; + command = FindClaudeCommand(); + + if (string.IsNullOrEmpty(command)) + { + UnityEngine.Debug.LogError("Claude CLI not found. Please ensure Claude Code is installed and accessible."); + return; + } // Try to find uv.exe in common locations string uvPath = FindWindowsUvPath(); @@ -982,7 +988,13 @@ private void UnregisterWithClaudeCode() if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - command = "claude"; + command = FindClaudeCommand(); + + if (string.IsNullOrEmpty(command)) + { + UnityEngine.Debug.LogError("Claude CLI not found. Please ensure Claude Code is installed and accessible."); + return; + } } else { @@ -1151,6 +1163,68 @@ private string FindWindowsUvPath() return null; // Will fallback to using 'uv' from PATH } + private string FindClaudeCommand() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // Common locations for Claude CLI on Windows + string[] possiblePaths = { + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "npm", "claude.cmd"), + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "npm", "claude.cmd"), + "claude.cmd", // Fallback to PATH + "claude" // Final fallback + }; + + foreach (string path in possiblePaths) + { + if (path.Contains("\\") && File.Exists(path)) + { + return path; + } + } + + // Try to find via where command + try + { + var psi = new ProcessStartInfo + { + FileName = "where", + Arguments = "claude", + UseShellExecute = false, + RedirectStandardOutput = true, + CreateNoWindow = true + }; + + using var process = Process.Start(psi); + string output = process.StandardOutput.ReadToEnd().Trim(); + process.WaitForExit(); + + if (!string.IsNullOrEmpty(output)) + { + string[] lines = output.Split('\n'); + foreach (string line in lines) + { + string cleanPath = line.Trim(); + if (File.Exists(cleanPath)) + { + return cleanPath; + } + } + } + } + catch + { + // Ignore errors and fall back + } + + return "claude"; // Final fallback to PATH + } + else + { + return "/usr/local/bin/claude"; + } + } + private void CheckClaudeCodeConfiguration(McpClient mcpClient) { try From 92ad2c50314fdbe9feda8c1e1ede6144f8d83d80 Mon Sep 17 00:00:00 2001 From: Scriptwonder <1300285021@qq.com> Date: Mon, 28 Jul 2025 18:30:33 -0400 Subject: [PATCH 4/4] Minor Changes Add successfully registration info and reorder the seleciton --- UnityMcpBridge/Editor/Data/McpClients.cs | 28 +++++++++---------- .../Editor/Windows/UnityMcpEditorWindow.cs | 3 +- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/UnityMcpBridge/Editor/Data/McpClients.cs b/UnityMcpBridge/Editor/Data/McpClients.cs index 9b2d7c1b..dec53c8c 100644 --- a/UnityMcpBridge/Editor/Data/McpClients.cs +++ b/UnityMcpBridge/Editor/Data/McpClients.cs @@ -28,6 +28,20 @@ public class McpClients configStatus = "Not Configured", }, new() + { + name = "Claude Code", + windowsConfigPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + ".claude.json" + ), + linuxConfigPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + ".claude.json" + ), + mcpType = McpTypes.ClaudeCode, + configStatus = "Not Configured", + }, + new() { name = "Cursor", windowsConfigPath = Path.Combine( @@ -63,20 +77,6 @@ public class McpClients mcpType = McpTypes.VSCode, configStatus = "Not Configured", }, - new() - { - name = "Claude Code", - windowsConfigPath = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), - ".claude.json" - ), - linuxConfigPath = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), - ".claude.json" - ), - mcpType = McpTypes.ClaudeCode, - configStatus = "Not Configured", - }, }; // Initialize status enums after construction diff --git a/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs b/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs index 46da78b1..561cd39d 100644 --- a/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs +++ b/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs @@ -957,7 +957,7 @@ private void RegisterWithClaudeCode(string pythonDir) process.WaitForExit(); - + // Check for success or already exists if (output.Contains("Added stdio MCP server") || errors.Contains("already exists")) { @@ -968,6 +968,7 @@ private void RegisterWithClaudeCode(string pythonDir) CheckMcpConfiguration(claudeClient); } Repaint(); + UnityEngine.Debug.Log("UnityMCP server successfully registered from Claude Code."); }