From 3ecee71453684e83ce31a37b7b81746aa3ec3ed6 Mon Sep 17 00:00:00 2001 From: dsarno Date: Tue, 3 Feb 2026 13:41:06 -0800 Subject: [PATCH 1/8] 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 --- .../Configurators/ClaudeCodeConfigurator.cs | 2 +- .../Clients/McpClientConfiguratorBase.cs | 358 +++++++++++++----- .../ClientConfig/McpClientConfigSection.cs | 2 +- 3 files changed, 257 insertions(+), 105 deletions(-) diff --git a/MCPForUnity/Editor/Clients/Configurators/ClaudeCodeConfigurator.cs b/MCPForUnity/Editor/Clients/Configurators/ClaudeCodeConfigurator.cs index c890d7c46..d2545b8d7 100644 --- a/MCPForUnity/Editor/Clients/Configurators/ClaudeCodeConfigurator.cs +++ b/MCPForUnity/Editor/Clients/Configurators/ClaudeCodeConfigurator.cs @@ -19,7 +19,7 @@ public ClaudeCodeConfigurator() : base(new McpClient public override IList GetInstallationSteps() => new List { "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", "Use Unregister to remove via 'claude mcp remove'" }; diff --git a/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs b/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs index afa8b2563..69c23c56c 100644 --- a/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs +++ b/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs @@ -394,7 +394,7 @@ public abstract class ClaudeCliMcpConfigurator : McpClientConfiguratorBase public ClaudeCliMcpConfigurator(McpClient client) : base(client) { } 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"; @@ -445,131 +445,130 @@ internal McpStatus CheckStatusWithProjectDir( throw new ArgumentNullException(nameof(projectDir), "Project directory must be provided for thread-safe execution"); } - string pathPrepend = null; - if (platform == RuntimePlatform.OSXEditor) + // Read Claude Code config directly from ~/.claude.json instead of using slow CLI + // 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; + return client.status; } - else if (platform == RuntimePlatform.LinuxEditor) + + if (configResult.serverConfig == null) { - pathPrepend = "/usr/local/bin:/usr/bin:/bin"; + // UnityMCP not found in config + client.SetStatus(McpStatus.NotConfigured); + client.configuredTransport = Models.ConfiguredTransport.Unknown; + return client.status; } - try + // 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) { - string claudeDir = Path.GetDirectoryName(claudePath); - if (!string.IsNullOrEmpty(claudeDir)) - { - pathPrepend = string.IsNullOrEmpty(pathPrepend) - ? claudeDir - : $"{claudeDir}:{pathPrepend}"; - } + client.configuredTransport = isRemoteScope + ? Models.ConfiguredTransport.HttpRemote + : Models.ConfiguredTransport.Http; + } + else if (registeredWithStdio) + { + client.configuredTransport = Models.ConfiguredTransport.Stdio; } - catch { } + else + { + client.configuredTransport = Models.ConfiguredTransport.Unknown; + } + + // Check for transport mismatch + bool hasTransportMismatch = (currentUseHttp && registeredWithStdio) || (!currentUseHttp && registeredWithHttp); - // 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)) + // For stdio transport, also check package version + bool hasVersionMismatch = false; + string configuredPackageSource = null; + string mismatchReason = null; + if (registeredWithStdio) { - if (!string.IsNullOrEmpty(listStdout) && listStdout.IndexOf("UnityMCP", StringComparison.OrdinalIgnoreCase) >= 0) + configuredPackageSource = ExtractPackageSourceFromConfig(serverConfig); + if (!string.IsNullOrEmpty(configuredPackageSource) && !string.IsNullOrEmpty(expectedPackageSource)) { - // 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) + // Check for exact match first + if (!string.Equals(configuredPackageSource, expectedPackageSource, StringComparison.OrdinalIgnoreCase)) { - // Parse the output to determine registered transport mode - // 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 - // For HTTP, we can't distinguish local/remote from CLI output alone, - // so infer from the current scope setting when HTTP is detected. - if (registeredWithHttp) + hasVersionMismatch = true; + + // Provide more specific mismatch reason for beta/stable differences + bool configuredIsBeta = IsBetaPackageSource(configuredPackageSource); + bool expectedIsBeta = IsBetaPackageSource(expectedPackageSource); + + if (configuredIsBeta && !expectedIsBeta) { - client.configuredTransport = isRemoteScope - ? Models.ConfiguredTransport.HttpRemote - : Models.ConfiguredTransport.Http; + mismatchReason = $"Beta/stable mismatch: registered with beta '{configuredPackageSource}' but plugin is stable '{expectedPackageSource}'."; } - else if (registeredWithStdio) + else if (!configuredIsBeta && expectedIsBeta) { - client.configuredTransport = Models.ConfiguredTransport.Stdio; + mismatchReason = $"Beta/stable mismatch: registered with stable '{configuredPackageSource}' but plugin is beta '{expectedPackageSource}'."; } else { - client.configuredTransport = Models.ConfiguredTransport.Unknown; - } - - // 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; - } + mismatchReason = $"Package version mismatch: registered with '{configuredPackageSource}' but current version is '{expectedPackageSource}'."; } } + } + } - 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.IncorrectPath, mismatchReason); + } return client.status; } } - client.SetStatus(McpStatus.NotConfigured); - client.configuredTransport = Models.ConfiguredTransport.Unknown; + client.SetStatus(McpStatus.Configured); + return client.status; } catch (Exception ex) { @@ -854,7 +853,7 @@ public override string GetManualSnippet() public override IList GetInstallationSteps() => new List { "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" }; @@ -993,5 +992,158 @@ private static string ExtractPackageSourceFromCliOutput(string cliOutput) return null; } + + /// + /// 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. + /// + 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)) + { + return (null, "Claude Code config file not found"); + } + + 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(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}"); + } + } + + /// + /// Normalizes a file path for comparison (handles forward/back slashes, trailing slashes). + /// + 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('/'); + } + + /// + /// Extracts the package source from Claude Code JSON config. + /// For stdio servers, this is in the args array after "--from". + /// + 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 followed by the package source + 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; + } + } + + return null; + } + + /// + /// 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" + /// + private static bool IsBetaPackageSource(string packageSource) + { + if (string.IsNullOrEmpty(packageSource)) + return false; + + // PyPI beta format: mcpforunityserver==X.Y.Zb + // 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; + } } } diff --git a/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs b/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs index 21d2f69c8..9ea7b79ba 100644 --- a/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs +++ b/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs @@ -286,7 +286,7 @@ private void ConfigureClaudeCliAsync(IMcpClientConfigurator client) statusRefreshInFlight.Add(client); 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 string projectDir = Path.GetDirectoryName(Application.dataPath); From 0ec7cebdc1b2fe5d3eab156de3a956d8bd6e86fb Mon Sep 17 00:00:00 2001 From: dsarno Date: Tue, 3 Feb 2026 13:41:06 -0800 Subject: [PATCH 2/8] 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 --- MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs b/MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs index 46e21757d..78739ca97 100644 --- a/MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs +++ b/MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs @@ -263,6 +263,7 @@ public void CreateGUI() await connectionSection.VerifyBridgeConnectionAsync(); }; advancedSection.OnBetaModeChanged += UpdateVersionLabel; + advancedSection.OnBetaModeChanged += _ => clientConfigSection?.RefreshSelectedClient(forceImmediate: true); // Wire up health status updates from Connection to Advanced connectionSection?.SetHealthStatusUpdateCallback((isHealthy, statusText) => @@ -552,6 +553,8 @@ private void SwitchPanel(ActivePanel panel) { case ActivePanel.Clients: 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; case ActivePanel.Validation: if (validationPanel != null) validationPanel.style.display = DisplayStyle.Flex; From 51d999cd5f14d4f7cc61b5c6dafbd3098e5d53da Mon Sep 17 00:00:00 2001 From: dsarno Date: Tue, 3 Feb 2026 14:05:12 -0800 Subject: [PATCH 3/8] 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 --- MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs | 2 +- MCPForUnity/Editor/Models/McpClient.cs | 1 + MCPForUnity/Editor/Models/McpStatus.cs | 1 + .../Components/ClientConfig/McpClientConfigSection.cs | 9 +++++++++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs b/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs index 69c23c56c..f2a16b25d 100644 --- a/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs +++ b/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs @@ -561,7 +561,7 @@ internal McpStatus CheckStatusWithProjectDir( } else { - client.SetStatus(McpStatus.IncorrectPath, mismatchReason); + client.SetStatus(McpStatus.VersionMismatch, mismatchReason); } return client.status; } diff --git a/MCPForUnity/Editor/Models/McpClient.cs b/MCPForUnity/Editor/Models/McpClient.cs index 832bb8a23..56c2f45d4 100644 --- a/MCPForUnity/Editor/Models/McpClient.cs +++ b/MCPForUnity/Editor/Models/McpClient.cs @@ -35,6 +35,7 @@ public string GetStatusDisplayString() McpStatus.UnsupportedOS => "Unsupported OS", McpStatus.MissingConfig => "Missing MCPForUnity Config", McpStatus.Error => configStatus?.StartsWith("Error:") == true ? configStatus : "Error", + McpStatus.VersionMismatch => "Version Mismatch", _ => "Unknown", }; } diff --git a/MCPForUnity/Editor/Models/McpStatus.cs b/MCPForUnity/Editor/Models/McpStatus.cs index c23bc819b..dfc04fdd0 100644 --- a/MCPForUnity/Editor/Models/McpStatus.cs +++ b/MCPForUnity/Editor/Models/McpStatus.cs @@ -13,6 +13,7 @@ public enum McpStatus MissingConfig, // Config file exists but missing required elements UnsupportedOS, // OS is not supported Error, // General error state + VersionMismatch, // Configuration version doesn't match expected version } /// diff --git a/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs b/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs index 9ea7b79ba..505891a15 100644 --- a/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs +++ b/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs @@ -53,6 +53,12 @@ public class McpClientConfigSection /// public event Action OnClientTransportDetected; + /// + /// 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). + /// + public event Action OnClientConfigMismatch; + public VisualElement Root { get; private set; } public McpClientConfigSection(VisualElement root) @@ -167,6 +173,7 @@ private string GetStatusDisplayString(McpStatus status) McpStatus.UnsupportedOS => "Unsupported OS", McpStatus.MissingConfig => "Missing MCPForUnity Config", McpStatus.Error => "Error", + McpStatus.VersionMismatch => "Version Mismatch", _ => "Unknown", }; } @@ -581,6 +588,8 @@ private void ApplyStatusToUi(IMcpClientConfigurator client, bool showChecking = case McpStatus.IncorrectPath: case McpStatus.CommunicationError: case McpStatus.NoResponse: + case McpStatus.Error: + case McpStatus.VersionMismatch: clientStatusIndicator.AddToClassList("warning"); break; default: From a3885eab71e59e50532a01a9188db3e37473f3ba Mon Sep 17 00:00:00 2001 From: dsarno Date: Tue, 3 Feb 2026 14:10:21 -0800 Subject: [PATCH 4/8] 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 --- MCPForUnity/Editor/Models/McpClient.cs | 4 +-- .../ClientConfig/McpClientConfigSection.cs | 13 ++++++++ .../Connection/McpConnectionSection.cs | 33 +++++++++++++++++++ .../Connection/McpConnectionSection.uxml | 3 ++ .../Editor/Windows/MCPForUnityEditorWindow.cs | 5 +++ 5 files changed, 56 insertions(+), 2 deletions(-) diff --git a/MCPForUnity/Editor/Models/McpClient.cs b/MCPForUnity/Editor/Models/McpClient.cs index 56c2f45d4..5f2a1c712 100644 --- a/MCPForUnity/Editor/Models/McpClient.cs +++ b/MCPForUnity/Editor/Models/McpClient.cs @@ -45,9 +45,9 @@ public void SetStatus(McpStatus newStatus, string errorDetails = null) { 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 { diff --git a/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs b/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs index 505891a15..575650f57 100644 --- a/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs +++ b/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs @@ -603,6 +603,19 @@ private void ApplyStatusToUi(IMcpClientConfigurator client, bool showChecking = // Notify listeners about the client's configured transport 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); + } } /// diff --git a/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs b/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs index 36f564749..cc39a2334 100644 --- a/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs +++ b/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs @@ -30,6 +30,8 @@ private enum TransportProtocol private EnumField transportDropdown; private VisualElement transportMismatchWarning; private Label transportMismatchText; + private VisualElement versionMismatchWarning; + private Label versionMismatchText; private VisualElement httpUrlRow; private VisualElement httpServerControlRow; private Foldout manualCommandFoldout; @@ -86,6 +88,8 @@ private void CacheUIElements() transportDropdown = Root.Q("transport-dropdown"); transportMismatchWarning = Root.Q("transport-mismatch-warning"); transportMismatchText = Root.Q