Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
204e95a
fix: resolve UV path override not being detected in System Requirements
whatevertogo Jan 11, 2026
25e5d05
fix: improve uv/uvx detection robustness on macOS and Linux
whatevertogo Jan 11, 2026
cc22320
Merge branch 'CoplayDev:main' into main
whatevertogo Jan 11, 2026
8d5fa2f
refactor: unify process execution with ExecPath.TryRun and add Window…
whatevertogo Jan 11, 2026
84cee8c
fix: improve version parsing to handle both spaces and parentheses
whatevertogo Jan 11, 2026
510e631
refactor: improve platform detectors with absolute path resolution
whatevertogo Jan 11, 2026
bf41479
fix: improve error handling in PathResolverService by logging exceptions
whatevertogo Jan 11, 2026
f39857c
Remove .meta files added after fork and update .gitignore
whatevertogo Jan 11, 2026
c4be11c
Merge branch 'CoplayDev:main' into main
whatevertogo Jan 12, 2026
1832715
Update .gitignore
whatevertogo Jan 12, 2026
554ddd0
save .meta
whatevertogo Jan 12, 2026
fb5909d
refactor: unify uv/uvx naming and path detection across platforms
whatevertogo Jan 12, 2026
254125a
fix: improve validation light(uvxPathStatus) logic for UVX path overr…
whatevertogo Jan 12, 2026
f9ae5d5
refactor: streamline UV version validation and unify path detection m…
whatevertogo Jan 12, 2026
84cb9c6
fix: add type handling for Claude Code client in config JSON builder
whatevertogo Jan 12, 2026
ee33077
fix: correct command from 'uvx' to 'uv' for Python version listing in…
whatevertogo Jan 12, 2026
2c3ebcd
Merge branch 'CoplayDev:main' into main
whatevertogo Jan 14, 2026
66fe194
Merge branch 'CoplayDev:main' into main
whatevertogo Jan 15, 2026
79bce47
Merge branch 'CoplayDev:main' into main
whatevertogo Jan 16, 2026
1877cc2
feat: add uvx path fallback with warning UI
whatevertogo Jan 17, 2026
f9b0563
refactor: remove GetDetails method from PlatformDetectorBase
whatevertogo Jan 17, 2026
c86eb78
Update ExecPath.cs
Scriptwonder Jan 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Runtime.InteropServices;
using MCPForUnity.Editor.Dependencies.Models;
using MCPForUnity.Editor.Helpers;
using MCPForUnity.Editor.Services;

