Skip to content

Commit 4cd6c07

Browse files
authored
Fix Claude Windows config and CLI status refresh (#412)
* Fix Claude Windows config and CLI status refresh * Fix Claude uvx path resolution * Address review feedback for Claude uvx * Polish config cleanup and status errors * Tidy Claude status refresh
1 parent 839665b commit 4cd6c07

File tree

5 files changed

+315
-45
lines changed

5 files changed

+315
-45
lines changed

MCPForUnity/Editor/Clients/Configurators/ClaudeDesktopConfigurator.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@ namespace MCPForUnity.Editor.Clients.Configurators
99
{
1010
public class ClaudeDesktopConfigurator : JsonFileMcpConfigurator
1111
{
12+
public const string ClientName = "Claude Desktop";
13+
1214
public ClaudeDesktopConfigurator() : base(new McpClient
1315
{
14-
name = "Claude Desktop",
16+
name = ClientName,
1517
windowsConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Claude", "claude_desktop_config.json"),
1618
macConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Application Support", "Claude", "claude_desktop_config.json"),
1719
linuxConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config", "Claude", "claude_desktop_config.json"),
18-
SupportsHttpTransport = false
20+
SupportsHttpTransport = false,
21+
StripEnvWhenNotRequired = true
1922
})
2023
{ }
2124

MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
35
using MCPForUnity.Editor.Constants;
6+
using MCPForUnity.Editor.Clients.Configurators;
47
using MCPForUnity.Editor.Helpers;
58
using MCPForUnity.Editor.Models;
69
using Newtonsoft.Json;
710
using Newtonsoft.Json.Linq;
811
using UnityEditor;
12+
using UnityEngine;
913

1014
namespace MCPForUnity.Editor.Helpers
1115
{
@@ -77,27 +81,26 @@ private static void PopulateUnityNode(JObject unity, string uvPath, McpClient cl
7781
// Stdio mode: Use uvx command
7882
var (uvxPath, fromUrl, packageName) = AssetPathUtility.GetUvxCommandParts();
7983

80-
unity["command"] = uvxPath;
84+
var toolArgs = BuildUvxArgs(fromUrl, packageName);
8185

82-
var args = new List<string> { packageName };
83-
if (!string.IsNullOrEmpty(fromUrl))
86+
if (ShouldUseWindowsCmdShim(client))
8487
{
85-
args.Insert(0, fromUrl);
86-
args.Insert(0, "--from");
87-
}
88+
unity["command"] = ResolveCmdPath();
8889

89-
args.Add("--transport");
90-
args.Add("stdio");
90+
var cmdArgs = new List<string> { "/c", uvxPath };
91+
cmdArgs.AddRange(toolArgs);
9192

92-
unity["args"] = JArray.FromObject(args.ToArray());
93+
unity["args"] = JArray.FromObject(cmdArgs.ToArray());
94+
}
95+
else
96+
{
97+
unity["command"] = uvxPath;
98+
unity["args"] = JArray.FromObject(toolArgs.ToArray());
99+
}
93100

94101
// Remove url/serverUrl if they exist from previous config
95102
if (unity["url"] != null) unity.Remove("url");
96103
if (unity["serverUrl"] != null) unity.Remove("serverUrl");
97-
foreach (var prop in urlPropsToRemove)
98-
{
99-
if (unity[prop] != null) unity.Remove(prop);
100-
}
101104

102105
if (isVSCode)
103106
{
@@ -145,5 +148,44 @@ private static JObject EnsureObject(JObject parent, string name)
145148
parent[name] = created;
146149
return created;
147150
}
151+
152+
private static IList<string> BuildUvxArgs(string fromUrl, string packageName)
153+
{
154+
var args = new List<string> { packageName };
155+
156+
if (!string.IsNullOrEmpty(fromUrl))
157+
{
158+
args.Insert(0, fromUrl);
159+
args.Insert(0, "--from");
160+
}
161+
162+
args.Add("--transport");
163+
args.Add("stdio");
164+
165+
return args;
166+
}
167+
168+
private static bool ShouldUseWindowsCmdShim(McpClient client)
169+
{
170+
if (client == null)
171+
{
172+
return false;
173+
}
174+
175+
return Application.platform == RuntimePlatform.WindowsEditor &&
176+
string.Equals(client.name, ClaudeDesktopConfigurator.ClientName, StringComparison.OrdinalIgnoreCase);
177+
}
178+
179+
private static string ResolveCmdPath()
180+
{
181+
var comSpec = Environment.GetEnvironmentVariable("ComSpec");
182+
if (!string.IsNullOrEmpty(comSpec) && File.Exists(comSpec))
183+
{
184+
return comSpec;
185+
}
186+
187+
string system32Cmd = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "cmd.exe");
188+
return File.Exists(system32Cmd) ? system32Cmd : "cmd.exe";
189+
}
148190
}
149191
}

MCPForUnity/Editor/Services/PathResolverService.cs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Diagnostics;
34
using System.IO;
45
using System.Linq;
@@ -34,6 +35,12 @@ public string GetUvxPath()
3435
McpLog.Debug("No uvx path override found, falling back to default command");
3536
}
3637

38+
string discovered = ResolveUvxFromSystem();
39+
if (!string.IsNullOrEmpty(discovered))
40+
{
41+
return discovered;
42+
}
43+
3744
return "uvx";
3845
}
3946

@@ -123,6 +130,81 @@ public bool IsClaudeCliDetected()
123130
return !string.IsNullOrEmpty(GetClaudeCliPath());
124131
}
125132

133+
private static string ResolveUvxFromSystem()
134+
{
135+
try
136+
{
137+
foreach (string candidate in EnumerateUvxCandidates())
138+
{
139+
if (!string.IsNullOrEmpty(candidate) && File.Exists(candidate))
140+
{
141+
return candidate;
142+
}
143+
}
144+
}
145+
catch
146+
{
147+
// fall back to bare command
148+
}
149+
150+
return null;
151+
}
152+
153+
private static IEnumerable<string> EnumerateUvxCandidates()
154+
{
155+
string exeName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "uvx.exe" : "uvx";
156+
157+
string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
158+
if (!string.IsNullOrEmpty(home))
159+
{
160+
yield return Path.Combine(home, ".local", "bin", exeName);
161+
yield return Path.Combine(home, ".cargo", "bin", exeName);
162+
}
163+
164+
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
165+
{
166+
yield return "/opt/homebrew/bin/" + exeName;
167+
yield return "/usr/local/bin/" + exeName;
168+
}
169+
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
170+
{
171+
yield return "/usr/local/bin/" + exeName;
172+
yield return "/usr/bin/" + exeName;
173+
}
174+
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
175+
{
176+
string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
177+
string programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
178+
179+
if (!string.IsNullOrEmpty(localAppData))
180+
{
181+
yield return Path.Combine(localAppData, "Programs", "uv", exeName);
182+
}
183+
184+
if (!string.IsNullOrEmpty(programFiles))
185+
{
186+
yield return Path.Combine(programFiles, "uv", exeName);
187+
}
188+
}
189+
190+
string pathEnv = Environment.GetEnvironmentVariable("PATH");
191+
if (!string.IsNullOrEmpty(pathEnv))
192+
{
193+
foreach (string rawDir in pathEnv.Split(Path.PathSeparator))
194+
{
195+
if (string.IsNullOrWhiteSpace(rawDir)) continue;
196+
string dir = rawDir.Trim();
197+
yield return Path.Combine(dir, exeName);
198+
199+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
200+
{
201+
// Some PATH entries may already contain the file without extension
202+
yield return Path.Combine(dir, "uvx");
203+
}
204+
}
205+
}
206+
}
207+
126208
public void SetUvxPathOverride(string path)
127209
{
128210
if (string.IsNullOrEmpty(path))

0 commit comments

Comments
 (0)