Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
28 changes: 24 additions & 4 deletions MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,7 @@ public void ConfigureWithCapturedValues(
string projectDir, string claudePath, string pathPrepend,
bool useHttpTransport, string httpUrl,
string uvxPath, string fromArgs, string packageName, bool shouldForceRefresh,
bool shouldUseOffline,
string apiKey,
Models.ConfiguredTransport serverTransport)
{
Expand All @@ -753,7 +754,7 @@ public void ConfigureWithCapturedValues(
{
RegisterWithCapturedValues(projectDir, claudePath, pathPrepend,
useHttpTransport, httpUrl, uvxPath, fromArgs, packageName, shouldForceRefresh,
apiKey, serverTransport);
shouldUseOffline, apiKey, serverTransport);
}
}

Expand All @@ -764,6 +765,7 @@ private void RegisterWithCapturedValues(
string projectDir, string claudePath, string pathPrepend,
bool useHttpTransport, string httpUrl,
string uvxPath, string fromArgs, string packageName, bool shouldForceRefresh,
bool shouldUseOffline,
string apiKey,
Models.ConfiguredTransport serverTransport)
{
Expand All @@ -790,7 +792,13 @@ private void RegisterWithCapturedValues(
else
{
// Note: --reinstall is not supported by uvx, use --no-cache --refresh instead
string devFlags = shouldForceRefresh ? "--no-cache --refresh " : string.Empty;
string devFlags;
if (shouldForceRefresh)
devFlags = "--no-cache --refresh ";
else if (shouldUseOffline)
devFlags = "--offline ";
else
devFlags = string.Empty;
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
Outdated
// Use --scope local to register in the project-local config, avoiding conflicts with user-level config (#664)
args = $"mcp add --scope local --transport stdio UnityMCP -- \"{uvxPath}\" {devFlags}{fromArgs} {packageName}";
}
Expand Down Expand Up @@ -869,7 +877,13 @@ private void Register()
var (uvxPath, _, packageName) = AssetPathUtility.GetUvxCommandParts();
// Use central helper that checks both DevModeForceServerRefresh AND local path detection.
// Note: --reinstall is not supported by uvx, use --no-cache --refresh instead
string devFlags = AssetPathUtility.ShouldForceUvxRefresh() ? "--no-cache --refresh " : string.Empty;
string devFlags;
if (AssetPathUtility.ShouldForceUvxRefresh())
devFlags = "--no-cache --refresh ";
else if (AssetPathUtility.ShouldUseUvxOffline())
devFlags = "--offline ";
else
devFlags = string.Empty;
string fromArgs = AssetPathUtility.GetBetaServerFromArgs(quoteFromPath: true);
// Use --scope local to register in the project-local config, avoiding conflicts with user-level config (#664)
args = $"mcp add --scope local --transport stdio UnityMCP -- \"{uvxPath}\" {devFlags}{fromArgs} {packageName}";
Expand Down Expand Up @@ -979,7 +993,13 @@ public override string GetManualSnippet()

// Use central helper that checks both DevModeForceServerRefresh AND local path detection.
// Note: --reinstall is not supported by uvx, use --no-cache --refresh instead
string devFlags = AssetPathUtility.ShouldForceUvxRefresh() ? "--no-cache --refresh " : string.Empty;
string devFlags;
if (AssetPathUtility.ShouldForceUvxRefresh())
devFlags = "--no-cache --refresh ";
else if (AssetPathUtility.ShouldUseUvxOffline())
devFlags = "--offline ";
else
devFlags = string.Empty;
string fromArgs = AssetPathUtility.GetBetaServerFromArgs(quoteFromPath: true);

return "# Register the MCP server with Claude Code:\n" +
Expand Down
31 changes: 31 additions & 0 deletions MCPForUnity/Editor/Helpers/AssetPathUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,37 @@ public static bool ShouldForceUvxRefresh()
return IsLocalServerPath();
}

/// <summary>
/// Determines whether uvx should use --offline mode for faster startup.
/// Runs a lightweight probe (uvx --offline ... mcp-for-unity --help) with a 3-second timeout
/// to check if the package is already cached. If cached, --offline skips the network
/// dependency check that can hang for 30+ seconds on poor connections.
/// Returns false if force refresh is enabled (new download needed).
/// </summary>
public static bool ShouldUseUvxOffline()
{
if (ShouldForceUvxRefresh())
return false;

try
{
string uvxPath = MCPServiceLocator.Paths.GetUvxPath();
if (string.IsNullOrEmpty(uvxPath))
return false;

string fromArgs = GetBetaServerFromArgs(quoteFromPath: false);
string probeArgs = string.IsNullOrEmpty(fromArgs)
? "--offline mcp-for-unity --help"
: $"--offline {fromArgs} mcp-for-unity --help";

return ExecPath.TryRun(uvxPath, probeArgs, null, out _, out _, timeoutMs: 3000);
}
catch
{
return false;
}
}

/// <summary>
/// Returns true if the server URL is a local path (file:// or absolute path).
/// </summary>
Expand Down
18 changes: 12 additions & 6 deletions MCPForUnity/Editor/Helpers/CodexConfigHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,20 @@ namespace MCPForUnity.Editor.Helpers
/// </summary>
public static class CodexConfigHelper
{
private static void AddDevModeArgs(TomlArray args)
private static void AddUvxModeFlags(TomlArray args)
{
if (args == null) return;
// Use central helper that checks both DevModeForceServerRefresh AND local path detection.
// Note: --reinstall is not supported by uvx, use --no-cache --refresh instead
if (!AssetPathUtility.ShouldForceUvxRefresh()) return;
args.Add(new TomlString { Value = "--no-cache" });
args.Add(new TomlString { Value = "--refresh" });
if (AssetPathUtility.ShouldForceUvxRefresh())
{
args.Add(new TomlString { Value = "--no-cache" });
args.Add(new TomlString { Value = "--refresh" });
}
else if (AssetPathUtility.ShouldUseUvxOffline())
{
args.Add(new TomlString { Value = "--offline" });
}
}

public static string BuildCodexServerBlock(string uvPath)
Expand Down Expand Up @@ -53,7 +59,7 @@ public static string BuildCodexServerBlock(string uvPath)
unityMCP["command"] = uvxPath;

var args = new TomlArray();
AddDevModeArgs(args);
AddUvxModeFlags(args);
// Use centralized helper for beta server / prerelease args
foreach (var arg in AssetPathUtility.GetBetaServerFromArgsList())
{
Expand Down Expand Up @@ -205,7 +211,7 @@ private static TomlTable CreateUnityMcpTable(string uvPath)
unityMCP["command"] = new TomlString { Value = uvxPath };

var argsArray = new TomlArray();
AddDevModeArgs(argsArray);
AddUvxModeFlags(argsArray);
// Use centralized helper for beta server / prerelease args
foreach (var arg in AssetPathUtility.GetBetaServerFromArgsList())
{
Expand Down
4 changes: 4 additions & 0 deletions MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,10 @@ private static IList<string> BuildUvxArgs(string fromUrl, string packageName)
args.Add("--no-cache");
args.Add("--refresh");
}
else if (AssetPathUtility.ShouldUseUvxOffline())
{
args.Add("--offline");
}

// Use centralized helper for beta server / prerelease args
foreach (var arg in AssetPathUtility.GetBetaServerFromArgsList())
Expand Down
8 changes: 7 additions & 1 deletion MCPForUnity/Editor/Services/Server/ServerCommandBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,13 @@ public bool TryBuildCommand(out string fileName, out string arguments, out strin

// Use central helper that checks both DevModeForceServerRefresh AND local path detection.
// Note: --reinstall is not supported by uvx, use --no-cache --refresh instead
string devFlags = AssetPathUtility.ShouldForceUvxRefresh() ? "--no-cache --refresh " : string.Empty;
string devFlags;
if (AssetPathUtility.ShouldForceUvxRefresh())
devFlags = "--no-cache --refresh ";
else if (AssetPathUtility.ShouldUseUvxOffline())
devFlags = "--offline ";
else
devFlags = string.Empty;
bool projectScopedTools = EditorPrefs.GetBool(
EditorPrefKeys.ProjectScopedToolsLocalHttp,
true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ private void ConfigureClaudeCliAsync(IMcpClientConfigurator client)
var (uvxPath, _, packageName) = AssetPathUtility.GetUvxCommandParts();
string fromArgs = AssetPathUtility.GetBetaServerFromArgs(quoteFromPath: true);
bool shouldForceRefresh = AssetPathUtility.ShouldForceUvxRefresh();
bool shouldUseOffline = AssetPathUtility.ShouldUseUvxOffline();
string apiKey = EditorPrefs.GetString(EditorPrefKeys.ApiKey, string.Empty);

// Compute pathPrepend on main thread
Expand Down Expand Up @@ -332,7 +333,7 @@ private void ConfigureClaudeCliAsync(IMcpClientConfigurator client)
projectDir, claudePath, pathPrepend,
useHttpTransport, httpUrl,
uvxPath, fromArgs, packageName, shouldForceRefresh,
apiKey, serverTransport);
shouldUseOffline, apiKey, serverTransport);
}
return (success: true, error: (string)null);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using NUnit.Framework;
using MCPForUnity.Editor.Helpers;
using MCPForUnity.Editor.Constants;
using UnityEditor;

namespace MCPForUnityTests.Editor.Helpers
{
public class AssetPathUtilityOfflineTests
{
private bool _originalForceRefresh;

[SetUp]
public void SetUp()
{
_originalForceRefresh = EditorPrefs.GetBool(EditorPrefKeys.DevModeForceServerRefresh, false);
}

[TearDown]
public void TearDown()
{
EditorPrefs.SetBool(EditorPrefKeys.DevModeForceServerRefresh, _originalForceRefresh);
}

[Test]
public void ShouldUseUvxOffline_WhenForceRefreshEnabled_ReturnsFalse()
{
EditorPrefs.SetBool(EditorPrefKeys.DevModeForceServerRefresh, true);
Assert.IsFalse(AssetPathUtility.ShouldUseUvxOffline());
}

[Test]
public void ShouldUseUvxOffline_DoesNotThrow()
{
EditorPrefs.SetBool(EditorPrefKeys.DevModeForceServerRefresh, false);
Assert.DoesNotThrow(() => AssetPathUtility.ShouldUseUvxOffline());
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -323,10 +323,11 @@ public void SetComponentProperties_ContinuesAfterException()
};

// Expect the error logs from the invalid property
// Note: PropertyConversion logs "Error converting token to..." when conversion fails
// Note: PropertyConversion logs "Error converting token to..." when conversion fails,
// then ComponentOps catches the exception and returns an error string (no second Error log).
// GameObjectComponentHelpers logs the failure as a warning.
LogAssert.Expect(LogType.Error, new System.Text.RegularExpressions.Regex("Error converting token to UnityEngine.Vector3"));
LogAssert.Expect(LogType.Error, new System.Text.RegularExpressions.Regex(@"\[SetProperty\].*Failed to set 'velocity'"));
LogAssert.Expect(LogType.Warning, new System.Text.RegularExpressions.Regex("Property 'velocity' not found"));
LogAssert.Expect(LogType.Warning, new System.Text.RegularExpressions.Regex(@"\[ManageGameObject\].*Failed to set property 'velocity'"));

// Act
var result = ManageGameObject.HandleCommand(setPropertiesParams);
Expand Down