namespace MCPForUnity.Editor.Dependencies.PlatformDetectors
{
Expand Down Expand Up @@ -92,21 +93,36 @@ public override string GetInstallationRecommendations()

public override DependencyStatus DetectUv()
{
var status = new DependencyStatus("uv Package Manager", isRequired: true)
// First, honor overrides and cross-platform resolution via the base implementation
var status = base.DetectUv();
if (status.IsAvailable)
{
InstallationHint = GetUvInstallUrl()
};
return status;
}

// If the user configured an override path, keep the base result (failure typically means the override path is invalid)
if (MCPServiceLocator.Paths.HasUvxPathOverride)
{
return status;
}

try
{
// Try running uv/uvx directly with augmented PATH
if (TryValidateUv("uv", out string version, out string fullPath) ||
TryValidateUv("uvx", out version, out fullPath))
{
// If we validated via bare command, resolve to absolute path
if (fullPath == "uv" && TryFindInPath("uv", out var resolvedUv))
fullPath = resolvedUv;
else if (fullPath == "uvx" && TryFindInPath("uvx", out var resolvedUvx))
fullPath = resolvedUvx;

status.IsAvailable = true;
status.Version = version;
status.Path = fullPath;
status.Details = $"Found uv {version} in PATH";
status.ErrorMessage = null;
return status;
}

Expand All @@ -120,6 +136,7 @@ public override DependencyStatus DetectUv()
status.Version = version;
status.Path = fullPath;
status.Details = $"Found uv {version} in PATH";
status.ErrorMessage = null;
return status;
}
}
Expand Down Expand Up @@ -214,14 +231,41 @@ private bool TryValidateUv(string uvPath, out string version, out string fullPat
using var process = Process.Start(psi);
if (process == null) return false;

string output = process.StandardOutput.ReadToEnd().Trim();
process.WaitForExit(5000);
// Read both streams to avoid missing output when tools write version to stderr
string stdout = process.StandardOutput.ReadToEnd();
string stderr = process.StandardError.ReadToEnd();

// Respect timeout - check return value
if (!process.WaitForExit(5000))
return false;

// Use stdout first, fallback to stderr (some tools output to stderr)
string output = string.IsNullOrWhiteSpace(stdout) ? stderr.Trim() : stdout.Trim();

if (process.ExitCode == 0 && output.StartsWith("uv "))
if (process.ExitCode == 0 && (output.StartsWith("uv ") || output.StartsWith("uvx ")))
{
version = output.Substring(3).Trim();
fullPath = uvPath;
return true;
// Extract version: "uvx 0.9.18" -> "0.9.18"
// Handle extra tokens: "uvx 0.9.18 (build info)" -> "0.9.18"
int spaceIndex = output.IndexOf(' ');
if (spaceIndex >= 0)
{
var remainder = output.Substring(spaceIndex + 1).Trim();
// Extract only the version number (up to next space or parenthesis)
int nextSpace = remainder.IndexOf(' ');
int parenIndex = remainder.IndexOf('(');
int endIndex = -1;

if (nextSpace >= 0 && parenIndex >= 0)
endIndex = Math.Min(nextSpace, parenIndex);
else if (nextSpace >= 0)
endIndex = nextSpace;
else if (parenIndex >= 0)
endIndex = parenIndex;

version = endIndex >= 0 ? remainder.Substring(0, endIndex).Trim() : remainder;
fullPath = uvPath;
return true;
}
}
}
catch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Runtime.InteropServices;
using MCPForUnity.Editor.Dependencies.Models;
using MCPForUnity.Editor.Helpers;
using MCPForUnity.Editor.Services;

namespace MCPForUnity.Editor.Dependencies.PlatformDetectors
{
Expand Down Expand Up @@ -90,21 +91,36 @@ public override string GetInstallationRecommendations()

public override DependencyStatus DetectUv()
{
var status = new DependencyStatus("uv Package Manager", isRequired: true)
// First, honor overrides and cross-platform resolution via the base implementation
var status = base.DetectUv();
if (status.IsAvailable)
{
InstallationHint = GetUvInstallUrl()
};
return status;
}

// If the user provided an override path, keep the base result (failure likely means the override is invalid)
if (MCPServiceLocator.Paths.HasUvxPathOverride)
{
return status;
}

try
{
// Try running uv/uvx directly with augmented PATH
if (TryValidateUv("uv", out string version, out string fullPath) ||
TryValidateUv("uvx", out version, out fullPath))
{
// If we validated via bare command, resolve to absolute path
if (fullPath == "uv" && TryFindInPath("uv", out var resolvedUv))
fullPath = resolvedUv;
else if (fullPath == "uvx" && TryFindInPath("uvx", out var resolvedUvx))
fullPath = resolvedUvx;

status.IsAvailable = true;
status.Version = version;
status.Path = fullPath;
status.Details = $"Found uv {version} in PATH";
status.ErrorMessage = null;
return status;
}

Expand All @@ -118,6 +134,7 @@ public override DependencyStatus DetectUv()
status.Version = version;
status.Path = fullPath;
status.Details = $"Found uv {version} in PATH";
status.ErrorMessage = null;
return status;
}
}
Expand Down Expand Up @@ -212,14 +229,41 @@ private bool TryValidateUv(string uvPath, out string version, out string fullPat
using var process = Process.Start(psi);
if (process == null) return false;

string output = process.StandardOutput.ReadToEnd().Trim();
process.WaitForExit(5000);
// Read both streams to avoid missing output when tools write version to stderr
string stdout = process.StandardOutput.ReadToEnd();
string stderr = process.StandardError.ReadToEnd();

// Respect timeout - check return value
if (!process.WaitForExit(5000))
return false;

// Use stdout first, fallback to stderr (some tools output to stderr)
string output = string.IsNullOrWhiteSpace(stdout) ? stderr.Trim() : stdout.Trim();

if (process.ExitCode == 0 && output.StartsWith("uv "))
if (process.ExitCode == 0 && (output.StartsWith("uv ") || output.StartsWith("uvx ")))
{
version = output.Substring(3).Trim();
fullPath = uvPath;
return true;
// Extract version: "uvx 0.9.18" -> "0.9.18"
// Handle extra tokens: "uvx 0.9.18 (Homebrew 2025-01-01)" -> "0.9.18"
int spaceIndex = output.IndexOf(' ');
if (spaceIndex >= 0)
{
var remainder = output.Substring(spaceIndex + 1).Trim();
// Extract only the version number (up to next space or parenthesis)
int nextSpace = remainder.IndexOf(' ');
int parenIndex = remainder.IndexOf('(');
int endIndex = -1;

if (nextSpace >= 0 && parenIndex >= 0)
endIndex = Math.Min(nextSpace, parenIndex);
else if (nextSpace >= 0)
endIndex = nextSpace;
else if (parenIndex >= 0)
endIndex = parenIndex;

version = endIndex >= 0 ? remainder.Substring(0, endIndex).Trim() : remainder;
fullPath = uvPath;
return true;
}
}
}
catch
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;
using System.Diagnostics;
using MCPForUnity.Editor.Dependencies.Models;
using MCPForUnity.Editor.Services;

