fix: speed up Claude Code config check by reading JSON directly (#682)
* fix: speed up Claude Code config check by reading JSON directly Instead of running `claude mcp list` (15+ seconds due to health checks), read the config directly from ~/.claude.json (instant). Changes: - Add ReadClaudeCodeConfig() to parse Claude's JSON config file - Walk up directory tree to find config at parent directories - Handle duplicate path entries (forward/backslash variants) - Add beta/stable version mismatch detection with clear messages - Add IsBetaPackageSource() to detect PyPI beta versions and prerelease ranges - Change button label from "Register" to "Configure" for consistency Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: speed up Claude Code config check by reading JSON directly Instead of running `claude mcp list` (15+ seconds due to health checks), read the config directly from ~/.claude.json (instant). Changes: - Add ReadClaudeCodeConfig() to parse Claude's JSON config file - Walk up directory tree to find config at parent directories - Handle duplicate path entries (forward/backslash variants) - Add beta/stable version mismatch detection with clear messages - Add IsBetaPackageSource() to detect PyPI beta versions and prerelease ranges - Change button label from "Register" to "Configure" for consistency - Refresh client status when switching to Connect tab or toggling beta mode Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: add VersionMismatch status for Claude Code config detection - Add McpStatus.VersionMismatch enum value for version mismatch cases - Show "Version Mismatch" with yellow warning indicator instead of "Error" - Use VersionMismatch for beta/stable package source mismatches - Keep Error status for transport mismatches and general errors Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: add version mismatch warning banner in Server section - Add version-mismatch-warning banner to McpConnectionSection.uxml - Add UpdateVersionMismatchWarning method to show/hide the banner - Fire OnClientConfigMismatch event when VersionMismatch status detected - Wire up event in main window to update the warning banner - Store mismatch details in configStatus for both Error and VersionMismatch Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: simplify version mismatch messages for non-technical users Before: "Beta/stable mismatch: registered with beta 'mcpforunityserver>=0.0.0a0' but plugin is stable 'mcpforunityserver==9.4.0'." After: "Configured for beta server, but 'Use Beta Server' is disabled in Advanced settings." Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address PR review feedback - Treat missing ~/.claude.json as "not configured" instead of error (distinguishes "no Claude Code installed" from actual read failures) - Handle --from=VALUE format in ExtractPackageSourceFromConfig (in addition to existing --from VALUE format) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: add beta/stable version mismatch detection to all JSON-based clients - Move GetExpectedPackageSourceForValidation() and IsBetaPackageSource() to base class so all configurators can use them - Update JsonFileMcpConfigurator.CheckStatus() to use beta-aware comparison - Show VersionMismatch status with clear messaging for Claude Desktop, Cursor, Windsurf, VS Code, and other JSON-based clients - Auto-rewrite still attempts to fix mismatches automatically Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: add beta-aware validation to CodexMcpConfigurator CodexMcpConfigurator was still using the non-beta-aware package source comparison. Now uses GetExpectedPackageSourceForValidation() and shows VersionMismatch status with clear messaging like other configurators. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>beta
parent
a8e478a42a
commit
6f3b869f3d
|
|
@ -19,7 +19,7 @@ namespace MCPForUnity.Editor.Clients.Configurators
|
||||||
public override IList<string> GetInstallationSteps() => new List<string>
|
public override IList<string> GetInstallationSteps() => new List<string>
|
||||||
{
|
{
|
||||||
"Ensure Claude CLI is installed (comes with Claude Code)",
|
"Ensure Claude CLI is installed (comes with Claude Code)",
|
||||||
"Click Register to add UnityMCP via 'claude mcp add'",
|
"Click Configure to add UnityMCP via 'claude mcp add'",
|
||||||
"The server will be automatically available in Claude Code",
|
"The server will be automatically available in Claude Code",
|
||||||
"Use Unregister to remove via 'claude mcp remove'"
|
"Use Unregister to remove via 'claude mcp remove'"
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,74 @@ namespace MCPForUnity.Editor.Clients
|
||||||
string Normalize(string value) => value.Trim().TrimEnd('/');
|
string Normalize(string value) => value.Trim().TrimEnd('/');
|
||||||
return string.Equals(Normalize(a), Normalize(b), StringComparison.OrdinalIgnoreCase);
|
return string.Equals(Normalize(a), Normalize(b), StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the expected package source for validation, accounting for beta mode.
|
||||||
|
/// This should match what Configure() would actually use for the --from argument.
|
||||||
|
/// MUST be called from the main thread due to EditorPrefs access.
|
||||||
|
/// </summary>
|
||||||
|
protected 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>
|
||||||
|
/// Checks if a package source string represents a beta/prerelease version.
|
||||||
|
/// Beta versions include:
|
||||||
|
/// - PyPI beta: "mcpforunityserver==9.4.0b20250203..." (contains 'b' before timestamp)
|
||||||
|
/// - PyPI prerelease range: "mcpforunityserver>=0.0.0a0" (used when beta mode is enabled)
|
||||||
|
/// - Git beta branch: contains "@beta" or "-beta"
|
||||||
|
/// </summary>
|
||||||
|
protected static bool IsBetaPackageSource(string packageSource)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(packageSource))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// PyPI beta format: mcpforunityserver==X.Y.Zb<timestamp>
|
||||||
|
// The 'b' suffix before numbers indicates a PEP 440 beta version
|
||||||
|
if (System.Text.RegularExpressions.Regex.IsMatch(packageSource, @"==\d+\.\d+\.\d+b\d+"))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// PyPI prerelease range: >=0.0.0a0 (used when "Use Beta Server" is enabled in Unity settings)
|
||||||
|
if (packageSource.Contains(">=0.0.0a0", StringComparison.OrdinalIgnoreCase))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Git-based beta references
|
||||||
|
if (packageSource.Contains("@beta", StringComparison.OrdinalIgnoreCase))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (packageSource.Contains("-beta", StringComparison.OrdinalIgnoreCase))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>JSON-file based configurator (Cursor, Windsurf, VS Code, etc.).</summary>
|
/// <summary>JSON-file based configurator (Cursor, Windsurf, VS Code, etc.).</summary>
|
||||||
|
|
@ -174,12 +242,44 @@ namespace MCPForUnity.Editor.Clients
|
||||||
}
|
}
|
||||||
|
|
||||||
bool matches = false;
|
bool matches = false;
|
||||||
|
bool hasVersionMismatch = false;
|
||||||
|
string mismatchReason = null;
|
||||||
|
|
||||||
if (args != null && args.Length > 0)
|
if (args != null && args.Length > 0)
|
||||||
{
|
{
|
||||||
string expectedUvxUrl = AssetPathUtility.GetMcpServerPackageSource();
|
// Use beta-aware expected package source for comparison
|
||||||
|
string expectedUvxUrl = GetExpectedPackageSourceForValidation();
|
||||||
string configuredUvxUrl = McpConfigurationHelper.ExtractUvxUrl(args);
|
string configuredUvxUrl = McpConfigurationHelper.ExtractUvxUrl(args);
|
||||||
matches = !string.IsNullOrEmpty(configuredUvxUrl) &&
|
|
||||||
McpConfigurationHelper.PathsEqual(configuredUvxUrl, expectedUvxUrl);
|
if (!string.IsNullOrEmpty(configuredUvxUrl) && !string.IsNullOrEmpty(expectedUvxUrl))
|
||||||
|
{
|
||||||
|
if (McpConfigurationHelper.PathsEqual(configuredUvxUrl, expectedUvxUrl))
|
||||||
|
{
|
||||||
|
matches = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Check for beta/stable mismatch
|
||||||
|
bool configuredIsBeta = IsBetaPackageSource(configuredUvxUrl);
|
||||||
|
bool expectedIsBeta = IsBetaPackageSource(expectedUvxUrl);
|
||||||
|
|
||||||
|
if (configuredIsBeta && !expectedIsBeta)
|
||||||
|
{
|
||||||
|
hasVersionMismatch = true;
|
||||||
|
mismatchReason = "Configured for beta server, but 'Use Beta Server' is disabled in Advanced settings.";
|
||||||
|
}
|
||||||
|
else if (!configuredIsBeta && expectedIsBeta)
|
||||||
|
{
|
||||||
|
hasVersionMismatch = true;
|
||||||
|
mismatchReason = "Configured for stable server, but 'Use Beta Server' is enabled in Advanced settings.";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hasVersionMismatch = true;
|
||||||
|
mismatchReason = "Server version doesn't match the plugin. Re-configure to update.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (!string.IsNullOrEmpty(configuredUrl))
|
else if (!string.IsNullOrEmpty(configuredUrl))
|
||||||
{
|
{
|
||||||
|
|
@ -194,7 +294,27 @@ namespace MCPForUnity.Editor.Clients
|
||||||
return client.status;
|
return client.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attemptAutoRewrite)
|
if (hasVersionMismatch)
|
||||||
|
{
|
||||||
|
if (attemptAutoRewrite)
|
||||||
|
{
|
||||||
|
var result = McpConfigurationHelper.WriteMcpConfiguration(path, client);
|
||||||
|
if (result == "Configured successfully")
|
||||||
|
{
|
||||||
|
client.SetStatus(McpStatus.Configured);
|
||||||
|
client.configuredTransport = HttpEndpointUtility.GetCurrentServerTransport();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
client.SetStatus(McpStatus.VersionMismatch, mismatchReason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
client.SetStatus(McpStatus.VersionMismatch, mismatchReason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (attemptAutoRewrite)
|
||||||
{
|
{
|
||||||
var result = McpConfigurationHelper.WriteMcpConfiguration(path, client);
|
var result = McpConfigurationHelper.WriteMcpConfiguration(path, client);
|
||||||
if (result == "Configured successfully")
|
if (result == "Configured successfully")
|
||||||
|
|
@ -300,6 +420,9 @@ namespace MCPForUnity.Editor.Clients
|
||||||
}
|
}
|
||||||
|
|
||||||
bool matches = false;
|
bool matches = false;
|
||||||
|
bool hasVersionMismatch = false;
|
||||||
|
string mismatchReason = null;
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(url))
|
if (!string.IsNullOrEmpty(url))
|
||||||
{
|
{
|
||||||
// Match against the active scope's URL
|
// Match against the active scope's URL
|
||||||
|
|
@ -307,10 +430,39 @@ namespace MCPForUnity.Editor.Clients
|
||||||
}
|
}
|
||||||
else if (args != null && args.Length > 0)
|
else if (args != null && args.Length > 0)
|
||||||
{
|
{
|
||||||
string expected = AssetPathUtility.GetMcpServerPackageSource();
|
// Use beta-aware expected package source for comparison
|
||||||
|
string expected = GetExpectedPackageSourceForValidation();
|
||||||
string configured = McpConfigurationHelper.ExtractUvxUrl(args);
|
string configured = McpConfigurationHelper.ExtractUvxUrl(args);
|
||||||
matches = !string.IsNullOrEmpty(configured) &&
|
|
||||||
McpConfigurationHelper.PathsEqual(configured, expected);
|
if (!string.IsNullOrEmpty(configured) && !string.IsNullOrEmpty(expected))
|
||||||
|
{
|
||||||
|
if (McpConfigurationHelper.PathsEqual(configured, expected))
|
||||||
|
{
|
||||||
|
matches = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Check for beta/stable mismatch
|
||||||
|
bool configuredIsBeta = IsBetaPackageSource(configured);
|
||||||
|
bool expectedIsBeta = IsBetaPackageSource(expected);
|
||||||
|
|
||||||
|
if (configuredIsBeta && !expectedIsBeta)
|
||||||
|
{
|
||||||
|
hasVersionMismatch = true;
|
||||||
|
mismatchReason = "Configured for beta server, but 'Use Beta Server' is disabled in Advanced settings.";
|
||||||
|
}
|
||||||
|
else if (!configuredIsBeta && expectedIsBeta)
|
||||||
|
{
|
||||||
|
hasVersionMismatch = true;
|
||||||
|
mismatchReason = "Configured for stable server, but 'Use Beta Server' is enabled in Advanced settings.";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hasVersionMismatch = true;
|
||||||
|
mismatchReason = "Server version doesn't match the plugin. Re-configure to update.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matches)
|
if (matches)
|
||||||
|
|
@ -318,6 +470,22 @@ namespace MCPForUnity.Editor.Clients
|
||||||
client.SetStatus(McpStatus.Configured);
|
client.SetStatus(McpStatus.Configured);
|
||||||
return client.status;
|
return client.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasVersionMismatch)
|
||||||
|
{
|
||||||
|
if (attemptAutoRewrite)
|
||||||
|
{
|
||||||
|
string result = McpConfigurationHelper.ConfigureCodexClient(path, client);
|
||||||
|
if (result == "Configured successfully")
|
||||||
|
{
|
||||||
|
client.SetStatus(McpStatus.Configured);
|
||||||
|
client.configuredTransport = HttpEndpointUtility.GetCurrentServerTransport();
|
||||||
|
return client.status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client.SetStatus(McpStatus.VersionMismatch, mismatchReason);
|
||||||
|
return client.status;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -394,7 +562,7 @@ namespace MCPForUnity.Editor.Clients
|
||||||
public ClaudeCliMcpConfigurator(McpClient client) : base(client) { }
|
public ClaudeCliMcpConfigurator(McpClient client) : base(client) { }
|
||||||
|
|
||||||
public override bool SupportsAutoConfigure => true;
|
public override bool SupportsAutoConfigure => true;
|
||||||
public override string GetConfigureActionLabel() => client.status == McpStatus.Configured ? "Unregister" : "Register";
|
public override string GetConfigureActionLabel() => client.status == McpStatus.Configured ? "Unregister" : "Configure";
|
||||||
|
|
||||||
public override string GetConfigPath() => "Managed via Claude CLI";
|
public override string GetConfigPath() => "Managed via Claude CLI";
|
||||||
|
|
||||||
|
|
@ -445,131 +613,130 @@ namespace MCPForUnity.Editor.Clients
|
||||||
throw new ArgumentNullException(nameof(projectDir), "Project directory must be provided for thread-safe execution");
|
throw new ArgumentNullException(nameof(projectDir), "Project directory must be provided for thread-safe execution");
|
||||||
}
|
}
|
||||||
|
|
||||||
string pathPrepend = null;
|
// Read Claude Code config directly from ~/.claude.json instead of using slow CLI
|
||||||
if (platform == RuntimePlatform.OSXEditor)
|
// This is instant vs 15+ seconds for `claude mcp list` which does health checks
|
||||||
|
var configResult = ReadClaudeCodeConfig(projectDir);
|
||||||
|
if (configResult.error != null)
|
||||||
{
|
{
|
||||||
pathPrepend = "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin";
|
client.SetStatus(McpStatus.NotConfigured, configResult.error);
|
||||||
}
|
client.configuredTransport = Models.ConfiguredTransport.Unknown;
|
||||||
else if (platform == RuntimePlatform.LinuxEditor)
|
return client.status;
|
||||||
{
|
|
||||||
pathPrepend = "/usr/local/bin:/usr/bin:/bin";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
if (configResult.serverConfig == null)
|
||||||
{
|
{
|
||||||
string claudeDir = Path.GetDirectoryName(claudePath);
|
// UnityMCP not found in config
|
||||||
if (!string.IsNullOrEmpty(claudeDir))
|
client.SetStatus(McpStatus.NotConfigured);
|
||||||
|
client.configuredTransport = Models.ConfiguredTransport.Unknown;
|
||||||
|
return client.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnityMCP is registered - check transport and version
|
||||||
|
bool currentUseHttp = useHttpTransport;
|
||||||
|
var serverConfig = configResult.serverConfig;
|
||||||
|
|
||||||
|
// Determine registered transport type
|
||||||
|
string registeredType = serverConfig["type"]?.ToString()?.ToLowerInvariant() ?? "";
|
||||||
|
bool registeredWithHttp = registeredType == "http";
|
||||||
|
bool registeredWithStdio = registeredType == "stdio";
|
||||||
|
|
||||||
|
// Set the configured transport based on what we detected
|
||||||
|
if (registeredWithHttp)
|
||||||
|
{
|
||||||
|
client.configuredTransport = isRemoteScope
|
||||||
|
? Models.ConfiguredTransport.HttpRemote
|
||||||
|
: Models.ConfiguredTransport.Http;
|
||||||
|
}
|
||||||
|
else if (registeredWithStdio)
|
||||||
|
{
|
||||||
|
client.configuredTransport = Models.ConfiguredTransport.Stdio;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
client.configuredTransport = Models.ConfiguredTransport.Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for transport mismatch
|
||||||
|
bool hasTransportMismatch = (currentUseHttp && registeredWithStdio) || (!currentUseHttp && registeredWithHttp);
|
||||||
|
|
||||||
|
// For stdio transport, also check package version
|
||||||
|
bool hasVersionMismatch = false;
|
||||||
|
string configuredPackageSource = null;
|
||||||
|
string mismatchReason = null;
|
||||||
|
if (registeredWithStdio)
|
||||||
|
{
|
||||||
|
configuredPackageSource = ExtractPackageSourceFromConfig(serverConfig);
|
||||||
|
if (!string.IsNullOrEmpty(configuredPackageSource) && !string.IsNullOrEmpty(expectedPackageSource))
|
||||||
{
|
{
|
||||||
pathPrepend = string.IsNullOrEmpty(pathPrepend)
|
// Check for exact match first
|
||||||
? claudeDir
|
if (!string.Equals(configuredPackageSource, expectedPackageSource, StringComparison.OrdinalIgnoreCase))
|
||||||
: $"{claudeDir}:{pathPrepend}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
|
|
||||||
// Check if UnityMCP exists (handles both "UnityMCP" and legacy "unityMCP")
|
|
||||||
if (ExecPath.TryRun(claudePath, "mcp list", projectDir, out var listStdout, out var listStderr, 10000, pathPrepend))
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(listStdout) && listStdout.IndexOf("UnityMCP", StringComparison.OrdinalIgnoreCase) >= 0)
|
|
||||||
{
|
|
||||||
// UnityMCP is registered - now verify transport mode matches
|
|
||||||
// useHttpTransport parameter is required (non-nullable) to ensure thread safety
|
|
||||||
bool currentUseHttp = useHttpTransport;
|
|
||||||
|
|
||||||
// Get detailed info about the registration to check transport type
|
|
||||||
// Try both "UnityMCP" and "unityMCP" (legacy naming)
|
|
||||||
string getStdout = null, getStderr = null;
|
|
||||||
bool gotInfo = ExecPath.TryRun(claudePath, "mcp get UnityMCP", projectDir, out getStdout, out getStderr, 7000, pathPrepend)
|
|
||||||
|| ExecPath.TryRun(claudePath, "mcp get unityMCP", projectDir, out getStdout, out getStderr, 7000, pathPrepend);
|
|
||||||
if (gotInfo)
|
|
||||||
{
|
{
|
||||||
// Parse the output to determine registered transport mode
|
hasVersionMismatch = true;
|
||||||
// The CLI output format contains "Type: http" or "Type: stdio"
|
|
||||||
bool registeredWithHttp = getStdout.Contains("Type: http", StringComparison.OrdinalIgnoreCase);
|
|
||||||
bool registeredWithStdio = getStdout.Contains("Type: stdio", StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
// Set the configured transport based on what we detected
|
// Provide more specific mismatch reason for beta/stable differences
|
||||||
// For HTTP, we can't distinguish local/remote from CLI output alone,
|
bool configuredIsBeta = IsBetaPackageSource(configuredPackageSource);
|
||||||
// so infer from the current scope setting when HTTP is detected.
|
bool expectedIsBeta = IsBetaPackageSource(expectedPackageSource);
|
||||||
if (registeredWithHttp)
|
|
||||||
|
if (configuredIsBeta && !expectedIsBeta)
|
||||||
{
|
{
|
||||||
client.configuredTransport = isRemoteScope
|
mismatchReason = "Configured for beta server, but 'Use Beta Server' is disabled in Advanced settings.";
|
||||||
? Models.ConfiguredTransport.HttpRemote
|
|
||||||
: Models.ConfiguredTransport.Http;
|
|
||||||
}
|
}
|
||||||
else if (registeredWithStdio)
|
else if (!configuredIsBeta && expectedIsBeta)
|
||||||
{
|
{
|
||||||
client.configuredTransport = Models.ConfiguredTransport.Stdio;
|
mismatchReason = "Configured for stable server, but 'Use Beta Server' is enabled in Advanced settings.";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
client.configuredTransport = Models.ConfiguredTransport.Unknown;
|
mismatchReason = "Server version doesn't match the plugin. Re-configure to update.";
|
||||||
}
|
|
||||||
|
|
||||||
// Check for transport mismatch (3-way: Stdio, Http, HttpRemote)
|
|
||||||
bool hasTransportMismatch = (currentUseHttp && registeredWithStdio) || (!currentUseHttp && registeredWithHttp);
|
|
||||||
|
|
||||||
// For stdio transport, also check package version
|
|
||||||
bool hasVersionMismatch = false;
|
|
||||||
string configuredPackageSource = null;
|
|
||||||
if (registeredWithStdio)
|
|
||||||
{
|
|
||||||
// expectedPackageSource was captured on main thread and passed as parameter
|
|
||||||
configuredPackageSource = ExtractPackageSourceFromCliOutput(getStdout);
|
|
||||||
hasVersionMismatch = !string.IsNullOrEmpty(configuredPackageSource) &&
|
|
||||||
!string.Equals(configuredPackageSource, expectedPackageSource, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there's any mismatch and auto-rewrite is enabled, re-register
|
|
||||||
if (hasTransportMismatch || hasVersionMismatch)
|
|
||||||
{
|
|
||||||
// Configure() requires main thread (accesses EditorPrefs, Application.dataPath)
|
|
||||||
// Only attempt auto-rewrite if we're on the main thread
|
|
||||||
bool isMainThread = System.Threading.Thread.CurrentThread.ManagedThreadId == 1;
|
|
||||||
if (attemptAutoRewrite && isMainThread)
|
|
||||||
{
|
|
||||||
string reason = hasTransportMismatch
|
|
||||||
? $"Transport mismatch (registered: {(registeredWithHttp ? "HTTP" : "stdio")}, expected: {(currentUseHttp ? "HTTP" : "stdio")})"
|
|
||||||
: $"Package version mismatch (registered: {configuredPackageSource}, expected: {expectedPackageSource})";
|
|
||||||
McpLog.Info($"{reason}. Re-registering...");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Force re-register by ensuring status is not Configured (which would toggle to Unregister)
|
|
||||||
client.SetStatus(McpStatus.IncorrectPath);
|
|
||||||
Configure();
|
|
||||||
return client.status;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
McpLog.Warn($"Auto-reregister failed: {ex.Message}");
|
|
||||||
client.SetStatus(McpStatus.IncorrectPath, $"Configuration mismatch. Click Configure to re-register.");
|
|
||||||
return client.status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (hasTransportMismatch)
|
|
||||||
{
|
|
||||||
string errorMsg = $"Transport mismatch: Claude Code is registered with {(registeredWithHttp ? "HTTP" : "stdio")} but current setting is {(currentUseHttp ? "HTTP" : "stdio")}. Click Configure to re-register.";
|
|
||||||
client.SetStatus(McpStatus.Error, errorMsg);
|
|
||||||
McpLog.Warn(errorMsg);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
client.SetStatus(McpStatus.IncorrectPath, $"Package version mismatch: registered with '{configuredPackageSource}' but current version is '{expectedPackageSource}'.");
|
|
||||||
}
|
|
||||||
return client.status;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
client.SetStatus(McpStatus.Configured);
|
// If there's any mismatch and auto-rewrite is enabled, re-register
|
||||||
|
if (hasTransportMismatch || hasVersionMismatch)
|
||||||
|
{
|
||||||
|
// Configure() requires main thread (accesses EditorPrefs, Application.dataPath)
|
||||||
|
// Only attempt auto-rewrite if we're on the main thread
|
||||||
|
bool isMainThread = System.Threading.Thread.CurrentThread.ManagedThreadId == 1;
|
||||||
|
if (attemptAutoRewrite && isMainThread)
|
||||||
|
{
|
||||||
|
string reason = hasTransportMismatch
|
||||||
|
? $"Transport mismatch (registered: {(registeredWithHttp ? "HTTP" : "stdio")}, expected: {(currentUseHttp ? "HTTP" : "stdio")})"
|
||||||
|
: mismatchReason ?? $"Package version mismatch";
|
||||||
|
McpLog.Info($"{reason}. Re-registering...");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Force re-register by ensuring status is not Configured (which would toggle to Unregister)
|
||||||
|
client.SetStatus(McpStatus.IncorrectPath);
|
||||||
|
Configure();
|
||||||
|
return client.status;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
McpLog.Warn($"Auto-reregister failed: {ex.Message}");
|
||||||
|
client.SetStatus(McpStatus.IncorrectPath, $"Configuration mismatch. Click Configure to re-register.");
|
||||||
|
return client.status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (hasTransportMismatch)
|
||||||
|
{
|
||||||
|
string errorMsg = $"Transport mismatch: Claude Code is registered with {(registeredWithHttp ? "HTTP" : "stdio")} but current setting is {(currentUseHttp ? "HTTP" : "stdio")}. Click Configure to re-register.";
|
||||||
|
client.SetStatus(McpStatus.Error, errorMsg);
|
||||||
|
McpLog.Warn(errorMsg);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
client.SetStatus(McpStatus.VersionMismatch, mismatchReason);
|
||||||
|
}
|
||||||
return client.status;
|
return client.status;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
client.SetStatus(McpStatus.NotConfigured);
|
client.SetStatus(McpStatus.Configured);
|
||||||
client.configuredTransport = Models.ConfiguredTransport.Unknown;
|
return client.status;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -854,7 +1021,7 @@ namespace MCPForUnity.Editor.Clients
|
||||||
public override IList<string> GetInstallationSteps() => new List<string>
|
public override IList<string> GetInstallationSteps() => new List<string>
|
||||||
{
|
{
|
||||||
"Ensure Claude CLI is installed",
|
"Ensure Claude CLI is installed",
|
||||||
"Use Register to add UnityMCP (or run claude mcp add UnityMCP)",
|
"Use Configure to add UnityMCP (or run claude mcp add UnityMCP)",
|
||||||
"Restart Claude Code"
|
"Restart Claude Code"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -911,43 +1078,6 @@ namespace MCPForUnity.Editor.Clients
|
||||||
return sb.ToString();
|
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>
|
/// <summary>
|
||||||
/// Extracts the package source (--from argument value) from claude mcp get output.
|
/// Extracts the package source (--from argument value) from claude mcp get output.
|
||||||
/// The output format includes args like: --from "mcpforunityserver==9.0.1"
|
/// The output format includes args like: --from "mcpforunityserver==9.0.1"
|
||||||
|
|
@ -993,5 +1123,134 @@ namespace MCPForUnity.Editor.Clients
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads Claude Code configuration directly from ~/.claude.json file.
|
||||||
|
/// This is much faster than running `claude mcp list` which does health checks on all servers.
|
||||||
|
/// </summary>
|
||||||
|
private static (JObject serverConfig, string error) ReadClaudeCodeConfig(string projectDir)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Find the Claude config file
|
||||||
|
string homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||||
|
string configPath = Path.Combine(homeDir, ".claude.json");
|
||||||
|
|
||||||
|
if (!File.Exists(configPath))
|
||||||
|
{
|
||||||
|
// Missing config file is "not configured", not an error
|
||||||
|
// (Claude Code may not be installed or just hasn't been configured yet)
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
string configJson = File.ReadAllText(configPath);
|
||||||
|
var config = JObject.Parse(configJson);
|
||||||
|
|
||||||
|
var projects = config["projects"] as JObject;
|
||||||
|
if (projects == null)
|
||||||
|
{
|
||||||
|
return (null, null); // No projects configured
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a dictionary of normalized paths for quick lookup
|
||||||
|
// Use last entry for duplicates (forward/backslash variants) as it's typically more recent
|
||||||
|
var normalizedProjects = new Dictionary<string, JObject>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (var project in projects.Properties())
|
||||||
|
{
|
||||||
|
string normalizedPath = NormalizePath(project.Name);
|
||||||
|
normalizedProjects[normalizedPath] = project.Value as JObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk up the directory tree to find a matching project config
|
||||||
|
// Claude Code may be configured at a parent directory (e.g., repo root)
|
||||||
|
// while Unity project is in a subdirectory (e.g., TestProjects/UnityMCPTests)
|
||||||
|
string currentDir = NormalizePath(projectDir);
|
||||||
|
while (!string.IsNullOrEmpty(currentDir))
|
||||||
|
{
|
||||||
|
if (normalizedProjects.TryGetValue(currentDir, out var projectConfig))
|
||||||
|
{
|
||||||
|
var mcpServers = projectConfig?["mcpServers"] as JObject;
|
||||||
|
if (mcpServers != null)
|
||||||
|
{
|
||||||
|
// Look for UnityMCP (case-insensitive)
|
||||||
|
foreach (var server in mcpServers.Properties())
|
||||||
|
{
|
||||||
|
if (string.Equals(server.Name, "UnityMCP", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return (server.Value as JObject, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Found the project but no UnityMCP - don't continue walking up
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move up one directory
|
||||||
|
int lastSlash = currentDir.LastIndexOf('/');
|
||||||
|
if (lastSlash <= 0)
|
||||||
|
break;
|
||||||
|
currentDir = currentDir.Substring(0, lastSlash);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (null, null); // Project not found in config
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return (null, $"Error reading Claude config: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Normalizes a file path for comparison (handles forward/back slashes, trailing slashes).
|
||||||
|
/// </summary>
|
||||||
|
private static string NormalizePath(string path)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(path))
|
||||||
|
return path;
|
||||||
|
|
||||||
|
// Replace backslashes with forward slashes and remove trailing slashes
|
||||||
|
return path.Replace('\\', '/').TrimEnd('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extracts the package source from Claude Code JSON config.
|
||||||
|
/// For stdio servers, this is in the args array after "--from".
|
||||||
|
/// </summary>
|
||||||
|
private static string ExtractPackageSourceFromConfig(JObject serverConfig)
|
||||||
|
{
|
||||||
|
if (serverConfig == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var args = serverConfig["args"] as JArray;
|
||||||
|
if (args == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Look for --from argument (either "--from VALUE" or "--from=VALUE" format)
|
||||||
|
bool foundFrom = false;
|
||||||
|
foreach (var arg in args)
|
||||||
|
{
|
||||||
|
string argStr = arg?.ToString();
|
||||||
|
if (argStr == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (foundFrom)
|
||||||
|
{
|
||||||
|
// This is the package source following --from
|
||||||
|
return argStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argStr == "--from")
|
||||||
|
{
|
||||||
|
foundFrom = true;
|
||||||
|
}
|
||||||
|
else if (argStr.StartsWith("--from=", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
// Handle --from=VALUE format
|
||||||
|
return argStr.Substring(7).Trim('"', '\'');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ namespace MCPForUnity.Editor.Models
|
||||||
McpStatus.UnsupportedOS => "Unsupported OS",
|
McpStatus.UnsupportedOS => "Unsupported OS",
|
||||||
McpStatus.MissingConfig => "Missing MCPForUnity Config",
|
McpStatus.MissingConfig => "Missing MCPForUnity Config",
|
||||||
McpStatus.Error => configStatus?.StartsWith("Error:") == true ? configStatus : "Error",
|
McpStatus.Error => configStatus?.StartsWith("Error:") == true ? configStatus : "Error",
|
||||||
|
McpStatus.VersionMismatch => "Version Mismatch",
|
||||||
_ => "Unknown",
|
_ => "Unknown",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -44,9 +45,9 @@ namespace MCPForUnity.Editor.Models
|
||||||
{
|
{
|
||||||
status = newStatus;
|
status = newStatus;
|
||||||
|
|
||||||
if (newStatus == McpStatus.Error && !string.IsNullOrEmpty(errorDetails))
|
if ((newStatus == McpStatus.Error || newStatus == McpStatus.VersionMismatch) && !string.IsNullOrEmpty(errorDetails))
|
||||||
{
|
{
|
||||||
configStatus = $"Error: {errorDetails}";
|
configStatus = errorDetails;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ namespace MCPForUnity.Editor.Models
|
||||||
MissingConfig, // Config file exists but missing required elements
|
MissingConfig, // Config file exists but missing required elements
|
||||||
UnsupportedOS, // OS is not supported
|
UnsupportedOS, // OS is not supported
|
||||||
Error, // General error state
|
Error, // General error state
|
||||||
|
VersionMismatch, // Configuration version doesn't match expected version
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,12 @@ namespace MCPForUnity.Editor.Windows.Components.ClientConfig
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<string, ConfiguredTransport> OnClientTransportDetected;
|
public event Action<string, ConfiguredTransport> OnClientTransportDetected;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fired when a config mismatch is detected (e.g., version mismatch).
|
||||||
|
/// The parameter contains the client name and the mismatch message (null if no mismatch).
|
||||||
|
/// </summary>
|
||||||
|
public event Action<string, string> OnClientConfigMismatch;
|
||||||
|
|
||||||
public VisualElement Root { get; private set; }
|
public VisualElement Root { get; private set; }
|
||||||
|
|
||||||
public McpClientConfigSection(VisualElement root)
|
public McpClientConfigSection(VisualElement root)
|
||||||
|
|
@ -167,6 +173,7 @@ namespace MCPForUnity.Editor.Windows.Components.ClientConfig
|
||||||
McpStatus.UnsupportedOS => "Unsupported OS",
|
McpStatus.UnsupportedOS => "Unsupported OS",
|
||||||
McpStatus.MissingConfig => "Missing MCPForUnity Config",
|
McpStatus.MissingConfig => "Missing MCPForUnity Config",
|
||||||
McpStatus.Error => "Error",
|
McpStatus.Error => "Error",
|
||||||
|
McpStatus.VersionMismatch => "Version Mismatch",
|
||||||
_ => "Unknown",
|
_ => "Unknown",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -286,7 +293,7 @@ namespace MCPForUnity.Editor.Windows.Components.ClientConfig
|
||||||
|
|
||||||
statusRefreshInFlight.Add(client);
|
statusRefreshInFlight.Add(client);
|
||||||
bool isCurrentlyConfigured = client.Status == McpStatus.Configured;
|
bool isCurrentlyConfigured = client.Status == McpStatus.Configured;
|
||||||
ApplyStatusToUi(client, showChecking: true, customMessage: isCurrentlyConfigured ? "Unregistering..." : "Registering...");
|
ApplyStatusToUi(client, showChecking: true, customMessage: isCurrentlyConfigured ? "Unregistering..." : "Configuring...");
|
||||||
|
|
||||||
// Capture ALL main-thread-only values before async task
|
// Capture ALL main-thread-only values before async task
|
||||||
string projectDir = Path.GetDirectoryName(Application.dataPath);
|
string projectDir = Path.GetDirectoryName(Application.dataPath);
|
||||||
|
|
@ -581,6 +588,8 @@ namespace MCPForUnity.Editor.Windows.Components.ClientConfig
|
||||||
case McpStatus.IncorrectPath:
|
case McpStatus.IncorrectPath:
|
||||||
case McpStatus.CommunicationError:
|
case McpStatus.CommunicationError:
|
||||||
case McpStatus.NoResponse:
|
case McpStatus.NoResponse:
|
||||||
|
case McpStatus.Error:
|
||||||
|
case McpStatus.VersionMismatch:
|
||||||
clientStatusIndicator.AddToClassList("warning");
|
clientStatusIndicator.AddToClassList("warning");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
@ -594,6 +603,19 @@ namespace MCPForUnity.Editor.Windows.Components.ClientConfig
|
||||||
|
|
||||||
// Notify listeners about the client's configured transport
|
// Notify listeners about the client's configured transport
|
||||||
OnClientTransportDetected?.Invoke(client.DisplayName, client.ConfiguredTransport);
|
OnClientTransportDetected?.Invoke(client.DisplayName, client.ConfiguredTransport);
|
||||||
|
|
||||||
|
// Notify listeners about version mismatch if applicable
|
||||||
|
if (client.Status == McpStatus.VersionMismatch && client is McpClientConfiguratorBase baseConfigurator)
|
||||||
|
{
|
||||||
|
// Get the mismatch reason from the configStatus field
|
||||||
|
string mismatchReason = baseConfigurator.Client.configStatus;
|
||||||
|
OnClientConfigMismatch?.Invoke(client.DisplayName, mismatchReason);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Clear any previous mismatch warning
|
||||||
|
OnClientConfigMismatch?.Invoke(client.DisplayName, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,8 @@ namespace MCPForUnity.Editor.Windows.Components.Connection
|
||||||
private EnumField transportDropdown;
|
private EnumField transportDropdown;
|
||||||
private VisualElement transportMismatchWarning;
|
private VisualElement transportMismatchWarning;
|
||||||
private Label transportMismatchText;
|
private Label transportMismatchText;
|
||||||
|
private VisualElement versionMismatchWarning;
|
||||||
|
private Label versionMismatchText;
|
||||||
private VisualElement httpUrlRow;
|
private VisualElement httpUrlRow;
|
||||||
private VisualElement httpServerControlRow;
|
private VisualElement httpServerControlRow;
|
||||||
private Foldout manualCommandFoldout;
|
private Foldout manualCommandFoldout;
|
||||||
|
|
@ -86,6 +88,8 @@ namespace MCPForUnity.Editor.Windows.Components.Connection
|
||||||
transportDropdown = Root.Q<EnumField>("transport-dropdown");
|
transportDropdown = Root.Q<EnumField>("transport-dropdown");
|
||||||
transportMismatchWarning = Root.Q<VisualElement>("transport-mismatch-warning");
|
transportMismatchWarning = Root.Q<VisualElement>("transport-mismatch-warning");
|
||||||
transportMismatchText = Root.Q<Label>("transport-mismatch-text");
|
transportMismatchText = Root.Q<Label>("transport-mismatch-text");
|
||||||
|
versionMismatchWarning = Root.Q<VisualElement>("version-mismatch-warning");
|
||||||
|
versionMismatchText = Root.Q<Label>("version-mismatch-text");
|
||||||
httpUrlRow = Root.Q<VisualElement>("http-url-row");
|
httpUrlRow = Root.Q<VisualElement>("http-url-row");
|
||||||
httpServerControlRow = Root.Q<VisualElement>("http-server-control-row");
|
httpServerControlRow = Root.Q<VisualElement>("http-server-control-row");
|
||||||
manualCommandFoldout = Root.Q<Foldout>("manual-command-foldout");
|
manualCommandFoldout = Root.Q<Foldout>("manual-command-foldout");
|
||||||
|
|
@ -1023,6 +1027,35 @@ namespace MCPForUnity.Editor.Windows.Components.Connection
|
||||||
transportMismatchWarning?.RemoveFromClassList("visible");
|
transportMismatchWarning?.RemoveFromClassList("visible");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the version mismatch warning banner based on the client's configuration status.
|
||||||
|
/// Shows a warning if the client is registered with a different package version than expected.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientName">The display name of the client being checked.</param>
|
||||||
|
/// <param name="mismatchMessage">The mismatch message, or null if no mismatch.</param>
|
||||||
|
public void UpdateVersionMismatchWarning(string clientName, string mismatchMessage)
|
||||||
|
{
|
||||||
|
if (versionMismatchWarning == null || versionMismatchText == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(mismatchMessage))
|
||||||
|
{
|
||||||
|
versionMismatchWarning.RemoveFromClassList("visible");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
versionMismatchText.text = $"⚠ {clientName}: {mismatchMessage}";
|
||||||
|
versionMismatchWarning.AddToClassList("visible");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears the version mismatch warning banner.
|
||||||
|
/// </summary>
|
||||||
|
public void ClearVersionMismatchWarning()
|
||||||
|
{
|
||||||
|
versionMismatchWarning?.RemoveFromClassList("visible");
|
||||||
|
}
|
||||||
|
|
||||||
private static string TransportDisplayName(ConfiguredTransport transport)
|
private static string TransportDisplayName(ConfiguredTransport transport)
|
||||||
{
|
{
|
||||||
return transport switch
|
return transport switch
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@
|
||||||
<ui:VisualElement name="transport-mismatch-warning" class="warning-banner">
|
<ui:VisualElement name="transport-mismatch-warning" class="warning-banner">
|
||||||
<ui:Label name="transport-mismatch-text" class="warning-banner-text" />
|
<ui:Label name="transport-mismatch-text" class="warning-banner-text" />
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
|
<ui:VisualElement name="version-mismatch-warning" class="warning-banner">
|
||||||
|
<ui:Label name="version-mismatch-text" class="warning-banner-text" />
|
||||||
|
</ui:VisualElement>
|
||||||
<ui:VisualElement class="setting-row" name="http-url-row">
|
<ui:VisualElement class="setting-row" name="http-url-row">
|
||||||
<ui:Label text="HTTP URL:" class="setting-label" />
|
<ui:Label text="HTTP URL:" class="setting-label" />
|
||||||
<ui:TextField name="http-url" class="url-field" />
|
<ui:TextField name="http-url" class="url-field" />
|
||||||
|
|
|
||||||
|
|
@ -229,6 +229,11 @@ namespace MCPForUnity.Editor.Windows
|
||||||
// update the connection section's warning banner if there's a mismatch
|
// update the connection section's warning banner if there's a mismatch
|
||||||
clientConfigSection.OnClientTransportDetected += (clientName, transport) =>
|
clientConfigSection.OnClientTransportDetected += (clientName, transport) =>
|
||||||
connectionSection?.UpdateTransportMismatchWarning(clientName, transport);
|
connectionSection?.UpdateTransportMismatchWarning(clientName, transport);
|
||||||
|
|
||||||
|
// Wire up version mismatch detection: when client status is checked,
|
||||||
|
// update the connection section's warning banner if there's a version mismatch
|
||||||
|
clientConfigSection.OnClientConfigMismatch += (clientName, mismatchMessage) =>
|
||||||
|
connectionSection?.UpdateVersionMismatchWarning(clientName, mismatchMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load and initialize Validation section
|
// Load and initialize Validation section
|
||||||
|
|
@ -263,6 +268,7 @@ namespace MCPForUnity.Editor.Windows
|
||||||
await connectionSection.VerifyBridgeConnectionAsync();
|
await connectionSection.VerifyBridgeConnectionAsync();
|
||||||
};
|
};
|
||||||
advancedSection.OnBetaModeChanged += UpdateVersionLabel;
|
advancedSection.OnBetaModeChanged += UpdateVersionLabel;
|
||||||
|
advancedSection.OnBetaModeChanged += _ => clientConfigSection?.RefreshSelectedClient(forceImmediate: true);
|
||||||
|
|
||||||
// Wire up health status updates from Connection to Advanced
|
// Wire up health status updates from Connection to Advanced
|
||||||
connectionSection?.SetHealthStatusUpdateCallback((isHealthy, statusText) =>
|
connectionSection?.SetHealthStatusUpdateCallback((isHealthy, statusText) =>
|
||||||
|
|
@ -552,6 +558,8 @@ namespace MCPForUnity.Editor.Windows
|
||||||
{
|
{
|
||||||
case ActivePanel.Clients:
|
case ActivePanel.Clients:
|
||||||
if (clientsPanel != null) clientsPanel.style.display = DisplayStyle.Flex;
|
if (clientsPanel != null) clientsPanel.style.display = DisplayStyle.Flex;
|
||||||
|
// Refresh client status when switching to Connect tab (e.g., after changing beta mode in Advanced)
|
||||||
|
clientConfigSection?.RefreshSelectedClient(forceImmediate: true);
|
||||||
break;
|
break;
|
||||||
case ActivePanel.Validation:
|
case ActivePanel.Validation:
|
||||||
if (validationPanel != null) validationPanel.style.display = DisplayStyle.Flex;
|
if (validationPanel != null) validationPanel.style.display = DisplayStyle.Flex;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue