From 74236b6f66a764e4b3ae3b851d09f7fe12f58dae Mon Sep 17 00:00:00 2001 From: dsarno Date: Mon, 2 Feb 2026 23:16:30 -0800 Subject: [PATCH] fix: Beta mode status validation and EditorPrefs Manager improvements (#671) * fix: Use beta server args for Claude Code registration Claude Code registration was still using gitUrl directly instead of GetBetaServerFromArgs(), meaning the beta server toggle had no effect on registration. Now uses GetBetaServerFromArgs(quoteFromPath: true) to properly generate --prerelease args for beta users. Co-Authored-By: Claude Opus 4.5 * fix: EditorPrefs Manager shows 'Unset' for keys that haven't been set - Show "Unset. Default: [value]" for EditorPrefs that haven't been explicitly set - Disable value field and save button for unset items (grayed out at 60% opacity) - Fix search filter bug where reloading window showed filtered results with empty search field Co-Authored-By: Claude Opus 4.5 * fix: Claude Code status check now accounts for beta server mode The version validation was comparing the registered package source (mcpforunityserver>=0.0.0a0 for beta) against the exact package version (mcpforunityserver==9.4.0-beta.1), causing false "Incorrect Path" status. Now both CheckStatus paths (main thread and background thread) use the same logic that considers: - Explicit GitUrlOverride - UseBetaServer setting (with dynamic default for prerelease packages) - Standard pinned version as fallback Co-Authored-By: Claude Opus 4.5 --------- Co-authored-by: Claude Opus 4.5 --- .../Clients/McpClientConfiguratorBase.cs | 57 ++++++++++++++++--- .../ClientConfig/McpClientConfigSection.cs | 44 +++++++++++++- .../Windows/EditorPrefs/EditorPrefsWindow.cs | 23 ++++++-- 3 files changed, 108 insertions(+), 16 deletions(-) diff --git a/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs b/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs index 2a20199..afa8b25 100644 --- a/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs +++ b/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs @@ -411,7 +411,8 @@ namespace MCPForUnity.Editor.Clients string claudePath = MCPServiceLocator.Paths.GetClaudeCliPath(); RuntimePlatform platform = Application.platform; bool isRemoteScope = HttpEndpointUtility.IsRemoteScope(); - string expectedPackageSource = AssetPathUtility.GetMcpServerPackageSource(); + // Get expected package source considering beta mode (matches what Register() would use) + string expectedPackageSource = GetExpectedPackageSourceForValidation(); return CheckStatusWithProjectDir(projectDir, useHttpTransport, claudePath, platform, isRemoteScope, expectedPackageSource, attemptAutoRewrite); } @@ -599,7 +600,7 @@ namespace MCPForUnity.Editor.Clients public void ConfigureWithCapturedValues( string projectDir, string claudePath, string pathPrepend, bool useHttpTransport, string httpUrl, - string uvxPath, string gitUrl, string packageName, bool shouldForceRefresh, + string uvxPath, string fromArgs, string packageName, bool shouldForceRefresh, string apiKey, Models.ConfiguredTransport serverTransport) { @@ -610,7 +611,7 @@ namespace MCPForUnity.Editor.Clients else { RegisterWithCapturedValues(projectDir, claudePath, pathPrepend, - useHttpTransport, httpUrl, uvxPath, gitUrl, packageName, shouldForceRefresh, + useHttpTransport, httpUrl, uvxPath, fromArgs, packageName, shouldForceRefresh, apiKey, serverTransport); } } @@ -621,7 +622,7 @@ namespace MCPForUnity.Editor.Clients private void RegisterWithCapturedValues( string projectDir, string claudePath, string pathPrepend, bool useHttpTransport, string httpUrl, - string uvxPath, string gitUrl, string packageName, bool shouldForceRefresh, + string uvxPath, string fromArgs, string packageName, bool shouldForceRefresh, string apiKey, Models.ConfiguredTransport serverTransport) { @@ -650,7 +651,7 @@ namespace MCPForUnity.Editor.Clients // Note: --reinstall is not supported by uvx, use --no-cache --refresh instead string devFlags = shouldForceRefresh ? "--no-cache --refresh " : 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}--from \"{gitUrl}\" {packageName}"; + args = $"mcp add --scope local --transport stdio UnityMCP -- \"{uvxPath}\" {devFlags}{fromArgs} {packageName}"; } // Remove any existing registrations from ALL scopes to prevent stale config conflicts (#664) @@ -724,12 +725,13 @@ namespace MCPForUnity.Editor.Clients } else { - var (uvxPath, gitUrl, packageName) = AssetPathUtility.GetUvxCommandParts(); + 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 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}--from \"{gitUrl}\" {packageName}"; + args = $"mcp add --scope local --transport stdio UnityMCP -- \"{uvxPath}\" {devFlags}{fromArgs} {packageName}"; } string projectDir = Path.GetDirectoryName(Application.dataPath); @@ -834,13 +836,13 @@ namespace MCPForUnity.Editor.Clients return "# Error: Configuration not available - check paths in Advanced Settings"; } - string packageSource = AssetPathUtility.GetMcpServerPackageSource(); // 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 fromArgs = AssetPathUtility.GetBetaServerFromArgs(quoteFromPath: true); return "# Register the MCP server with Claude Code:\n" + - $"claude mcp add --scope local --transport stdio UnityMCP -- \"{uvxPath}\" {devFlags}--from \"{packageSource}\" mcp-for-unity\n\n" + + $"claude mcp add --scope local --transport stdio UnityMCP -- \"{uvxPath}\" {devFlags}{fromArgs} mcp-for-unity\n\n" + "# Unregister the MCP server (from all scopes to clean up any stale configs):\n" + "claude mcp remove --scope local UnityMCP\n" + "claude mcp remove --scope user UnityMCP\n" + @@ -909,6 +911,43 @@ namespace MCPForUnity.Editor.Clients return sb.ToString(); } + /// + /// Gets the expected package source for validation, accounting for beta mode. + /// This should match what Register() would actually use for the --from argument. + /// MUST be called from the main thread due to EditorPrefs access. + /// + private static string GetExpectedPackageSourceForValidation() + { + // Check for explicit override first + string gitUrlOverride = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, ""); + if (!string.IsNullOrEmpty(gitUrlOverride)) + { + return gitUrlOverride; + } + + // Check beta mode using the same logic as GetUseBetaServerWithDynamicDefault + // (bypass cache to ensure fresh read) + bool useBetaServer; + bool hasPrefKey = EditorPrefs.HasKey(EditorPrefKeys.UseBetaServer); + if (hasPrefKey) + { + useBetaServer = EditorPrefs.GetBool(EditorPrefKeys.UseBetaServer, false); + } + else + { + // Dynamic default based on package version + useBetaServer = AssetPathUtility.IsPreReleaseVersion(); + } + + if (useBetaServer) + { + return "mcpforunityserver>=0.0.0a0"; + } + + // Standard mode uses exact version from package.json + return AssetPathUtility.GetMcpServerPackageSource(); + } + /// /// Extracts the package source (--from argument value) from claude mcp get output. /// The output format includes args like: --from "mcpforunityserver==9.0.1" diff --git a/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs b/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs index 0e4d5fe..21d2f69 100644 --- a/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs +++ b/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs @@ -293,7 +293,8 @@ namespace MCPForUnity.Editor.Windows.Components.ClientConfig bool useHttpTransport = EditorConfigurationCache.Instance.UseHttpTransport; string claudePath = MCPServiceLocator.Paths.GetClaudeCliPath(); string httpUrl = HttpEndpointUtility.GetMcpRpcUrl(); - var (uvxPath, gitUrl, packageName) = AssetPathUtility.GetUvxCommandParts(); + var (uvxPath, _, packageName) = AssetPathUtility.GetUvxCommandParts(); + string fromArgs = AssetPathUtility.GetBetaServerFromArgs(quoteFromPath: true); bool shouldForceRefresh = AssetPathUtility.ShouldForceUvxRefresh(); string apiKey = EditorPrefs.GetString(EditorPrefKeys.ApiKey, string.Empty); @@ -321,7 +322,7 @@ namespace MCPForUnity.Editor.Windows.Components.ClientConfig cliConfigurator.ConfigureWithCapturedValues( projectDir, claudePath, pathPrepend, useHttpTransport, httpUrl, - uvxPath, gitUrl, packageName, shouldForceRefresh, + uvxPath, fromArgs, packageName, shouldForceRefresh, apiKey, serverTransport); } return (success: true, error: (string)null); @@ -480,7 +481,8 @@ namespace MCPForUnity.Editor.Windows.Components.ClientConfig string claudePath = MCPServiceLocator.Paths.GetClaudeCliPath(); RuntimePlatform platform = Application.platform; bool isRemoteScope = HttpEndpointUtility.IsRemoteScope(); - string expectedPackageSource = AssetPathUtility.GetMcpServerPackageSource(); + // Get expected package source considering beta mode (bypass cache for fresh read) + string expectedPackageSource = GetExpectedPackageSourceForBetaMode(); Task.Run(() => { @@ -593,5 +595,41 @@ namespace MCPForUnity.Editor.Windows.Components.ClientConfig // Notify listeners about the client's configured transport OnClientTransportDetected?.Invoke(client.DisplayName, client.ConfiguredTransport); } + + /// + /// Gets the expected package source for validation, accounting for beta mode. + /// Uses the same logic as registration to ensure validation matches what was registered. + /// MUST be called from the main thread due to EditorPrefs access. + /// + private static string GetExpectedPackageSourceForBetaMode() + { + // Check for explicit override first + string gitUrlOverride = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, ""); + if (!string.IsNullOrEmpty(gitUrlOverride)) + { + return gitUrlOverride; + } + + // Check beta mode using the same logic as GetUseBetaServerWithDynamicDefault + // (bypass cache to ensure fresh read) + bool useBetaServer; + if (EditorPrefs.HasKey(EditorPrefKeys.UseBetaServer)) + { + useBetaServer = EditorPrefs.GetBool(EditorPrefKeys.UseBetaServer, false); + } + else + { + // Dynamic default based on package version + useBetaServer = AssetPathUtility.IsPreReleaseVersion(); + } + + if (useBetaServer) + { + return "mcpforunityserver>=0.0.0a0"; + } + + // Standard mode uses exact version from package.json + return AssetPathUtility.GetMcpServerPackageSource(); + } } } diff --git a/MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs b/MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs index 9a2b86a..b36125d 100644 --- a/MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs +++ b/MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs @@ -92,6 +92,9 @@ namespace MCPForUnity.Editor.Windows public void CreateGUI() { + // Clear search filter on GUI recreation to avoid stale filtered results + searchFilter = ""; + string basePath = AssetPathUtility.GetMcpPackageRootPath(); // Load UXML @@ -245,24 +248,27 @@ namespace MCPForUnity.Editor.Windows // Check if we know the type of this pref if (knownPrefTypes.TryGetValue(key, out var knownType)) { + // Check if the key actually exists + item.IsUnset = !EditorPrefs.HasKey(key); + // Use the known type switch (knownType) { case EditorPrefType.Bool: item.Type = EditorPrefType.Bool; - item.Value = EditorPrefs.GetBool(key, false).ToString(); + item.Value = item.IsUnset ? "Unset. Default: False" : EditorPrefs.GetBool(key, false).ToString(); break; case EditorPrefType.Int: item.Type = EditorPrefType.Int; - item.Value = EditorPrefs.GetInt(key, 0).ToString(); + item.Value = item.IsUnset ? "Unset. Default: 0" : EditorPrefs.GetInt(key, 0).ToString(); break; case EditorPrefType.Float: item.Type = EditorPrefType.Float; - item.Value = EditorPrefs.GetFloat(key, 0f).ToString(); + item.Value = item.IsUnset ? "Unset. Default: 0" : EditorPrefs.GetFloat(key, 0f).ToString(); break; case EditorPrefType.String: item.Type = EditorPrefType.String; - item.Value = EditorPrefs.GetString(key, ""); + item.Value = item.IsUnset ? "Unset. Default: (empty)" : EditorPrefs.GetString(key, ""); break; } } @@ -324,6 +330,14 @@ namespace MCPForUnity.Editor.Windows // Buttons var saveButton = itemElement.Q