Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
57 changes: 48 additions & 9 deletions MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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)
{
Expand All @@ -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);
}
}
Expand All @@ -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)
{
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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" +
Expand Down Expand Up @@ -909,6 +911,43 @@ private static string SanitizeShellHeaderValue(string value)
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
Comment on lines +919 to +928
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: The beta/override resolution logic and magic string are duplicated and could drift over time.

This helper duplicates the logic and "mcpforunityserver>=0.0.0a0" literal from McpClientConfigSection.GetExpectedPackageSourceForBetaMode (including EditorPrefs checks). Please extract a shared utility (e.g., in AssetPathUtility or another helper) that computes the effective package source so both registration and validation use the same implementation and the magic string lives in one place.

// (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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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(() =>
{
Expand Down Expand Up @@ -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);
}

/// <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();
}
}
}
23 changes: 19 additions & 4 deletions MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -324,6 +330,14 @@ private VisualElement CreateItemUI(EditorPrefItem item)
// 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);

Expand Down Expand Up @@ -389,6 +403,7 @@ public class EditorPrefItem
public string Value { get; set; }
public EditorPrefType Type { get; set; }
public bool IsKnown { get; set; }
public bool IsUnset { get; set; }
}

/// <summary>
Expand Down