namespace MCPForUnity.Editor.Dependencies.PlatformDetectors
{
Expand All @@ -26,18 +26,23 @@ public virtual DependencyStatus DetectUv()

try
{
// Try to find uv/uvx in PATH
if (TryFindUvInPath(out string uvPath, out string version))
// Get uv path from PathResolverService (respects override)
string uvPath = MCPServiceLocator.Paths.GetUvxPath();

// Verify uv executable and get version
if (MCPServiceLocator.Paths.TryValidateUvExecutable(uvPath, out string version))
{
status.IsAvailable = true;
status.Version = version;
status.Path = uvPath;
status.Details = $"Found uv {version} in PATH";
status.Details = MCPServiceLocator.Paths.HasUvxPathOverride
? $"Found uv {version} (override path)"
: $"Found uv {version} in system path";
return status;
}

status.ErrorMessage = "uv not found in PATH";
status.Details = "Install uv package manager and ensure it's added to PATH.";
status.ErrorMessage = "uv not found";
status.Details = "Install uv package manager or configure path override in Advanced Settings.";
}
catch (Exception ex)
{
Expand All @@ -47,50 +52,6 @@ public virtual DependencyStatus DetectUv()
return status;
}

protected bool TryFindUvInPath(out string uvPath, out string version)
{
uvPath = null;
version = null;

// Try common uv command names
var commands = new[] { "uvx", "uv" };

foreach (var cmd in commands)
{
try
{
var psi = new ProcessStartInfo
{
FileName = cmd,
Arguments = "--version",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};

using var process = Process.Start(psi);
if (process == null) continue;

string output = process.StandardOutput.ReadToEnd().Trim();
process.WaitForExit(5000);

if (process.ExitCode == 0 && output.StartsWith("uv "))
{
version = output.Substring(3).Trim();
uvPath = cmd;
return true;
}
}
catch
{
// Try next command
}
}

return false;
}

protected bool TryParseVersion(string version, out int major, out int minor)
{
major = 0;
Expand Down
8 changes: 8 additions & 0 deletions MCPForUnity/Editor/Services/IPathResolverService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,13 @@ public interface IPathResolverService
/// Gets whether a Claude CLI path override is active
/// </summary>
bool HasClaudeCliPathOverride { get; }

/// <summary>
/// Validates the provided uv executable by running "--version" and parsing the output.
/// </summary>
/// <param name="uvPath">Absolute or relative path to the uv/uvx executable.</param>
/// <param name="version">Parsed version string if successful.</param>
/// <returns>True when the executable runs and returns a uv version string.</returns>
bool TryValidateUvExecutable(string uvPath, out string version);
}
}
Loading