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 refreshmain
parent
839665b37c
commit
4cd6c071db
|
|
@ -9,13 +9,16 @@ namespace MCPForUnity.Editor.Clients.Configurators
|
||||||
{
|
{
|
||||||
public class ClaudeDesktopConfigurator : JsonFileMcpConfigurator
|
public class ClaudeDesktopConfigurator : JsonFileMcpConfigurator
|
||||||
{
|
{
|
||||||
|
public const string ClientName = "Claude Desktop";
|
||||||
|
|
||||||
public ClaudeDesktopConfigurator() : base(new McpClient
|
public ClaudeDesktopConfigurator() : base(new McpClient
|
||||||
{
|
{
|
||||||
name = "Claude Desktop",
|
name = ClientName,
|
||||||
windowsConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Claude", "claude_desktop_config.json"),
|
windowsConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Claude", "claude_desktop_config.json"),
|
||||||
macConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Application Support", "Claude", "claude_desktop_config.json"),
|
macConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Application Support", "Claude", "claude_desktop_config.json"),
|
||||||
linuxConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config", "Claude", "claude_desktop_config.json"),
|
linuxConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config", "Claude", "claude_desktop_config.json"),
|
||||||
SupportsHttpTransport = false
|
SupportsHttpTransport = false,
|
||||||
|
StripEnvWhenNotRequired = true
|
||||||
})
|
})
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using MCPForUnity.Editor.Constants;
|
using MCPForUnity.Editor.Constants;
|
||||||
|
using MCPForUnity.Editor.Clients.Configurators;
|
||||||
using MCPForUnity.Editor.Helpers;
|
using MCPForUnity.Editor.Helpers;
|
||||||
using MCPForUnity.Editor.Models;
|
using MCPForUnity.Editor.Models;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace MCPForUnity.Editor.Helpers
|
namespace MCPForUnity.Editor.Helpers
|
||||||
{
|
{
|
||||||
|
|
@ -77,27 +81,26 @@ namespace MCPForUnity.Editor.Helpers
|
||||||
// Stdio mode: Use uvx command
|
// Stdio mode: Use uvx command
|
||||||
var (uvxPath, fromUrl, packageName) = AssetPathUtility.GetUvxCommandParts();
|
var (uvxPath, fromUrl, packageName) = AssetPathUtility.GetUvxCommandParts();
|
||||||
|
|
||||||
unity["command"] = uvxPath;
|
var toolArgs = BuildUvxArgs(fromUrl, packageName);
|
||||||
|
|
||||||
var args = new List<string> { packageName };
|
if (ShouldUseWindowsCmdShim(client))
|
||||||
if (!string.IsNullOrEmpty(fromUrl))
|
|
||||||
{
|
{
|
||||||
args.Insert(0, fromUrl);
|
unity["command"] = ResolveCmdPath();
|
||||||
args.Insert(0, "--from");
|
|
||||||
|
var cmdArgs = new List<string> { "/c", uvxPath };
|
||||||
|
cmdArgs.AddRange(toolArgs);
|
||||||
|
|
||||||
|
unity["args"] = JArray.FromObject(cmdArgs.ToArray());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
unity["command"] = uvxPath;
|
||||||
|
unity["args"] = JArray.FromObject(toolArgs.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
args.Add("--transport");
|
|
||||||
args.Add("stdio");
|
|
||||||
|
|
||||||
unity["args"] = JArray.FromObject(args.ToArray());
|
|
||||||
|
|
||||||
// Remove url/serverUrl if they exist from previous config
|
// Remove url/serverUrl if they exist from previous config
|
||||||
if (unity["url"] != null) unity.Remove("url");
|
if (unity["url"] != null) unity.Remove("url");
|
||||||
if (unity["serverUrl"] != null) unity.Remove("serverUrl");
|
if (unity["serverUrl"] != null) unity.Remove("serverUrl");
|
||||||
foreach (var prop in urlPropsToRemove)
|
|
||||||
{
|
|
||||||
if (unity[prop] != null) unity.Remove(prop);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isVSCode)
|
if (isVSCode)
|
||||||
{
|
{
|
||||||
|
|
@ -145,5 +148,44 @@ namespace MCPForUnity.Editor.Helpers
|
||||||
parent[name] = created;
|
parent[name] = created;
|
||||||
return created;
|
return created;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IList<string> BuildUvxArgs(string fromUrl, string packageName)
|
||||||
|
{
|
||||||
|
var args = new List<string> { packageName };
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(fromUrl))
|
||||||
|
{
|
||||||
|
args.Insert(0, fromUrl);
|
||||||
|
args.Insert(0, "--from");
|
||||||
|
}
|
||||||
|
|
||||||
|
args.Add("--transport");
|
||||||
|
args.Add("stdio");
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ShouldUseWindowsCmdShim(McpClient client)
|
||||||
|
{
|
||||||
|
if (client == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Application.platform == RuntimePlatform.WindowsEditor &&
|
||||||
|
string.Equals(client.name, ClaudeDesktopConfigurator.ClientName, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ResolveCmdPath()
|
||||||
|
{
|
||||||
|
var comSpec = Environment.GetEnvironmentVariable("ComSpec");
|
||||||
|
if (!string.IsNullOrEmpty(comSpec) && File.Exists(comSpec))
|
||||||
|
{
|
||||||
|
return comSpec;
|
||||||
|
}
|
||||||
|
|
||||||
|
string system32Cmd = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "cmd.exe");
|
||||||
|
return File.Exists(system32Cmd) ? system32Cmd : "cmd.exe";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
@ -34,6 +35,12 @@ namespace MCPForUnity.Editor.Services
|
||||||
McpLog.Debug("No uvx path override found, falling back to default command");
|
McpLog.Debug("No uvx path override found, falling back to default command");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string discovered = ResolveUvxFromSystem();
|
||||||
|
if (!string.IsNullOrEmpty(discovered))
|
||||||
|
{
|
||||||
|
return discovered;
|
||||||
|
}
|
||||||
|
|
||||||
return "uvx";
|
return "uvx";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,6 +130,81 @@ namespace MCPForUnity.Editor.Services
|
||||||
return !string.IsNullOrEmpty(GetClaudeCliPath());
|
return !string.IsNullOrEmpty(GetClaudeCliPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string ResolveUvxFromSystem()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (string candidate in EnumerateUvxCandidates())
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(candidate) && File.Exists(candidate))
|
||||||
|
{
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// fall back to bare command
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> EnumerateUvxCandidates()
|
||||||
|
{
|
||||||
|
string exeName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "uvx.exe" : "uvx";
|
||||||
|
|
||||||
|
string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||||
|
if (!string.IsNullOrEmpty(home))
|
||||||
|
{
|
||||||
|
yield return Path.Combine(home, ".local", "bin", exeName);
|
||||||
|
yield return Path.Combine(home, ".cargo", "bin", exeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||||
|
{
|
||||||
|
yield return "/opt/homebrew/bin/" + exeName;
|
||||||
|
yield return "/usr/local/bin/" + exeName;
|
||||||
|
}
|
||||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||||
|
{
|
||||||
|
yield return "/usr/local/bin/" + exeName;
|
||||||
|
yield return "/usr/bin/" + exeName;
|
||||||
|
}
|
||||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||||
|
string programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(localAppData))
|
||||||
|
{
|
||||||
|
yield return Path.Combine(localAppData, "Programs", "uv", exeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(programFiles))
|
||||||
|
{
|
||||||
|
yield return Path.Combine(programFiles, "uv", exeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string pathEnv = Environment.GetEnvironmentVariable("PATH");
|
||||||
|
if (!string.IsNullOrEmpty(pathEnv))
|
||||||
|
{
|
||||||
|
foreach (string rawDir in pathEnv.Split(Path.PathSeparator))
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(rawDir)) continue;
|
||||||
|
string dir = rawDir.Trim();
|
||||||
|
yield return Path.Combine(dir, exeName);
|
||||||
|
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
// Some PATH entries may already contain the file without extension
|
||||||
|
yield return Path.Combine(dir, "uvx");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void SetUvxPathOverride(string path)
|
public void SetUvxPathOverride(string path)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(path))
|
if (string.IsNullOrEmpty(path))
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using MCPForUnity.Editor.Clients;
|
using MCPForUnity.Editor.Clients;
|
||||||
using MCPForUnity.Editor.Helpers;
|
using MCPForUnity.Editor.Helpers;
|
||||||
using MCPForUnity.Editor.Models;
|
using MCPForUnity.Editor.Models;
|
||||||
|
|
@ -38,6 +39,9 @@ namespace MCPForUnity.Editor.Windows.Components.ClientConfig
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
private readonly List<IMcpClientConfigurator> configurators;
|
private readonly List<IMcpClientConfigurator> configurators;
|
||||||
|
private readonly Dictionary<IMcpClientConfigurator, DateTime> lastStatusChecks = new();
|
||||||
|
private readonly HashSet<IMcpClientConfigurator> statusRefreshInFlight = new();
|
||||||
|
private static readonly TimeSpan StatusRefreshInterval = TimeSpan.FromSeconds(45);
|
||||||
private int selectedClientIndex = 0;
|
private int selectedClientIndex = 0;
|
||||||
|
|
||||||
public VisualElement Root { get; private set; }
|
public VisualElement Root { get; private set; }
|
||||||
|
|
@ -105,33 +109,7 @@ namespace MCPForUnity.Editor.Windows.Components.ClientConfig
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var client = configurators[selectedClientIndex];
|
var client = configurators[selectedClientIndex];
|
||||||
MCPServiceLocator.Client.CheckClientStatus(client);
|
RefreshClientStatus(client);
|
||||||
|
|
||||||
clientStatusLabel.text = GetStatusDisplayString(client.Status);
|
|
||||||
clientStatusLabel.style.color = StyleKeyword.Null;
|
|
||||||
|
|
||||||
clientStatusIndicator.RemoveFromClassList("configured");
|
|
||||||
clientStatusIndicator.RemoveFromClassList("not-configured");
|
|
||||||
clientStatusIndicator.RemoveFromClassList("warning");
|
|
||||||
|
|
||||||
switch (client.Status)
|
|
||||||
{
|
|
||||||
case McpStatus.Configured:
|
|
||||||
case McpStatus.Running:
|
|
||||||
case McpStatus.Connected:
|
|
||||||
clientStatusIndicator.AddToClassList("configured");
|
|
||||||
break;
|
|
||||||
case McpStatus.IncorrectPath:
|
|
||||||
case McpStatus.CommunicationError:
|
|
||||||
case McpStatus.NoResponse:
|
|
||||||
clientStatusIndicator.AddToClassList("warning");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
clientStatusIndicator.AddToClassList("not-configured");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
configureButton.text = client.GetConfigureActionLabel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetStatusDisplayString(McpStatus status)
|
private string GetStatusDisplayString(McpStatus status)
|
||||||
|
|
@ -240,7 +218,8 @@ namespace MCPForUnity.Editor.Windows.Components.ClientConfig
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
MCPServiceLocator.Client.ConfigureClient(client);
|
MCPServiceLocator.Client.ConfigureClient(client);
|
||||||
UpdateClientStatus();
|
lastStatusChecks.Remove(client);
|
||||||
|
RefreshClientStatus(client, forceImmediate: true);
|
||||||
UpdateManualConfiguration();
|
UpdateManualConfiguration();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
@ -314,11 +293,140 @@ namespace MCPForUnity.Editor.Windows.Components.ClientConfig
|
||||||
if (selectedClientIndex >= 0 && selectedClientIndex < configurators.Count)
|
if (selectedClientIndex >= 0 && selectedClientIndex < configurators.Count)
|
||||||
{
|
{
|
||||||
var client = configurators[selectedClientIndex];
|
var client = configurators[selectedClientIndex];
|
||||||
MCPServiceLocator.Client.CheckClientStatus(client);
|
RefreshClientStatus(client, forceImmediate: true);
|
||||||
UpdateClientStatus();
|
|
||||||
UpdateManualConfiguration();
|
UpdateManualConfiguration();
|
||||||
UpdateClaudeCliPathVisibility();
|
UpdateClaudeCliPathVisibility();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RefreshClientStatus(IMcpClientConfigurator client, bool forceImmediate = false)
|
||||||
|
{
|
||||||
|
if (client is ClaudeCliMcpConfigurator)
|
||||||
|
{
|
||||||
|
RefreshClaudeCliStatus(client, forceImmediate);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (forceImmediate || ShouldRefreshClient(client))
|
||||||
|
{
|
||||||
|
MCPServiceLocator.Client.CheckClientStatus(client);
|
||||||
|
lastStatusChecks[client] = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyStatusToUi(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshClaudeCliStatus(IMcpClientConfigurator client, bool forceImmediate)
|
||||||
|
{
|
||||||
|
if (forceImmediate)
|
||||||
|
{
|
||||||
|
MCPServiceLocator.Client.CheckClientStatus(client, attemptAutoRewrite: false);
|
||||||
|
lastStatusChecks[client] = DateTime.UtcNow;
|
||||||
|
ApplyStatusToUi(client);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasStatus = lastStatusChecks.ContainsKey(client);
|
||||||
|
bool needsRefresh = !hasStatus || ShouldRefreshClient(client);
|
||||||
|
|
||||||
|
if (!hasStatus)
|
||||||
|
{
|
||||||
|
ApplyStatusToUi(client, showChecking: true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ApplyStatusToUi(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsRefresh && !statusRefreshInFlight.Contains(client))
|
||||||
|
{
|
||||||
|
statusRefreshInFlight.Add(client);
|
||||||
|
ApplyStatusToUi(client, showChecking: true);
|
||||||
|
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
MCPServiceLocator.Client.CheckClientStatus(client, attemptAutoRewrite: false);
|
||||||
|
}).ContinueWith(t =>
|
||||||
|
{
|
||||||
|
bool faulted = false;
|
||||||
|
string errorMessage = null;
|
||||||
|
if (t.IsFaulted && t.Exception != null)
|
||||||
|
{
|
||||||
|
var baseException = t.Exception.GetBaseException();
|
||||||
|
errorMessage = baseException?.Message ?? "Status check failed";
|
||||||
|
McpLog.Error($"Failed to refresh Claude CLI status: {errorMessage}");
|
||||||
|
faulted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorApplication.delayCall += () =>
|
||||||
|
{
|
||||||
|
statusRefreshInFlight.Remove(client);
|
||||||
|
lastStatusChecks[client] = DateTime.UtcNow;
|
||||||
|
if (faulted)
|
||||||
|
{
|
||||||
|
if (client is McpClientConfiguratorBase baseConfigurator)
|
||||||
|
{
|
||||||
|
baseConfigurator.Client.SetStatus(McpStatus.Error, errorMessage ?? "Status check failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ApplyStatusToUi(client);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ShouldRefreshClient(IMcpClientConfigurator client)
|
||||||
|
{
|
||||||
|
if (!lastStatusChecks.TryGetValue(client, out var last))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (DateTime.UtcNow - last) > StatusRefreshInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyStatusToUi(IMcpClientConfigurator client, bool showChecking = false)
|
||||||
|
{
|
||||||
|
if (selectedClientIndex < 0 || selectedClientIndex >= configurators.Count)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!ReferenceEquals(configurators[selectedClientIndex], client))
|
||||||
|
return;
|
||||||
|
|
||||||
|
clientStatusIndicator.RemoveFromClassList("configured");
|
||||||
|
clientStatusIndicator.RemoveFromClassList("not-configured");
|
||||||
|
clientStatusIndicator.RemoveFromClassList("warning");
|
||||||
|
|
||||||
|
if (showChecking)
|
||||||
|
{
|
||||||
|
clientStatusLabel.text = "Checking...";
|
||||||
|
clientStatusLabel.style.color = StyleKeyword.Null;
|
||||||
|
clientStatusIndicator.AddToClassList("warning");
|
||||||
|
configureButton.text = client.GetConfigureActionLabel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clientStatusLabel.text = GetStatusDisplayString(client.Status);
|
||||||
|
clientStatusLabel.style.color = StyleKeyword.Null;
|
||||||
|
|
||||||
|
switch (client.Status)
|
||||||
|
{
|
||||||
|
case McpStatus.Configured:
|
||||||
|
case McpStatus.Running:
|
||||||
|
case McpStatus.Connected:
|
||||||
|
clientStatusIndicator.AddToClassList("configured");
|
||||||
|
break;
|
||||||
|
case McpStatus.IncorrectPath:
|
||||||
|
case McpStatus.CommunicationError:
|
||||||
|
case McpStatus.NoResponse:
|
||||||
|
clientStatusIndicator.AddToClassList("warning");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
clientStatusIndicator.AddToClassList("not-configured");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
configureButton.text = client.GetConfigureActionLabel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using UnityEditor;
|
||||||
using MCPForUnity.Editor.Helpers;
|
using MCPForUnity.Editor.Helpers;
|
||||||
using MCPForUnity.Editor.Models;
|
using MCPForUnity.Editor.Models;
|
||||||
using MCPForUnity.Editor.Constants;
|
using MCPForUnity.Editor.Constants;
|
||||||
|
using MCPForUnity.Editor.Services;
|
||||||
|
|
||||||
namespace MCPForUnityTests.Editor.Helpers
|
namespace MCPForUnityTests.Editor.Helpers
|
||||||
{
|
{
|
||||||
|
|
@ -167,6 +168,40 @@ namespace MCPForUnityTests.Editor.Helpers
|
||||||
AssertTransportConfiguration(unity, client);
|
AssertTransportConfiguration(unity, client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ClaudeDesktop_UsesAbsoluteUvPath_WhenOverrideProvided()
|
||||||
|
{
|
||||||
|
var configPath = Path.Combine(_tempRoot, "claude-desktop.json");
|
||||||
|
WriteInitialConfig(configPath, isVSCode: false, command: "uvx", directory: "/old/path");
|
||||||
|
|
||||||
|
WithTransportPreference(false, () =>
|
||||||
|
{
|
||||||
|
MCPServiceLocator.Paths.SetUvxPathOverride(_fakeUvPath);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var client = new McpClient
|
||||||
|
{
|
||||||
|
name = "Claude Desktop",
|
||||||
|
SupportsHttpTransport = false,
|
||||||
|
StripEnvWhenNotRequired = true
|
||||||
|
};
|
||||||
|
|
||||||
|
InvokeWriteToConfig(configPath, client);
|
||||||
|
|
||||||
|
var root = JObject.Parse(File.ReadAllText(configPath));
|
||||||
|
var unity = (JObject)root.SelectToken("mcpServers.unityMCP");
|
||||||
|
Assert.NotNull(unity, "Expected mcpServers.unityMCP node");
|
||||||
|
Assert.AreEqual(_fakeUvPath, (string)unity["command"], "Claude Desktop should use absolute uvx path");
|
||||||
|
Assert.IsNull(unity["env"], "Claude Desktop config should not include env block when not required");
|
||||||
|
AssertTransportConfiguration(unity, client);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
MCPServiceLocator.Paths.ClearUvxPathOverride();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void PreservesExistingEnvAndDisabled_ForKiro()
|
public void PreservesExistingEnvAndDisabled_ForKiro()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue