From 7128a357fb261570e62dcef3f8b3f98d01320531 Mon Sep 17 00:00:00 2001 From: David Sarno Date: Mon, 16 Feb 2026 19:39:20 -0800 Subject: [PATCH 1/4] Add --offline flag to uvx launches for faster startup when cache is warm (#760) When the uvx cache already has the package, pass --offline to skip the network dependency check that can hang for 30+ seconds on poor connections. A lightweight probe (uvx --offline ... --help) with a 3-second timeout determines cache warmth before building the command. Also fixes stale LogAssert expectations in SetComponentProperties test to match current error message format from ComponentOps refactor. Co-Authored-By: Claude Opus 4.6 --- .../Clients/McpClientConfiguratorBase.cs | 28 ++++++++++++-- .../Editor/Helpers/AssetPathUtility.cs | 31 +++++++++++++++ .../Editor/Helpers/CodexConfigHelper.cs | 18 ++++++--- .../Editor/Helpers/ConfigJsonBuilder.cs | 4 ++ .../Services/Server/ServerCommandBuilder.cs | 8 +++- .../ClientConfig/McpClientConfigSection.cs | 3 +- .../Helpers/AssetPathUtilityOfflineTests.cs | 38 +++++++++++++++++++ .../AssetPathUtilityOfflineTests.cs.meta | 11 ++++++ .../EditMode/Tools/ManageGameObjectTests.cs | 7 ++-- 9 files changed, 133 insertions(+), 15 deletions(-) create mode 100644 TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/AssetPathUtilityOfflineTests.cs create mode 100644 TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/AssetPathUtilityOfflineTests.cs.meta diff --git a/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs b/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs index 021c14ce4..502b1827d 100644 --- a/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs +++ b/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs @@ -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) { @@ -753,7 +754,7 @@ public void ConfigureWithCapturedValues( { RegisterWithCapturedValues(projectDir, claudePath, pathPrepend, useHttpTransport, httpUrl, uvxPath, fromArgs, packageName, shouldForceRefresh, - apiKey, serverTransport); + shouldUseOffline, apiKey, serverTransport); } } @@ -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) { @@ -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; // 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}"; } @@ -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}"; @@ -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" + diff --git a/MCPForUnity/Editor/Helpers/AssetPathUtility.cs b/MCPForUnity/Editor/Helpers/AssetPathUtility.cs index 1af9ea236..a9149b641 100644 --- a/MCPForUnity/Editor/Helpers/AssetPathUtility.cs +++ b/MCPForUnity/Editor/Helpers/AssetPathUtility.cs @@ -438,6 +438,37 @@ public static bool ShouldForceUvxRefresh() return IsLocalServerPath(); } + /// + /// 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). + /// + 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; + } + } + /// /// Returns true if the server URL is a local path (file:// or absolute path). /// diff --git a/MCPForUnity/Editor/Helpers/CodexConfigHelper.cs b/MCPForUnity/Editor/Helpers/CodexConfigHelper.cs index a68d47ed7..a273fcdf2 100644 --- a/MCPForUnity/Editor/Helpers/CodexConfigHelper.cs +++ b/MCPForUnity/Editor/Helpers/CodexConfigHelper.cs @@ -17,14 +17,20 @@ namespace MCPForUnity.Editor.Helpers /// 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) @@ -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()) { @@ -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()) { diff --git a/MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs b/MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs index 815477232..7fdfa7c27 100644 --- a/MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs +++ b/MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs @@ -186,6 +186,10 @@ private static IList 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()) diff --git a/MCPForUnity/Editor/Services/Server/ServerCommandBuilder.cs b/MCPForUnity/Editor/Services/Server/ServerCommandBuilder.cs index 47ba7186d..e8e9d769b 100644 --- a/MCPForUnity/Editor/Services/Server/ServerCommandBuilder.cs +++ b/MCPForUnity/Editor/Services/Server/ServerCommandBuilder.cs @@ -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 diff --git a/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs b/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs index 33ffc8a79..6d711ac4b 100644 --- a/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs +++ b/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs @@ -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 @@ -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); } diff --git a/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/AssetPathUtilityOfflineTests.cs b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/AssetPathUtilityOfflineTests.cs new file mode 100644 index 000000000..2d7679513 --- /dev/null +++ b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/AssetPathUtilityOfflineTests.cs @@ -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()); + } + } +} diff --git a/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/AssetPathUtilityOfflineTests.cs.meta b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/AssetPathUtilityOfflineTests.cs.meta new file mode 100644 index 000000000..328664e45 --- /dev/null +++ b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/AssetPathUtilityOfflineTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 39ea6f0fc573340d689bf01ef5510153 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs index ec89eda32..8a9039ec2 100644 --- a/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs +++ b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManageGameObjectTests.cs @@ -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); From 863d08dfbbab6158ad652085933fe19708be2e19 Mon Sep 17 00:00:00 2001 From: David Sarno Date: Mon, 16 Feb 2026 20:26:18 -0800 Subject: [PATCH 2/4] Centralize uvx dev-flags logic and cache offline probe result Extract GetUvxDevFlags() and GetUvxDevFlagsList() helpers to AssetPathUtility, replacing duplicated if/else if/else blocks across 6 call sites. Add 30-second TTL cache to ShouldUseUvxOffline() to avoid redundant subprocess spawns. Simplify ConfigureWithCapturedValues signature from two bools to a single pre-captured flags string. Co-Authored-By: Claude Opus 4.6 --- .../Clients/McpClientConfiguratorBase.cs | 40 +++----------- .../Editor/Helpers/AssetPathUtility.cs | 53 +++++++++++++++++++ .../Editor/Helpers/CodexConfigHelper.cs | 13 +---- .../Editor/Helpers/ConfigJsonBuilder.cs | 12 +---- .../Services/Server/ServerCommandBuilder.cs | 10 +--- .../ClientConfig/McpClientConfigSection.cs | 7 ++- 6 files changed, 68 insertions(+), 67 deletions(-) diff --git a/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs b/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs index 502b1827d..5708626ab 100644 --- a/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs +++ b/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs @@ -741,8 +741,7 @@ public override void Configure() public void ConfigureWithCapturedValues( string projectDir, string claudePath, string pathPrepend, bool useHttpTransport, string httpUrl, - string uvxPath, string fromArgs, string packageName, bool shouldForceRefresh, - bool shouldUseOffline, + string uvxPath, string fromArgs, string packageName, string uvxDevFlags, string apiKey, Models.ConfiguredTransport serverTransport) { @@ -753,8 +752,8 @@ public void ConfigureWithCapturedValues( else { RegisterWithCapturedValues(projectDir, claudePath, pathPrepend, - useHttpTransport, httpUrl, uvxPath, fromArgs, packageName, shouldForceRefresh, - shouldUseOffline, apiKey, serverTransport); + useHttpTransport, httpUrl, uvxPath, fromArgs, packageName, uvxDevFlags, + apiKey, serverTransport); } } @@ -764,8 +763,7 @@ public void ConfigureWithCapturedValues( private void RegisterWithCapturedValues( string projectDir, string claudePath, string pathPrepend, bool useHttpTransport, string httpUrl, - string uvxPath, string fromArgs, string packageName, bool shouldForceRefresh, - bool shouldUseOffline, + string uvxPath, string fromArgs, string packageName, string uvxDevFlags, string apiKey, Models.ConfiguredTransport serverTransport) { @@ -791,16 +789,8 @@ private void RegisterWithCapturedValues( } else { - // Note: --reinstall is not supported by uvx, use --no-cache --refresh instead - string devFlags; - if (shouldForceRefresh) - devFlags = "--no-cache --refresh "; - else if (shouldUseOffline) - devFlags = "--offline "; - else - devFlags = string.Empty; // 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}"; + args = $"mcp add --scope local --transport stdio UnityMCP -- \"{uvxPath}\" {uvxDevFlags}{fromArgs} {packageName}"; } // Remove any existing registrations from ALL scopes to prevent stale config conflicts (#664) @@ -875,15 +865,7 @@ private void Register() else { 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; - if (AssetPathUtility.ShouldForceUvxRefresh()) - devFlags = "--no-cache --refresh "; - else if (AssetPathUtility.ShouldUseUvxOffline()) - devFlags = "--offline "; - else - devFlags = string.Empty; + string devFlags = AssetPathUtility.GetUvxDevFlags(); 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}"; @@ -991,15 +973,7 @@ public override string GetManualSnippet() return "# Error: Configuration not available - check paths in Advanced Settings"; } - // 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; - if (AssetPathUtility.ShouldForceUvxRefresh()) - devFlags = "--no-cache --refresh "; - else if (AssetPathUtility.ShouldUseUvxOffline()) - devFlags = "--offline "; - else - devFlags = string.Empty; + string devFlags = AssetPathUtility.GetUvxDevFlags(); string fromArgs = AssetPathUtility.GetBetaServerFromArgs(quoteFromPath: true); return "# Register the MCP server with Claude Code:\n" + diff --git a/MCPForUnity/Editor/Helpers/AssetPathUtility.cs b/MCPForUnity/Editor/Helpers/AssetPathUtility.cs index a9149b641..3e4ee5663 100644 --- a/MCPForUnity/Editor/Helpers/AssetPathUtility.cs +++ b/MCPForUnity/Editor/Helpers/AssetPathUtility.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using MCPForUnity.Editor.Constants; using MCPForUnity.Editor.Services; @@ -444,12 +445,30 @@ public static bool ShouldForceUvxRefresh() /// 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). + /// The result is cached for 30 seconds to avoid redundant subprocess spawns. + /// Must be called on the main thread (reads EditorPrefs). /// public static bool ShouldUseUvxOffline() { if (ShouldForceUvxRefresh()) return false; + double now = EditorApplication.timeSinceStartup; + if (now - _offlineCacheTimestamp < OfflineCacheTtlSeconds) + return _offlineCacheResult; + + bool result = RunOfflineProbe(); + _offlineCacheResult = result; + _offlineCacheTimestamp = now; + return result; + } + + private static bool _offlineCacheResult; + private static double _offlineCacheTimestamp = -999; + private const double OfflineCacheTtlSeconds = 30.0; + + private static bool RunOfflineProbe() + { try { string uvxPath = MCPServiceLocator.Paths.GetUvxPath(); @@ -469,6 +488,40 @@ public static bool ShouldUseUvxOffline() } } + /// + /// Returns the uvx dev-mode flags as a single string for command-line builders. + /// Returns "--no-cache --refresh " if force refresh is enabled, + /// "--offline " if the cache is warm, or string.Empty otherwise. + /// Must be called on the main thread (reads EditorPrefs). + /// + public static string GetUvxDevFlags() + { + return GetUvxDevFlags(ShouldForceUvxRefresh(), ShouldUseUvxOffline()); + } + + /// + /// Returns the uvx dev-mode flags from pre-captured bool values. + /// Use this overload when values were captured on the main thread for background use. + /// + public static string GetUvxDevFlags(bool forceRefresh, bool useOffline) + { + if (forceRefresh) return "--no-cache --refresh "; + if (useOffline) return "--offline "; + return string.Empty; + } + + /// + /// Returns the uvx dev-mode flags as a list of individual arguments. + /// Suitable for callers that build argument lists (ConfigJsonBuilder, CodexConfigHelper). + /// Must be called on the main thread (reads EditorPrefs). + /// + public static IReadOnlyList GetUvxDevFlagsList() + { + if (ShouldForceUvxRefresh()) return new[] { "--no-cache", "--refresh" }; + if (ShouldUseUvxOffline()) return new[] { "--offline" }; + return Array.Empty(); + } + /// /// Returns true if the server URL is a local path (file:// or absolute path). /// diff --git a/MCPForUnity/Editor/Helpers/CodexConfigHelper.cs b/MCPForUnity/Editor/Helpers/CodexConfigHelper.cs index a273fcdf2..45ae5a39e 100644 --- a/MCPForUnity/Editor/Helpers/CodexConfigHelper.cs +++ b/MCPForUnity/Editor/Helpers/CodexConfigHelper.cs @@ -20,17 +20,8 @@ public static class CodexConfigHelper 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()) - { - args.Add(new TomlString { Value = "--no-cache" }); - args.Add(new TomlString { Value = "--refresh" }); - } - else if (AssetPathUtility.ShouldUseUvxOffline()) - { - args.Add(new TomlString { Value = "--offline" }); - } + foreach (var flag in AssetPathUtility.GetUvxDevFlagsList()) + args.Add(new TomlString { Value = flag }); } public static string BuildCodexServerBlock(string uvPath) diff --git a/MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs b/MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs index 7fdfa7c27..8405068dd 100644 --- a/MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs +++ b/MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs @@ -180,16 +180,8 @@ private static IList BuildUvxArgs(string fromUrl, string packageName) // Keep ordering consistent with other uvx builders: dev flags first, then --from , then package name. var args = new List(); - // Use central helper that checks both DevModeForceServerRefresh AND local path detection. - if (AssetPathUtility.ShouldForceUvxRefresh()) - { - args.Add("--no-cache"); - args.Add("--refresh"); - } - else if (AssetPathUtility.ShouldUseUvxOffline()) - { - args.Add("--offline"); - } + foreach (var flag in AssetPathUtility.GetUvxDevFlagsList()) + args.Add(flag); // Use centralized helper for beta server / prerelease args foreach (var arg in AssetPathUtility.GetBetaServerFromArgsList()) diff --git a/MCPForUnity/Editor/Services/Server/ServerCommandBuilder.cs b/MCPForUnity/Editor/Services/Server/ServerCommandBuilder.cs index e8e9d769b..3e505634a 100644 --- a/MCPForUnity/Editor/Services/Server/ServerCommandBuilder.cs +++ b/MCPForUnity/Editor/Services/Server/ServerCommandBuilder.cs @@ -46,15 +46,7 @@ public bool TryBuildCommand(out string fileName, out string arguments, out strin return false; } - // 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; - if (AssetPathUtility.ShouldForceUvxRefresh()) - devFlags = "--no-cache --refresh "; - else if (AssetPathUtility.ShouldUseUvxOffline()) - devFlags = "--offline "; - else - devFlags = string.Empty; + string devFlags = AssetPathUtility.GetUvxDevFlags(); bool projectScopedTools = EditorPrefs.GetBool( EditorPrefKeys.ProjectScopedToolsLocalHttp, true diff --git a/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs b/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs index 6d711ac4b..31069022e 100644 --- a/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs +++ b/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs @@ -304,8 +304,7 @@ private void ConfigureClaudeCliAsync(IMcpClientConfigurator client) string httpUrl = HttpEndpointUtility.GetMcpRpcUrl(); var (uvxPath, _, packageName) = AssetPathUtility.GetUvxCommandParts(); string fromArgs = AssetPathUtility.GetBetaServerFromArgs(quoteFromPath: true); - bool shouldForceRefresh = AssetPathUtility.ShouldForceUvxRefresh(); - bool shouldUseOffline = AssetPathUtility.ShouldUseUvxOffline(); + string uvxDevFlags = AssetPathUtility.GetUvxDevFlags(); string apiKey = EditorPrefs.GetString(EditorPrefKeys.ApiKey, string.Empty); // Compute pathPrepend on main thread @@ -332,8 +331,8 @@ private void ConfigureClaudeCliAsync(IMcpClientConfigurator client) cliConfigurator.ConfigureWithCapturedValues( projectDir, claudePath, pathPrepend, useHttpTransport, httpUrl, - uvxPath, fromArgs, packageName, shouldForceRefresh, - shouldUseOffline, apiKey, serverTransport); + uvxPath, fromArgs, packageName, uvxDevFlags, + apiKey, serverTransport); } return (success: true, error: (string)null); } From c3665d1d3fe4477ce2c1e327016ca4ba2ab6038d Mon Sep 17 00:00:00 2001 From: David Sarno Date: Mon, 16 Feb 2026 20:50:11 -0800 Subject: [PATCH 3/4] Move cache fields above methods and avoid redundant ShouldForceUvxRefresh calls Address CodeRabbit nitpicks: relocate static cache fields above the methods that use them for readability, extract GetCachedOfflineProbeResult() so GetUvxDevFlags()/GetUvxDevFlagsList() evaluate ShouldForceUvxRefresh() only once per call instead of twice. Co-Authored-By: Claude Opus 4.6 --- .../Editor/Helpers/AssetPathUtility.cs | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/MCPForUnity/Editor/Helpers/AssetPathUtility.cs b/MCPForUnity/Editor/Helpers/AssetPathUtility.cs index 3e4ee5663..c648c19e4 100644 --- a/MCPForUnity/Editor/Helpers/AssetPathUtility.cs +++ b/MCPForUnity/Editor/Helpers/AssetPathUtility.cs @@ -439,6 +439,10 @@ public static bool ShouldForceUvxRefresh() return IsLocalServerPath(); } + private static bool _offlineCacheResult; + private static double _offlineCacheTimestamp = -999; + private const double OfflineCacheTtlSeconds = 30.0; + /// /// 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 @@ -452,7 +456,11 @@ public static bool ShouldUseUvxOffline() { if (ShouldForceUvxRefresh()) return false; + return GetCachedOfflineProbeResult(); + } + private static bool GetCachedOfflineProbeResult() + { double now = EditorApplication.timeSinceStartup; if (now - _offlineCacheTimestamp < OfflineCacheTtlSeconds) return _offlineCacheResult; @@ -463,10 +471,6 @@ public static bool ShouldUseUvxOffline() return result; } - private static bool _offlineCacheResult; - private static double _offlineCacheTimestamp = -999; - private const double OfflineCacheTtlSeconds = 30.0; - private static bool RunOfflineProbe() { try @@ -496,7 +500,8 @@ private static bool RunOfflineProbe() /// public static string GetUvxDevFlags() { - return GetUvxDevFlags(ShouldForceUvxRefresh(), ShouldUseUvxOffline()); + bool forceRefresh = ShouldForceUvxRefresh(); + return GetUvxDevFlags(forceRefresh, !forceRefresh && GetCachedOfflineProbeResult()); } /// @@ -517,8 +522,9 @@ public static string GetUvxDevFlags(bool forceRefresh, bool useOffline) /// public static IReadOnlyList GetUvxDevFlagsList() { - if (ShouldForceUvxRefresh()) return new[] { "--no-cache", "--refresh" }; - if (ShouldUseUvxOffline()) return new[] { "--offline" }; + bool forceRefresh = ShouldForceUvxRefresh(); + if (forceRefresh) return new[] { "--no-cache", "--refresh" }; + if (GetCachedOfflineProbeResult()) return new[] { "--offline" }; return Array.Empty(); } From 173322f3cc92a3287e1ccbbb6f0badfc18718cb5 Mon Sep 17 00:00:00 2001 From: David Sarno Date: Mon, 16 Feb 2026 20:56:47 -0800 Subject: [PATCH 4/4] Skip stdio migration for clients incompatible with current transport When HTTP transport is enabled, skip Configure() for clients that do not support HTTP (e.g., Claude Desktop). Previously this threw and logged a spurious warning on every editor launch. Co-Authored-By: Claude Opus 4.6 --- MCPForUnity/Editor/Migrations/StdIoVersionMigration.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MCPForUnity/Editor/Migrations/StdIoVersionMigration.cs b/MCPForUnity/Editor/Migrations/StdIoVersionMigration.cs index 850e27337..ee1050958 100644 --- a/MCPForUnity/Editor/Migrations/StdIoVersionMigration.cs +++ b/MCPForUnity/Editor/Migrations/StdIoVersionMigration.cs @@ -74,6 +74,12 @@ private static void RunMigrationIfNeeded() if (!ConfigUsesStdIo(configurator.Client)) continue; + // Skip clients that don't support the current transport setting — + // Configure() would throw (e.g., Claude Desktop when HTTP is enabled). + bool useHttp = EditorConfigurationCache.Instance.UseHttpTransport; + if (useHttp && !configurator.Client.SupportsHttpTransport) + continue; + MCPServiceLocator.Client.ConfigureClient(configurator); touchedAny = true; }