diff --git a/UnityMcpBridge/Editor/Helpers/ExecPath.cs b/UnityMcpBridge/Editor/Helpers/ExecPath.cs index 537962e..4403b69 100644 --- a/UnityMcpBridge/Editor/Helpers/ExecPath.cs +++ b/UnityMcpBridge/Editor/Helpers/ExecPath.cs @@ -35,6 +35,9 @@ namespace UnityMcpBridge.Editor.Helpers Path.Combine(home, ".local", "bin", "claude"), }; foreach (string c in candidates) { if (File.Exists(c)) return c; } + // Try NVM-installed claude under ~/.nvm/versions/node/*/bin/claude + string nvmClaude = ResolveClaudeFromNvm(home); + if (!string.IsNullOrEmpty(nvmClaude)) return nvmClaude; #if UNITY_EDITOR_OSX || UNITY_EDITOR_LINUX return Which("claude", "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin"); #else @@ -70,6 +73,9 @@ namespace UnityMcpBridge.Editor.Helpers Path.Combine(home, ".local", "bin", "claude"), }; foreach (string c in candidates) { if (File.Exists(c)) return c; } + // Try NVM-installed claude under ~/.nvm/versions/node/*/bin/claude + string nvmClaude = ResolveClaudeFromNvm(home); + if (!string.IsNullOrEmpty(nvmClaude)) return nvmClaude; #if UNITY_EDITOR_OSX || UNITY_EDITOR_LINUX return Which("claude", "/usr/local/bin:/usr/bin:/bin"); #else @@ -78,6 +84,68 @@ namespace UnityMcpBridge.Editor.Helpers } } + // Attempt to resolve claude from NVM-managed Node installations, choosing the newest version + private static string ResolveClaudeFromNvm(string home) + { + try + { + if (string.IsNullOrEmpty(home)) return null; + string nvmNodeDir = Path.Combine(home, ".nvm", "versions", "node"); + if (!Directory.Exists(nvmNodeDir)) return null; + + string bestPath = null; + Version bestVersion = null; + foreach (string versionDir in Directory.EnumerateDirectories(nvmNodeDir)) + { + string name = Path.GetFileName(versionDir); + if (string.IsNullOrEmpty(name)) continue; + if (name.StartsWith("v", StringComparison.OrdinalIgnoreCase)) + { + if (Version.TryParse(name.Substring(1), out Version parsed)) + { + string candidate = Path.Combine(versionDir, "bin", "claude"); + if (File.Exists(candidate)) + { + if (bestVersion == null || parsed > bestVersion) + { + bestVersion = parsed; + bestPath = candidate; + } + } + } + } + } + return bestPath; + } + catch { return null; } + } + + // Explicitly set the Claude CLI absolute path override in EditorPrefs + internal static void SetClaudeCliPath(string absolutePath) + { + try + { + if (!string.IsNullOrEmpty(absolutePath) && File.Exists(absolutePath)) + { + EditorPrefs.SetString(PrefClaude, absolutePath); + } + } + catch { } + } + + // Clear any previously set Claude CLI override path + internal static void ClearClaudeCliPath() + { + try + { + if (EditorPrefs.HasKey(PrefClaude)) + { + EditorPrefs.DeleteKey(PrefClaude); + } + } + catch { } + } + // Use existing UV resolver; returns absolute path or null. internal static string ResolveUv() { diff --git a/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs b/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs index b212719..47b983e 100644 --- a/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs +++ b/UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs @@ -438,14 +438,7 @@ namespace UnityMcpBridge.Editor.Windows EditorGUILayout.LabelField("MCP Client Configuration", sectionTitleStyle); EditorGUILayout.Space(10); - // Auto-connect toggle (moved from Server Status) - bool newAuto = EditorGUILayout.ToggleLeft("Auto-connect to MCP Clients", autoRegisterEnabled); - if (newAuto != autoRegisterEnabled) - { - autoRegisterEnabled = newAuto; - EditorPrefs.SetBool("UnityMCP.AutoRegisterEnabled", autoRegisterEnabled); - } - EditorGUILayout.Space(6); + // (Auto-connect toggle removed per design) // Client selector string[] clientNames = mcpClients.clients.Select(c => c.name).ToArray(); @@ -697,6 +690,17 @@ namespace UnityMcpBridge.Editor.Windows private void DrawClientConfigurationCompact(McpClient mcpClient) { + // Special pre-check for Claude Code: if CLI missing, reflect in status UI + if (mcpClient.mcpType == McpTypes.ClaudeCode) + { + string claudeCheck = ExecPath.ResolveClaude(); + if (string.IsNullOrEmpty(claudeCheck)) + { + mcpClient.configStatus = "Claude Not Found"; + mcpClient.status = McpStatus.NotConfigured; + } + } + // Status display EditorGUILayout.BeginHorizontal(); Rect statusRect = GUILayoutUtility.GetRect(0, 28, GUILayout.Width(24)); @@ -710,7 +714,24 @@ namespace UnityMcpBridge.Editor.Windows }; EditorGUILayout.LabelField(mcpClient.configStatus, clientStatusStyle, GUILayout.Height(28)); EditorGUILayout.EndHorizontal(); - + // When Claude CLI is missing, show a clear install hint directly below status + if (mcpClient.mcpType == McpTypes.ClaudeCode && string.IsNullOrEmpty(ExecPath.ResolveClaude())) + { + GUIStyle installHintStyle = new GUIStyle(clientStatusStyle); + installHintStyle.normal.textColor = new Color(1f, 0.5f, 0f); // orange + EditorGUILayout.BeginHorizontal(); + GUIContent installText = new GUIContent("Make sure Claude Code is installed!"); + Vector2 textSize = installHintStyle.CalcSize(installText); + EditorGUILayout.LabelField(installText, installHintStyle, GUILayout.Height(22), GUILayout.Width(textSize.x + 2), GUILayout.ExpandWidth(false)); + GUIStyle helpLinkStyle = new GUIStyle(EditorStyles.linkLabel) { fontStyle = FontStyle.Bold }; + GUILayout.Space(6); + if (GUILayout.Button("[CLICK]", helpLinkStyle, GUILayout.Height(22), GUILayout.ExpandWidth(false))) + { + Application.OpenURL("https://github.com/CoplayDev/unity-mcp/wiki/Troubleshooting-Unity-MCP-and-Claude-Code"); + } + EditorGUILayout.EndHorizontal(); + } + EditorGUILayout.Space(10); // Action buttons in horizontal layout @@ -723,23 +744,57 @@ namespace UnityMcpBridge.Editor.Windows 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 (mcpClient.mcpType == McpTypes.ClaudeCode) + { + bool claudeAvailable = !string.IsNullOrEmpty(ExecPath.ResolveClaude()); + if (claudeAvailable) + { + 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); + } + } + // Hide the picker once a valid binary is available + EditorGUILayout.EndHorizontal(); + EditorGUILayout.BeginHorizontal(); + GUIStyle pathLabelStyle = new GUIStyle(EditorStyles.miniLabel) { wordWrap = true }; + string resolvedClaude = ExecPath.ResolveClaude(); + EditorGUILayout.LabelField($"Claude CLI: {resolvedClaude}", pathLabelStyle); + EditorGUILayout.EndHorizontal(); + EditorGUILayout.BeginHorizontal(); + } + // CLI picker row (only when not found) + EditorGUILayout.EndHorizontal(); + EditorGUILayout.BeginHorizontal(); + if (!claudeAvailable) + { + // Only show the picker button in not-found state (no redundant "not found" label) + if (GUILayout.Button("Choose Claude Install Location", GUILayout.Width(260), GUILayout.Height(22))) + { + string suggested = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "/opt/homebrew/bin" : Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); + string picked = EditorUtility.OpenFilePanel("Select 'claude' CLI", suggested, ""); + if (!string.IsNullOrEmpty(picked)) + { + ExecPath.SetClaudeCliPath(picked); + // Auto-register after setting a valid path + string pythonDir = FindPackagePythonDirectory(); + RegisterWithClaudeCode(pythonDir); + Repaint(); + } + } + } + EditorGUILayout.EndHorizontal(); + EditorGUILayout.BeginHorizontal(); + } else { if (GUILayout.Button($"Auto Configure", GUILayout.Height(32))) @@ -793,13 +848,17 @@ namespace UnityMcpBridge.Editor.Windows EditorGUILayout.EndHorizontal(); - EditorGUILayout.Space(8); - // Quick info - GUIStyle configInfoStyle = new GUIStyle(EditorStyles.miniLabel) - { - fontSize = 10 - }; - EditorGUILayout.LabelField($"Config: {Path.GetFileName(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? mcpClient.windowsConfigPath : mcpClient.linuxConfigPath)}", configInfoStyle); + EditorGUILayout.Space(8); + // Quick info (hide when Claude is not found to avoid confusion) + bool hideConfigInfo = (mcpClient.mcpType == McpTypes.ClaudeCode) && string.IsNullOrEmpty(ExecPath.ResolveClaude()); + if (!hideConfigInfo) + { + GUIStyle configInfoStyle = new GUIStyle(EditorStyles.miniLabel) + { + fontSize = 10 + }; + EditorGUILayout.LabelField($"Config: {Path.GetFileName(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? mcpClient.windowsConfigPath : mcpClient.linuxConfigPath)}", configInfoStyle); + } } private void ToggleUnityBridge()