diff --git a/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs b/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs index 2a201995d..afa8b2563 100644 --- a/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs +++ b/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs @@ -411,7 +411,8 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true) 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 @@ public override void Configure() 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 @@ public void ConfigureWithCapturedValues( else { RegisterWithCapturedValues(projectDir, claudePath, pathPrepend, - useHttpTransport, httpUrl, uvxPath, gitUrl, packageName, shouldForceRefresh, + useHttpTransport, httpUrl, uvxPath, fromArgs, packageName, shouldForceRefresh, apiKey, serverTransport); } } @@ -621,7 +622,7 @@ public void ConfigureWithCapturedValues( 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 @@ private void RegisterWithCapturedValues( // 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 @@ private void Register() } 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 @@ public override string GetManualSnippet() 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 @@ private static string SanitizeShellHeaderValue(string value) 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 0e4d5fee2..21d2f69c8 100644 --- a/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs +++ b/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs @@ -293,7 +293,8 @@ private void ConfigureClaudeCliAsync(IMcpClientConfigurator client) 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 @@ private void ConfigureClaudeCliAsync(IMcpClientConfigurator client) 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 @@ private void RefreshClaudeCliStatus(IMcpClientConfigurator client, bool forceImm 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 @@ private void ApplyStatusToUi(IMcpClientConfigurator client, bool showChecking = // 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 9a2b86ad4..b36125d4f 100644 --- a/MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs +++ b/MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs @@ -92,6 +92,9 @@ public static void ShowWindow() 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 @@ private EditorPrefItem CreateEditorPrefItem(string key) // 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 @@ private VisualElement CreateItemUI(EditorPrefItem item) // Buttons var saveButton = itemElement.Q