Skip to content

Commit 5965158

Browse files
committed
Unity MCP: Claude Code UX improvements: dynamic not-found state with inline help link; NVM auto-detection; path picker override; hide picker after detection; remove auto-connect toggle.
1 parent bd6114b commit 5965158

File tree

2 files changed

+160
-33
lines changed

2 files changed

+160
-33
lines changed

UnityMcpBridge/Editor/Helpers/ExecPath.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ internal static string ResolveClaude()
3535
Path.Combine(home, ".local", "bin", "claude"),
3636
};
3737
foreach (string c in candidates) { if (File.Exists(c)) return c; }
38+
// Try NVM-installed claude under ~/.nvm/versions/node/*/bin/claude
39+
string nvmClaude = ResolveClaudeFromNvm(home);
40+
if (!string.IsNullOrEmpty(nvmClaude)) return nvmClaude;
3841
#if UNITY_EDITOR_OSX || UNITY_EDITOR_LINUX
3942
return Which("claude", "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin");
4043
#else
@@ -70,6 +73,9 @@ internal static string ResolveClaude()
7073
Path.Combine(home, ".local", "bin", "claude"),
7174
};
7275
foreach (string c in candidates) { if (File.Exists(c)) return c; }
76+
// Try NVM-installed claude under ~/.nvm/versions/node/*/bin/claude
77+
string nvmClaude = ResolveClaudeFromNvm(home);
78+
if (!string.IsNullOrEmpty(nvmClaude)) return nvmClaude;
7379
#if UNITY_EDITOR_OSX || UNITY_EDITOR_LINUX
7480
return Which("claude", "/usr/local/bin:/usr/bin:/bin");
7581
#else
@@ -78,6 +84,68 @@ internal static string ResolveClaude()
7884
}
7985
}
8086

87+
// Attempt to resolve claude from NVM-managed Node installations, choosing the newest version
88+
private static string ResolveClaudeFromNvm(string home)
89+
{
90+
try
91+
{
92+
if (string.IsNullOrEmpty(home)) return null;
93+
string nvmNodeDir = Path.Combine(home, ".nvm", "versions", "node");
94+
if (!Directory.Exists(nvmNodeDir)) return null;
95+
96+
string bestPath = null;
97+
Version bestVersion = null;
98+
foreach (string versionDir in Directory.EnumerateDirectories(nvmNodeDir))
99+
{
100+
string name = Path.GetFileName(versionDir);
101+
if (string.IsNullOrEmpty(name)) continue;
102+
if (name.StartsWith("v", StringComparison.OrdinalIgnoreCase))
103+
{
104+
if (Version.TryParse(name.Substring(1), out Version parsed))
105+
{
106+
string candidate = Path.Combine(versionDir, "bin", "claude");
107+
if (File.Exists(candidate))
108+
{
109+
if (bestVersion == null || parsed > bestVersion)
110+
{
111+
bestVersion = parsed;
112+
bestPath = candidate;
113+
}
114+
}
115+
}
116+
}
117+
}
118+
return bestPath;
119+
}
120+
catch { return null; }
121+
}
122+
123+
// Explicitly set the Claude CLI absolute path override in EditorPrefs
124+
internal static void SetClaudeCliPath(string absolutePath)
125+
{
126+
try
127+
{
128+
if (!string.IsNullOrEmpty(absolutePath) && File.Exists(absolutePath))
129+
{
130+
EditorPrefs.SetString(PrefClaude, absolutePath);
131+
}
132+
}
133+
catch { }
134+
}
135+
136+
// Clear any previously set Claude CLI override path
137+
internal static void ClearClaudeCliPath()
138+
{
139+
try
140+
{
141+
if (EditorPrefs.HasKey(PrefClaude))
142+
{
143+
EditorPrefs.DeleteKey(PrefClaude);
144+
}
145+
}
146+
catch { }
147+
}
148+
81149
// Use existing UV resolver; returns absolute path or null.
82150
internal static string ResolveUv()
83151
{

UnityMcpBridge/Editor/Windows/UnityMcpEditorWindow.cs

Lines changed: 92 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -438,14 +438,7 @@ private void DrawUnifiedClientConfiguration()
438438
EditorGUILayout.LabelField("MCP Client Configuration", sectionTitleStyle);
439439
EditorGUILayout.Space(10);
440440

441-
// Auto-connect toggle (moved from Server Status)
442-
bool newAuto = EditorGUILayout.ToggleLeft("Auto-connect to MCP Clients", autoRegisterEnabled);
443-
if (newAuto != autoRegisterEnabled)
444-
{
445-
autoRegisterEnabled = newAuto;
446-
EditorPrefs.SetBool("UnityMCP.AutoRegisterEnabled", autoRegisterEnabled);
447-
}
448-
EditorGUILayout.Space(6);
441+
// (Auto-connect toggle removed per design)
449442

450443
// Client selector
451444
string[] clientNames = mcpClients.clients.Select(c => c.name).ToArray();
@@ -697,6 +690,17 @@ private static bool VerifyBridgePing(int port)
697690

698691
private void DrawClientConfigurationCompact(McpClient mcpClient)
699692
{
693+
// Special pre-check for Claude Code: if CLI missing, reflect in status UI
694+
if (mcpClient.mcpType == McpTypes.ClaudeCode)
695+
{
696+
string claudeCheck = ExecPath.ResolveClaude();
697+
if (string.IsNullOrEmpty(claudeCheck))
698+
{
699+
mcpClient.configStatus = "Claude Not Found";
700+
mcpClient.status = McpStatus.NotConfigured;
701+
}
702+
}
703+
700704
// Status display
701705
EditorGUILayout.BeginHorizontal();
702706
Rect statusRect = GUILayoutUtility.GetRect(0, 28, GUILayout.Width(24));
@@ -710,7 +714,24 @@ private void DrawClientConfigurationCompact(McpClient mcpClient)
710714
};
711715
EditorGUILayout.LabelField(mcpClient.configStatus, clientStatusStyle, GUILayout.Height(28));
712716
EditorGUILayout.EndHorizontal();
713-
717+
// When Claude CLI is missing, show a clear install hint directly below status
718+
if (mcpClient.mcpType == McpTypes.ClaudeCode && string.IsNullOrEmpty(ExecPath.ResolveClaude()))
719+
{
720+
GUIStyle installHintStyle = new GUIStyle(clientStatusStyle);
721+
installHintStyle.normal.textColor = new Color(1f, 0.5f, 0f); // orange
722+
EditorGUILayout.BeginHorizontal();
723+
GUIContent installText = new GUIContent("Make sure Claude Code is installed!");
724+
Vector2 textSize = installHintStyle.CalcSize(installText);
725+
EditorGUILayout.LabelField(installText, installHintStyle, GUILayout.Height(22), GUILayout.Width(textSize.x + 2), GUILayout.ExpandWidth(false));
726+
GUIStyle helpLinkStyle = new GUIStyle(EditorStyles.linkLabel) { fontStyle = FontStyle.Bold };
727+
GUILayout.Space(6);
728+
if (GUILayout.Button("[CLICK]", helpLinkStyle, GUILayout.Height(22), GUILayout.ExpandWidth(false)))
729+
{
730+
Application.OpenURL("https://github.com/CoplayDev/unity-mcp/wiki/Troubleshooting-Unity-MCP-and-Claude-Code");
731+
}
732+
EditorGUILayout.EndHorizontal();
733+
}
734+
714735
EditorGUILayout.Space(10);
715736

716737
// Action buttons in horizontal layout
@@ -723,23 +744,57 @@ private void DrawClientConfigurationCompact(McpClient mcpClient)
723744
ConfigureMcpClient(mcpClient);
724745
}
725746
}
726-
else if (mcpClient.mcpType == McpTypes.ClaudeCode)
727-
{
728-
bool isConfigured = mcpClient.status == McpStatus.Configured;
729-
string buttonText = isConfigured ? "Unregister UnityMCP with Claude Code" : "Register with Claude Code";
730-
if (GUILayout.Button(buttonText, GUILayout.Height(32)))
731-
{
732-
if (isConfigured)
733-
{
734-
UnregisterWithClaudeCode();
735-
}
736-
else
737-
{
738-
string pythonDir = FindPackagePythonDirectory();
739-
RegisterWithClaudeCode(pythonDir);
740-
}
741-
}
742-
}
747+
else if (mcpClient.mcpType == McpTypes.ClaudeCode)
748+
{
749+
bool claudeAvailable = !string.IsNullOrEmpty(ExecPath.ResolveClaude());
750+
if (claudeAvailable)
751+
{
752+
bool isConfigured = mcpClient.status == McpStatus.Configured;
753+
string buttonText = isConfigured ? "Unregister UnityMCP with Claude Code" : "Register with Claude Code";
754+
if (GUILayout.Button(buttonText, GUILayout.Height(32)))
755+
{
756+
if (isConfigured)
757+
{
758+
UnregisterWithClaudeCode();
759+
}
760+
else
761+
{
762+
string pythonDir = FindPackagePythonDirectory();
763+
RegisterWithClaudeCode(pythonDir);
764+
}
765+
}
766+
// Hide the picker once a valid binary is available
767+
EditorGUILayout.EndHorizontal();
768+
EditorGUILayout.BeginHorizontal();
769+
GUIStyle pathLabelStyle = new GUIStyle(EditorStyles.miniLabel) { wordWrap = true };
770+
string resolvedClaude = ExecPath.ResolveClaude();
771+
EditorGUILayout.LabelField($"Claude CLI: {resolvedClaude}", pathLabelStyle);
772+
EditorGUILayout.EndHorizontal();
773+
EditorGUILayout.BeginHorizontal();
774+
}
775+
// CLI picker row (only when not found)
776+
EditorGUILayout.EndHorizontal();
777+
EditorGUILayout.BeginHorizontal();
778+
if (!claudeAvailable)
779+
{
780+
// Only show the picker button in not-found state (no redundant "not found" label)
781+
if (GUILayout.Button("Choose Claude Install Location", GUILayout.Width(260), GUILayout.Height(22)))
782+
{
783+
string suggested = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "/opt/homebrew/bin" : Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
784+
string picked = EditorUtility.OpenFilePanel("Select 'claude' CLI", suggested, "");
785+
if (!string.IsNullOrEmpty(picked))
786+
{
787+
ExecPath.SetClaudeCliPath(picked);
788+
// Auto-register after setting a valid path
789+
string pythonDir = FindPackagePythonDirectory();
790+
RegisterWithClaudeCode(pythonDir);
791+
Repaint();
792+
}
793+
}
794+
}
795+
EditorGUILayout.EndHorizontal();
796+
EditorGUILayout.BeginHorizontal();
797+
}
743798
else
744799
{
745800
if (GUILayout.Button($"Auto Configure", GUILayout.Height(32)))
@@ -793,13 +848,17 @@ private void DrawClientConfigurationCompact(McpClient mcpClient)
793848

794849
EditorGUILayout.EndHorizontal();
795850

796-
EditorGUILayout.Space(8);
797-
// Quick info
798-
GUIStyle configInfoStyle = new GUIStyle(EditorStyles.miniLabel)
799-
{
800-
fontSize = 10
801-
};
802-
EditorGUILayout.LabelField($"Config: {Path.GetFileName(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? mcpClient.windowsConfigPath : mcpClient.linuxConfigPath)}", configInfoStyle);
851+
EditorGUILayout.Space(8);
852+
// Quick info (hide when Claude is not found to avoid confusion)
853+
bool hideConfigInfo = (mcpClient.mcpType == McpTypes.ClaudeCode) && string.IsNullOrEmpty(ExecPath.ResolveClaude());
854+
if (!hideConfigInfo)
855+
{
856+
GUIStyle configInfoStyle = new GUIStyle(EditorStyles.miniLabel)
857+
{
858+
fontSize = 10
859+
};
860+
EditorGUILayout.LabelField($"Config: {Path.GetFileName(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? mcpClient.windowsConfigPath : mcpClient.linuxConfigPath)}", configInfoStyle);
861+
}
803862
}
804863

805864
private void ToggleUnityBridge()

0 commit comments

Comments
 (0)