From 77e43346b48c05564f7cb08964f49a1257d71700 Mon Sep 17 00:00:00 2001 From: dsarno Date: Thu, 22 Jan 2026 17:08:49 -0800 Subject: [PATCH 1/2] feat: Add transport mismatch warning and improve editor UI Transport Mismatch Detection: - Add detection for when MCP client config transport doesn't match Unity transport - Display warning banner when mismatch is detected - Add transport property to McpStatus and McpClient models - Detect transport from client configuration files (HTTP Local vs StdIO) UI Improvements: - Restructure editor window with cleaner tab-based layout - Update header styling with fixed height and improved version badge - Improve tab styling with proper borders and active state - Optimize spacing: remove bottom padding from panels, add space between sections - Rename Connection section to Server for clarity - Add flex-shrink: 0 to header to prevent clipping when dropdowns expand Refactoring: - Split monolithic Settings section into Advanced and Validation sections - Move path override controls to Advanced section - Move script validation to dedicated Validation section - Improve component organization and maintainability --- .../Editor/Clients/IMcpClientConfigurator.cs | 6 + .../Clients/McpClientConfiguratorBase.cs | 72 ++++++ .../Editor/Helpers/GameObjectLookup.cs | 4 + MCPForUnity/Editor/Models/MCPConfigServer.cs | 4 + MCPForUnity/Editor/Models/McpClient.cs | 1 + MCPForUnity/Editor/Models/McpStatus.cs | 11 + .../{Settings.meta => Advanced.meta} | 2 +- .../McpAdvancedSection.cs} | 183 ++++++++------- .../McpAdvancedSection.cs.meta} | 2 +- .../Advanced/McpAdvancedSection.uxml | 61 +++++ .../McpAdvancedSection.uxml.meta} | 2 +- .../ClientConfig/McpClientConfigSection.cs | 70 ++++-- .../ClientConfig/McpClientConfigSection.uxml | 2 +- .../Editor/Windows/Components/Common.uss | 86 ++++++- .../Connection/McpConnectionSection.cs | 191 ++++++++------- .../Connection/McpConnectionSection.uxml | 38 ++- .../Settings/McpSettingsSection.uxml | 72 ------ .../Components/Tools/McpToolsSection.cs | 15 ++ .../Components/Tools/McpToolsSection.uxml | 4 + .../Editor/Windows/Components/Validation.meta | 8 + .../Validation/McpValidationSection.cs | 79 +++++++ .../Validation/McpValidationSection.cs.meta | 11 + .../Validation/McpValidationSection.uxml | 13 ++ .../Validation/McpValidationSection.uxml.meta | 10 + .../Editor/Windows/MCPForUnityEditorWindow.cs | 218 +++++++++++++----- .../Windows/MCPForUnityEditorWindow.uss | 111 ++++++++- .../Windows/MCPForUnityEditorWindow.uxml | 22 +- 27 files changed, 908 insertions(+), 390 deletions(-) rename MCPForUnity/Editor/Windows/Components/{Settings.meta => Advanced.meta} (77%) rename MCPForUnity/Editor/Windows/Components/{Settings/McpSettingsSection.cs => Advanced/McpAdvancedSection.cs} (76%) rename MCPForUnity/Editor/Windows/Components/{Settings/McpSettingsSection.cs.meta => Advanced/McpAdvancedSection.cs.meta} (83%) create mode 100644 MCPForUnity/Editor/Windows/Components/Advanced/McpAdvancedSection.uxml rename MCPForUnity/Editor/Windows/Components/{Settings/McpSettingsSection.uxml.meta => Advanced/McpAdvancedSection.uxml.meta} (86%) delete mode 100644 MCPForUnity/Editor/Windows/Components/Settings/McpSettingsSection.uxml create mode 100644 MCPForUnity/Editor/Windows/Components/Validation.meta create mode 100644 MCPForUnity/Editor/Windows/Components/Validation/McpValidationSection.cs create mode 100644 MCPForUnity/Editor/Windows/Components/Validation/McpValidationSection.cs.meta create mode 100644 MCPForUnity/Editor/Windows/Components/Validation/McpValidationSection.uxml create mode 100644 MCPForUnity/Editor/Windows/Components/Validation/McpValidationSection.uxml.meta diff --git a/MCPForUnity/Editor/Clients/IMcpClientConfigurator.cs b/MCPForUnity/Editor/Clients/IMcpClientConfigurator.cs index 02bc41e72..9fdea29c8 100644 --- a/MCPForUnity/Editor/Clients/IMcpClientConfigurator.cs +++ b/MCPForUnity/Editor/Clients/IMcpClientConfigurator.cs @@ -17,6 +17,12 @@ public interface IMcpClientConfigurator /// Current status cached by the configurator. McpStatus Status { get; } + /// + /// The transport type the client is currently configured for. + /// Returns Unknown if the client is not configured or the transport cannot be determined. + /// + ConfiguredTransport ConfiguredTransport { get; } + /// True if this client supports auto-configure. bool SupportsAutoConfigure { get; } diff --git a/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs b/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs index 897e7dfd8..00e440cc1 100644 --- a/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs +++ b/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs @@ -28,6 +28,7 @@ protected McpClientConfiguratorBase(McpClient client) public string Id => client.name.Replace(" ", "").ToLowerInvariant(); public virtual string DisplayName => client.name; public McpStatus Status => client.status; + public ConfiguredTransport ConfiguredTransport => client.configuredTransport; public virtual bool SupportsAutoConfigure => true; public virtual string GetConfigureActionLabel() => "Configure"; @@ -94,6 +95,7 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true) if (!File.Exists(path)) { client.SetStatus(McpStatus.NotConfigured); + client.configuredTransport = Models.ConfiguredTransport.Unknown; return client.status; } @@ -135,6 +137,7 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true) if (standardConfig?.mcpServers?.unityMCP != null) { args = standardConfig.mcpServers.unityMCP.args; + configuredUrl = standardConfig.mcpServers.unityMCP.url; configExists = true; } } @@ -142,9 +145,24 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true) if (!configExists) { client.SetStatus(McpStatus.MissingConfig); + client.configuredTransport = Models.ConfiguredTransport.Unknown; return client.status; } + // Determine and set the configured transport type + if (args != null && args.Length > 0) + { + client.configuredTransport = Models.ConfiguredTransport.Stdio; + } + else if (!string.IsNullOrEmpty(configuredUrl)) + { + client.configuredTransport = Models.ConfiguredTransport.Http; + } + else + { + client.configuredTransport = Models.ConfiguredTransport.Unknown; + } + bool matches = false; if (args != null && args.Length > 0) { @@ -171,6 +189,9 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true) if (result == "Configured successfully") { client.SetStatus(McpStatus.Configured); + // Update transport after rewrite based on current server setting + bool useHttp = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true); + client.configuredTransport = useHttp ? Models.ConfiguredTransport.Http : Models.ConfiguredTransport.Stdio; } else { @@ -185,6 +206,7 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true) catch (Exception ex) { client.SetStatus(McpStatus.Error, ex.Message); + client.configuredTransport = Models.ConfiguredTransport.Unknown; } return client.status; @@ -198,6 +220,9 @@ public override void Configure() if (result == "Configured successfully") { client.SetStatus(McpStatus.Configured); + // Set transport based on current server setting + bool useHttp = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true); + client.configuredTransport = useHttp ? Models.ConfiguredTransport.Http : Models.ConfiguredTransport.Stdio; } else { @@ -237,12 +262,27 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true) if (!File.Exists(path)) { client.SetStatus(McpStatus.NotConfigured); + client.configuredTransport = Models.ConfiguredTransport.Unknown; return client.status; } string toml = File.ReadAllText(path); if (CodexConfigHelper.TryParseCodexServer(toml, out _, out var args, out var url)) { + // Determine and set the configured transport type + if (!string.IsNullOrEmpty(url)) + { + client.configuredTransport = Models.ConfiguredTransport.Http; + } + else if (args != null && args.Length > 0) + { + client.configuredTransport = Models.ConfiguredTransport.Stdio; + } + else + { + client.configuredTransport = Models.ConfiguredTransport.Unknown; + } + bool matches = false; if (!string.IsNullOrEmpty(url)) { @@ -262,6 +302,10 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true) return client.status; } } + else + { + client.configuredTransport = Models.ConfiguredTransport.Unknown; + } if (attemptAutoRewrite) { @@ -269,6 +313,9 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true) if (result == "Configured successfully") { client.SetStatus(McpStatus.Configured); + // Update transport after rewrite based on current server setting + bool useHttp = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true); + client.configuredTransport = useHttp ? Models.ConfiguredTransport.Http : Models.ConfiguredTransport.Stdio; } else { @@ -283,6 +330,7 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true) catch (Exception ex) { client.SetStatus(McpStatus.Error, ex.Message); + client.configuredTransport = Models.ConfiguredTransport.Unknown; } return client.status; @@ -296,6 +344,9 @@ public override void Configure() if (result == "Configured successfully") { client.SetStatus(McpStatus.Configured); + // Set transport based on current server setting + bool useHttp = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true); + client.configuredTransport = useHttp ? Models.ConfiguredTransport.Http : Models.ConfiguredTransport.Stdio; } else { @@ -363,6 +414,7 @@ internal McpStatus CheckStatusWithProjectDir(string projectDir, bool useHttpTran if (string.IsNullOrEmpty(claudePath)) { client.SetStatus(McpStatus.NotConfigured, "Claude CLI not found"); + client.configuredTransport = Models.ConfiguredTransport.Unknown; return client.status; } @@ -415,6 +467,20 @@ internal McpStatus CheckStatusWithProjectDir(string projectDir, bool useHttpTran 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 + if (registeredWithHttp) + { + client.configuredTransport = 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); @@ -479,10 +545,12 @@ internal McpStatus CheckStatusWithProjectDir(string projectDir, bool useHttpTran } client.SetStatus(McpStatus.NotConfigured); + client.configuredTransport = Models.ConfiguredTransport.Unknown; } catch (Exception ex) { client.SetStatus(McpStatus.Error, ex.Message); + client.configuredTransport = Models.ConfiguredTransport.Unknown; } return client.status; @@ -558,6 +626,7 @@ private void RegisterWithCapturedValues( McpLog.Info($"Successfully registered with Claude Code using {(useHttpTransport ? "HTTP" : "stdio")} transport."); client.SetStatus(McpStatus.Configured); + client.configuredTransport = useHttpTransport ? Models.ConfiguredTransport.Http : Models.ConfiguredTransport.Stdio; } /// @@ -577,6 +646,7 @@ private void UnregisterWithCapturedValues(string projectDir, string claudePath, McpLog.Info("MCP server successfully unregistered from Claude Code."); client.SetStatus(McpStatus.NotConfigured); + client.configuredTransport = Models.ConfiguredTransport.Unknown; } private void Register() @@ -645,6 +715,7 @@ private void Register() // Set status to Configured immediately after successful registration // The UI will trigger an async verification check separately to avoid blocking client.SetStatus(McpStatus.Configured); + client.configuredTransport = useHttpTransport ? Models.ConfiguredTransport.Http : Models.ConfiguredTransport.Stdio; } private void Unregister() @@ -675,6 +746,7 @@ private void Unregister() McpLog.Info("MCP server successfully unregistered from Claude Code."); client.SetStatus(McpStatus.NotConfigured); + client.configuredTransport = Models.ConfiguredTransport.Unknown; } public override string GetManualSnippet() diff --git a/MCPForUnity/Editor/Helpers/GameObjectLookup.cs b/MCPForUnity/Editor/Helpers/GameObjectLookup.cs index 60d2b0967..bd23bbd7a 100644 --- a/MCPForUnity/Editor/Helpers/GameObjectLookup.cs +++ b/MCPForUnity/Editor/Helpers/GameObjectLookup.cs @@ -69,7 +69,9 @@ public static GameObject FindByTarget(JToken target, string searchMethod, bool i /// public static GameObject FindById(int instanceId) { +#pragma warning disable CS0618 // Type or member is obsolete return EditorUtility.InstanceIDToObject(instanceId) as GameObject; +#pragma warning restore CS0618 } /// @@ -103,7 +105,9 @@ public static List SearchGameObjects(SearchMethod method, string searchTerm case SearchMethod.ById: if (int.TryParse(searchTerm, out int instanceId)) { +#pragma warning disable CS0618 // Type or member is obsolete var obj = EditorUtility.InstanceIDToObject(instanceId) as GameObject; +#pragma warning restore CS0618 if (obj != null && (includeInactive || obj.activeInHierarchy)) { results.Add(instanceId); diff --git a/MCPForUnity/Editor/Models/MCPConfigServer.cs b/MCPForUnity/Editor/Models/MCPConfigServer.cs index fbffed371..25f1163d8 100644 --- a/MCPForUnity/Editor/Models/MCPConfigServer.cs +++ b/MCPForUnity/Editor/Models/MCPConfigServer.cs @@ -15,5 +15,9 @@ public class McpConfigServer // VSCode expects a transport type; include only when explicitly set [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] public string type; + + // URL for HTTP transport mode + [JsonProperty("url", NullValueHandling = NullValueHandling.Ignore)] + public string url; } } diff --git a/MCPForUnity/Editor/Models/McpClient.cs b/MCPForUnity/Editor/Models/McpClient.cs index f09352a0d..8cb9e724c 100644 --- a/MCPForUnity/Editor/Models/McpClient.cs +++ b/MCPForUnity/Editor/Models/McpClient.cs @@ -10,6 +10,7 @@ public class McpClient public string linuxConfigPath; public string configStatus; public McpStatus status = McpStatus.NotConfigured; + public ConfiguredTransport configuredTransport = ConfiguredTransport.Unknown; // Capability flags/config for JSON-based configurators public bool IsVsCodeLayout; // Whether the config file follows VS Code layout (env object at root) diff --git a/MCPForUnity/Editor/Models/McpStatus.cs b/MCPForUnity/Editor/Models/McpStatus.cs index d041667dd..4fb84265e 100644 --- a/MCPForUnity/Editor/Models/McpStatus.cs +++ b/MCPForUnity/Editor/Models/McpStatus.cs @@ -14,5 +14,16 @@ public enum McpStatus UnsupportedOS, // OS is not supported Error, // General error state } + + /// + /// Represents the transport type a client is configured to use. + /// Used to detect mismatches between server and client transport settings. + /// + public enum ConfiguredTransport + { + Unknown, // Could not determine transport type + Stdio, // Client configured for stdio transport + Http // Client configured for HTTP transport + } } diff --git a/MCPForUnity/Editor/Windows/Components/Settings.meta b/MCPForUnity/Editor/Windows/Components/Advanced.meta similarity index 77% rename from MCPForUnity/Editor/Windows/Components/Settings.meta rename to MCPForUnity/Editor/Windows/Components/Advanced.meta index e9fe9b850..9c3d02468 100644 --- a/MCPForUnity/Editor/Windows/Components/Settings.meta +++ b/MCPForUnity/Editor/Windows/Components/Advanced.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 3e753cf64be77814d8074be7a9451338 +guid: 7723ed5eaaccb104e93acb9fd2d8cd32 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/MCPForUnity/Editor/Windows/Components/Settings/McpSettingsSection.cs b/MCPForUnity/Editor/Windows/Components/Advanced/McpAdvancedSection.cs similarity index 76% rename from MCPForUnity/Editor/Windows/Components/Settings/McpSettingsSection.cs rename to MCPForUnity/Editor/Windows/Components/Advanced/McpAdvancedSection.cs index e4f07db45..ec5990834 100644 --- a/MCPForUnity/Editor/Windows/Components/Settings/McpSettingsSection.cs +++ b/MCPForUnity/Editor/Windows/Components/Advanced/McpAdvancedSection.cs @@ -5,24 +5,18 @@ using MCPForUnity.Editor.Helpers; using MCPForUnity.Editor.Services; using UnityEditor; -using UnityEditor.UIElements; using UnityEngine; using UnityEngine.UIElements; -namespace MCPForUnity.Editor.Windows.Components.Settings +namespace MCPForUnity.Editor.Windows.Components.Advanced { /// - /// Controller for the Settings section of the MCP For Unity editor window. - /// Handles version display, debug logs, validation level, and advanced path overrides. + /// Controller for the Advanced Settings section. + /// Handles path overrides, server source configuration, dev mode, and package deployment. /// - public class McpSettingsSection + public class McpAdvancedSection { // UI Elements - private Label versionLabel; - private Toggle debugLogsToggle; - private EnumField validationLevelField; - private Label validationDescription; - private Foldout advancedSettingsFoldout; private TextField uvxPathOverride; private Button browseUvxButton; private Button clearUvxButton; @@ -30,6 +24,7 @@ public class McpSettingsSection private TextField gitUrlOverride; private Button browseGitUrlButton; private Button clearGitUrlButton; + private Toggle debugLogsToggle; private Toggle devModeForceRefreshToggle; private TextField deploySourcePath; private Button browseDeploySourceButton; @@ -39,26 +34,18 @@ public class McpSettingsSection private Label deployTargetLabel; private Label deployBackupLabel; private Label deployStatusLabel; - - // Data - private ValidationLevel currentValidationLevel = ValidationLevel.Standard; + private VisualElement healthIndicator; + private Label healthStatus; + private Button testConnectionButton; // Events public event Action OnGitUrlChanged; public event Action OnHttpServerCommandUpdateRequested; - - // Validation levels - private enum ValidationLevel - { - Basic, - Standard, - Comprehensive, - Strict - } + public event Action OnTestConnectionRequested; public VisualElement Root { get; private set; } - public McpSettingsSection(VisualElement root) + public McpAdvancedSection(VisualElement root) { Root = root; CacheUIElements(); @@ -68,11 +55,6 @@ public McpSettingsSection(VisualElement root) private void CacheUIElements() { - versionLabel = Root.Q