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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

* 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 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
beta
dsarno 2026-02-02 23:16:30 -08:00 committed by GitHub
parent 2671f2091a
commit 74236b6f66
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 108 additions and 16 deletions

View File

@ -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();
}
/// <summary>
/// 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.
/// </summary>
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();
}
/// <summary>
/// Extracts the package source (--from argument value) from claude mcp get output.
/// The output format includes args like: --from "mcpforunityserver==9.0.1"

View File

@ -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);
}
/// <summary>
/// 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.
/// </summary>
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();
}
}
}

View File

@ -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<Button>("save-button");
// Style unset items
if (item.IsUnset)
{
valueField.SetEnabled(false);
valueField.style.opacity = 0.6f;
saveButton.SetEnabled(false);
}
// Callbacks
saveButton.clicked += () => SavePref(item, valueField.value, (EditorPrefType)typeDropdown.index);
@ -389,6 +403,7 @@ namespace MCPForUnity.Editor.Windows
public string Value { get; set; }
public EditorPrefType Type { get; set; }
public bool IsKnown { get; set; }
public bool IsUnset { get; set; }
}
/// <summary>