feat: improve editor window UI + add transport mismatch warning (#613)

main
dsarno 2026-01-22 22:48:03 -08:00 committed by GitHub
parent ecb091eb9e
commit cb1a7dd2a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 942 additions and 402 deletions

View File

@ -17,6 +17,12 @@ namespace MCPForUnity.Editor.Clients
/// <summary>Current status cached by the configurator.</summary>
McpStatus Status { get; }
/// <summary>
/// The transport type the client is currently configured for.
/// Returns Unknown if the client is not configured or the transport cannot be determined.
/// </summary>
ConfiguredTransport ConfiguredTransport { get; }
/// <summary>True if this client supports auto-configure.</summary>
bool SupportsAutoConfigure { get; }

View File

@ -28,6 +28,7 @@ namespace MCPForUnity.Editor.Clients
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 @@ namespace MCPForUnity.Editor.Clients
if (!File.Exists(path))
{
client.SetStatus(McpStatus.NotConfigured);
client.configuredTransport = Models.ConfiguredTransport.Unknown;
return client.status;
}
@ -135,6 +137,7 @@ namespace MCPForUnity.Editor.Clients
if (standardConfig?.mcpServers?.unityMCP != null)
{
args = standardConfig.mcpServers.unityMCP.args;
configuredUrl = standardConfig.mcpServers.unityMCP.url;
configExists = true;
}
}
@ -142,9 +145,24 @@ namespace MCPForUnity.Editor.Clients
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 @@ namespace MCPForUnity.Editor.Clients
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 @@ namespace MCPForUnity.Editor.Clients
catch (Exception ex)
{
client.SetStatus(McpStatus.Error, ex.Message);
client.configuredTransport = Models.ConfiguredTransport.Unknown;
}
return client.status;
@ -198,6 +220,9 @@ namespace MCPForUnity.Editor.Clients
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 @@ namespace MCPForUnity.Editor.Clients
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 @@ namespace MCPForUnity.Editor.Clients
return client.status;
}
}
else
{
client.configuredTransport = Models.ConfiguredTransport.Unknown;
}
if (attemptAutoRewrite)
{
@ -269,6 +313,9 @@ namespace MCPForUnity.Editor.Clients
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 @@ namespace MCPForUnity.Editor.Clients
catch (Exception ex)
{
client.SetStatus(McpStatus.Error, ex.Message);
client.configuredTransport = Models.ConfiguredTransport.Unknown;
}
return client.status;
@ -296,6 +344,9 @@ namespace MCPForUnity.Editor.Clients
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 @@ namespace MCPForUnity.Editor.Clients
if (string.IsNullOrEmpty(claudePath))
{
client.SetStatus(McpStatus.NotConfigured, "Claude CLI not found");
client.configuredTransport = Models.ConfiguredTransport.Unknown;
return client.status;
}
@ -415,6 +467,20 @@ namespace MCPForUnity.Editor.Clients
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 @@ namespace MCPForUnity.Editor.Clients
}
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 @@ namespace MCPForUnity.Editor.Clients
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;
}
/// <summary>
@ -577,6 +646,7 @@ namespace MCPForUnity.Editor.Clients
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 @@ namespace MCPForUnity.Editor.Clients
// 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 @@ namespace MCPForUnity.Editor.Clients
McpLog.Info("MCP server successfully unregistered from Claude Code.");
client.SetStatus(McpStatus.NotConfigured);
client.configuredTransport = Models.ConfiguredTransport.Unknown;
}
public override string GetManualSnippet()

View File

@ -0,0 +1,14 @@
namespace MCPForUnity.Editor.Constants
{
/// <summary>
/// Constants for health check status values.
/// Used for coordinating health state between Connection and Advanced sections.
/// </summary>
public static class HealthStatus
{
public const string Unknown = "Unknown";
public const string Healthy = "Healthy";
public const string PingFailed = "Ping Failed";
public const string Unhealthy = "Unhealthy";
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: b945a3de2cb2683448d686c0f3174faa
guid: c15ed2426f43860479f1b8a99a343d16
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -69,7 +69,9 @@ namespace MCPForUnity.Editor.Helpers
/// </summary>
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
}
/// <summary>
@ -103,7 +105,9 @@ namespace MCPForUnity.Editor.Helpers
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);

View File

@ -15,5 +15,9 @@ namespace MCPForUnity.Editor.Models
// 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;
}
}

View File

@ -10,6 +10,7 @@ namespace MCPForUnity.Editor.Models
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)

View File

@ -14,5 +14,16 @@ namespace MCPForUnity.Editor.Models
UnsupportedOS, // OS is not supported
Error, // General error state
}
/// <summary>
/// Represents the transport type a client is configured to use.
/// Used to detect mismatches between server and client transport settings.
/// </summary>
public enum ConfiguredTransport
{
Unknown, // Could not determine transport type
Stdio, // Client configured for stdio transport
Http // Client configured for HTTP transport
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 3e753cf64be77814d8074be7a9451338
guid: 7723ed5eaaccb104e93acb9fd2d8cd32
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@ -5,24 +5,18 @@ using MCPForUnity.Editor.Constants;
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
{
/// <summary>
/// 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.
/// </summary>
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 @@ namespace MCPForUnity.Editor.Windows.Components.Settings
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 @@ namespace MCPForUnity.Editor.Windows.Components.Settings
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 @@ namespace MCPForUnity.Editor.Windows.Components.Settings
private void CacheUIElements()
{
versionLabel = Root.Q<Label>("version-label");
debugLogsToggle = Root.Q<Toggle>("debug-logs-toggle");
validationLevelField = Root.Q<EnumField>("validation-level");
validationDescription = Root.Q<Label>("validation-description");
advancedSettingsFoldout = Root.Q<Foldout>("advanced-settings-foldout");
uvxPathOverride = Root.Q<TextField>("uv-path-override");
browseUvxButton = Root.Q<Button>("browse-uv-button");
clearUvxButton = Root.Q<Button>("clear-uv-button");
@ -80,6 +62,7 @@ namespace MCPForUnity.Editor.Windows.Components.Settings
gitUrlOverride = Root.Q<TextField>("git-url-override");
browseGitUrlButton = Root.Q<Button>("browse-git-url-button");
clearGitUrlButton = Root.Q<Button>("clear-git-url-button");
debugLogsToggle = Root.Q<Toggle>("debug-logs-toggle");
devModeForceRefreshToggle = Root.Q<Toggle>("dev-mode-force-refresh-toggle");
deploySourcePath = Root.Q<TextField>("deploy-source-path");
browseDeploySourceButton = Root.Q<Button>("browse-deploy-source-button");
@ -89,46 +72,70 @@ namespace MCPForUnity.Editor.Windows.Components.Settings
deployTargetLabel = Root.Q<Label>("deploy-target-label");
deployBackupLabel = Root.Q<Label>("deploy-backup-label");
deployStatusLabel = Root.Q<Label>("deploy-status-label");
healthIndicator = Root.Q<VisualElement>("health-indicator");
healthStatus = Root.Q<Label>("health-status");
testConnectionButton = Root.Q<Button>("test-connection-button");
}
private void InitializeUI()
{
UpdateVersionLabel();
// Set tooltips for fields
if (uvxPathOverride != null)
uvxPathOverride.tooltip = "Override path to uvx executable. Leave empty for auto-detection.";
if (gitUrlOverride != null)
gitUrlOverride.tooltip = "Override server source for uvx --from. Leave empty to use default PyPI package. Example local dev: /path/to/unity-mcp/Server";
if (debugLogsToggle != null)
{
debugLogsToggle.tooltip = "Enable verbose debug logging to the Unity Console.";
var debugLabel = debugLogsToggle?.parent?.Q<Label>();
if (debugLabel != null)
debugLabel.tooltip = debugLogsToggle.tooltip;
}
if (devModeForceRefreshToggle != null)
{
devModeForceRefreshToggle.tooltip = "When enabled, generated uvx commands add '--no-cache --refresh' before launching (slower startup, but avoids stale cached builds while iterating on the Server).";
var forceRefreshLabel = devModeForceRefreshToggle?.parent?.Q<Label>();
if (forceRefreshLabel != null)
forceRefreshLabel.tooltip = devModeForceRefreshToggle.tooltip;
}
if (testConnectionButton != null)
testConnectionButton.tooltip = "Test the connection between Unity and the MCP server.";
if (deploySourcePath != null)
deploySourcePath.tooltip = "Copy a MCPForUnity folder into this project's package location.";
// Set tooltips for buttons
if (browseUvxButton != null)
browseUvxButton.tooltip = "Browse for uvx executable";
if (clearUvxButton != null)
clearUvxButton.tooltip = "Clear override and use auto-detection";
if (browseGitUrlButton != null)
browseGitUrlButton.tooltip = "Select local server source folder";
if (clearGitUrlButton != null)
clearGitUrlButton.tooltip = "Clear override and use default PyPI package";
if (browseDeploySourceButton != null)
browseDeploySourceButton.tooltip = "Select MCPForUnity source folder";
if (clearDeploySourceButton != null)
clearDeploySourceButton.tooltip = "Clear deployment source path";
if (deployButton != null)
deployButton.tooltip = "Copy MCPForUnity to this project's package location";
if (deployRestoreButton != null)
deployRestoreButton.tooltip = "Restore the last backup before deployment";
gitUrlOverride.value = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, "");
bool debugEnabled = EditorPrefs.GetBool(EditorPrefKeys.DebugLogs, false);
debugLogsToggle.value = debugEnabled;
McpLog.SetDebugLoggingEnabled(debugEnabled);
validationLevelField.Init(ValidationLevel.Standard);
int savedLevel = EditorPrefs.GetInt(EditorPrefKeys.ValidationLevel, 1);
currentValidationLevel = (ValidationLevel)Mathf.Clamp(savedLevel, 0, 3);
validationLevelField.value = currentValidationLevel;
UpdateValidationDescription();
advancedSettingsFoldout.value = false;
gitUrlOverride.value = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, "");
devModeForceRefreshToggle.value = EditorPrefs.GetBool(EditorPrefKeys.DevModeForceServerRefresh, false);
UpdatePathOverrides();
UpdateDeploymentSection();
}
private void RegisterCallbacks()
{
debugLogsToggle.RegisterValueChangedCallback(evt =>
{
McpLog.SetDebugLoggingEnabled(evt.newValue);
});
validationLevelField.RegisterValueChangedCallback(evt =>
{
currentValidationLevel = (ValidationLevel)evt.newValue;
EditorPrefs.SetInt(EditorPrefKeys.ValidationLevel, (int)currentValidationLevel);
UpdateValidationDescription();
});
browseUvxButton.clicked += OnBrowseUvxClicked;
clearUvxButton.clicked += OnClearUvxClicked;
browseGitUrlButton.clicked += OnBrowseGitUrlClicked;
gitUrlOverride.RegisterValueChangedCallback(evt =>
@ -154,6 +161,11 @@ namespace MCPForUnity.Editor.Windows.Components.Settings
OnHttpServerCommandUpdateRequested?.Invoke();
};
debugLogsToggle.RegisterValueChangedCallback(evt =>
{
McpLog.SetDebugLoggingEnabled(evt.newValue);
});
devModeForceRefreshToggle.RegisterValueChangedCallback(evt =>
{
EditorPrefs.SetBool(EditorPrefKeys.DevModeForceServerRefresh, evt.newValue);
@ -167,7 +179,7 @@ namespace MCPForUnity.Editor.Windows.Components.Settings
{
return;
}
try
{
MCPServiceLocator.Deployment.SetStoredSourcePath(path);
@ -183,6 +195,7 @@ namespace MCPForUnity.Editor.Windows.Components.Settings
clearDeploySourceButton.clicked += OnClearDeploySourceClicked;
deployButton.clicked += OnDeployClicked;
deployRestoreButton.clicked += OnRestoreBackupClicked;
testConnectionButton.clicked += () => OnTestConnectionRequested?.Invoke();
}
public void UpdatePathOverrides()
@ -259,47 +272,11 @@ namespace MCPForUnity.Editor.Windows.Components.Settings
}
gitUrlOverride.value = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, "");
debugLogsToggle.value = EditorPrefs.GetBool(EditorPrefKeys.DebugLogs, false);
devModeForceRefreshToggle.value = EditorPrefs.GetBool(EditorPrefKeys.DevModeForceServerRefresh, false);
UpdateDeploymentSection();
}
private void UpdateVersionLabel()
{
string currentVersion = AssetPathUtility.GetPackageVersion();
versionLabel.text = $"v{currentVersion}";
var updateCheck = MCPServiceLocator.Updates.CheckForUpdate(currentVersion);
if (updateCheck.UpdateAvailable && !string.IsNullOrEmpty(updateCheck.LatestVersion))
{
versionLabel.text = $"\u2191 v{currentVersion} (Update available: v{updateCheck.LatestVersion})";
versionLabel.style.color = new Color(1f, 0.7f, 0f);
versionLabel.tooltip = $"Version {updateCheck.LatestVersion} is available. Update via Package Manager.\n\nGit URL: https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity";
}
else
{
versionLabel.style.color = StyleKeyword.Null;
versionLabel.tooltip = $"Current version: {currentVersion}";
}
}
private void UpdateValidationDescription()
{
validationDescription.text = GetValidationLevelDescription((int)currentValidationLevel);
}
private string GetValidationLevelDescription(int index)
{
return index switch
{
0 => "Only basic syntax checks (braces, quotes, comments)",
1 => "Syntax checks + Unity best practices and warnings",
2 => "All checks + semantic analysis and performance warnings",
3 => "Full semantic validation with namespace/type resolution (requires Roslyn)",
_ => "Standard validation"
};
}
private void OnBrowseUvxClicked()
{
string suggested = RuntimeInformation.IsOSPlatform(OSPlatform.OSX)
@ -439,5 +416,33 @@ namespace MCPForUnity.Editor.Windows.Components.Settings
? new StyleColor(new Color(0.85f, 0.2f, 0.2f))
: StyleKeyword.Null;
}
public void UpdateHealthStatus(bool isHealthy, string statusText)
{
if (healthStatus != null)
{
healthStatus.text = statusText;
}
if (healthIndicator != null)
{
healthIndicator.RemoveFromClassList("healthy");
healthIndicator.RemoveFromClassList("disconnected");
healthIndicator.RemoveFromClassList("unknown");
if (isHealthy)
{
healthIndicator.AddToClassList("healthy");
}
else if (statusText == HealthStatus.Unknown)
{
healthIndicator.AddToClassList("unknown");
}
else
{
healthIndicator.AddToClassList("disconnected");
}
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bf87d9c1c3b287e4180379f65af95dca
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,61 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="True">
<Style src="../Common.uss" />
<ui:VisualElement name="advanced-section" class="section">
<ui:Label text="Advanced Settings" class="section-title" />
<ui:VisualElement class="section-content">
<ui:VisualElement class="override-row">
<ui:Label text="UVX Path:" class="override-label" />
<ui:VisualElement class="status-indicator-small" name="uv-path-status" />
</ui:VisualElement>
<ui:VisualElement class="path-override-controls">
<ui:TextField name="uv-path-override" readonly="true" class="override-field" />
<ui:Button name="browse-uv-button" text="Browse" class="icon-button" />
<ui:Button name="clear-uv-button" text="Clear" class="icon-button" />
</ui:VisualElement>
<ui:VisualElement class="override-row" style="margin-top: 8px;">
<ui:Label text="Server Source:" class="override-label" />
</ui:VisualElement>
<ui:VisualElement class="path-override-controls">
<ui:TextField name="git-url-override" placeholder-text="/path/to/Server or git+https://..." class="override-field" />
<ui:Button name="browse-git-url-button" text="Select" class="icon-button" />
<ui:Button name="clear-git-url-button" text="Clear" class="icon-button" />
</ui:VisualElement>
<ui:VisualElement class="setting-row" style="margin-top: 8px;">
<ui:Label text="Debug Logging:" class="setting-label" />
<ui:Toggle name="debug-logs-toggle" class="setting-toggle" />
</ui:VisualElement>
<ui:VisualElement class="setting-row">
<ui:Label text="Server Health:" class="setting-label" />
<ui:VisualElement class="status-container">
<ui:VisualElement name="health-indicator" class="status-dot" />
<ui:Label name="health-status" text="Unknown" class="status-text" />
</ui:VisualElement>
<ui:Button name="test-connection-button" text="Test" class="action-button" />
</ui:VisualElement>
<ui:VisualElement class="setting-row">
<ui:Label text="Force Fresh Install:" class="setting-label" />
<ui:Toggle name="dev-mode-force-refresh-toggle" class="setting-toggle" />
</ui:VisualElement>
<ui:VisualElement class="override-row" style="margin-top: 8px;">
<ui:Label text="Package Source:" class="override-label" />
</ui:VisualElement>
<ui:VisualElement class="path-override-controls">
<ui:TextField name="deploy-source-path" class="override-field" />
<ui:Button name="browse-deploy-source-button" text="Select" class="icon-button" />
<ui:Button name="clear-deploy-source-button" text="Clear" class="icon-button" />
</ui:VisualElement>
<ui:Label name="deploy-target-label" class="help-text" />
<ui:Label name="deploy-backup-label" class="help-text" />
<ui:VisualElement class="path-override-controls" style="margin-top: 4px;">
<ui:Button name="deploy-button" text="Deploy" class="icon-button" />
<ui:Button name="deploy-restore-button" text="Restore" class="icon-button" />
</ui:VisualElement>
<ui:Label name="deploy-status-label" class="help-text" />
</ui:VisualElement>
</ui:VisualElement>
</ui:UXML>

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 405ae4db7c048f542ac9b8f83099bc45
guid: d7e63a0b220a4c9458289415ad91e7df
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}

View File

@ -31,6 +31,7 @@ namespace MCPForUnity.Editor.Windows.Components.ClientConfig
private VisualElement claudeCliPathRow;
private TextField claudeCliPath;
private Button browseClaudeButton;
private Foldout manualConfigFoldout;
private TextField configPathField;
private Button copyPathButton;
private Button openFileButton;
@ -45,6 +46,13 @@ namespace MCPForUnity.Editor.Windows.Components.ClientConfig
private static readonly TimeSpan StatusRefreshInterval = TimeSpan.FromSeconds(45);
private int selectedClientIndex = 0;
// Events
/// <summary>
/// Fired when the selected client's configured transport is detected/updated.
/// The parameter contains the client name and its configured transport.
/// </summary>
public event Action<string, ConfiguredTransport> OnClientTransportDetected;
public VisualElement Root { get; private set; }
public McpClientConfigSection(VisualElement root)
@ -66,6 +74,7 @@ namespace MCPForUnity.Editor.Windows.Components.ClientConfig
claudeCliPathRow = Root.Q<VisualElement>("claude-cli-path-row");
claudeCliPath = Root.Q<TextField>("claude-cli-path");
browseClaudeButton = Root.Q<Button>("browse-claude-button");
manualConfigFoldout = Root.Q<Foldout>("manual-config-foldout");
configPathField = Root.Q<TextField>("config-path");
copyPathButton = Root.Q<Button>("copy-path-button");
openFileButton = Root.Q<Button>("open-file-button");
@ -76,6 +85,12 @@ namespace MCPForUnity.Editor.Windows.Components.ClientConfig
private void InitializeUI()
{
// Ensure manual config foldout starts collapsed
if (manualConfigFoldout != null)
{
manualConfigFoldout.value = false;
}
var clientNames = configurators.Select(c => c.DisplayName).ToList();
clientDropdown.choices = clientNames;
if (clientNames.Count > 0)
@ -84,7 +99,7 @@ namespace MCPForUnity.Editor.Windows.Components.ClientConfig
}
claudeCliPathRow.style.display = DisplayStyle.None;
// Initialize the configuration display for the first selected client
UpdateClientStatus();
UpdateManualConfiguration();
@ -510,27 +525,48 @@ namespace MCPForUnity.Editor.Windows.Components.ClientConfig
return;
}
clientStatusLabel.text = GetStatusDisplayString(client.Status);
clientStatusLabel.style.color = StyleKeyword.Null;
switch (client.Status)
// Check for transport mismatch
bool hasTransportMismatch = false;
if (client.ConfiguredTransport != ConfiguredTransport.Unknown)
{
case McpStatus.Configured:
case McpStatus.Running:
case McpStatus.Connected:
clientStatusIndicator.AddToClassList("configured");
break;
case McpStatus.IncorrectPath:
case McpStatus.CommunicationError:
case McpStatus.NoResponse:
clientStatusIndicator.AddToClassList("warning");
break;
default:
clientStatusIndicator.AddToClassList("not-configured");
break;
bool serverUsesHttp = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true);
ConfiguredTransport serverTransport = serverUsesHttp ? ConfiguredTransport.Http : ConfiguredTransport.Stdio;
hasTransportMismatch = client.ConfiguredTransport != serverTransport;
}
// If configured but with transport mismatch, show warning state
if (hasTransportMismatch && (client.Status == McpStatus.Configured || client.Status == McpStatus.Running || client.Status == McpStatus.Connected))
{
clientStatusLabel.text = "Transport Mismatch";
clientStatusIndicator.AddToClassList("warning");
}
else
{
clientStatusLabel.text = GetStatusDisplayString(client.Status);
switch (client.Status)
{
case McpStatus.Configured:
case McpStatus.Running:
case McpStatus.Connected:
clientStatusIndicator.AddToClassList("configured");
break;
case McpStatus.IncorrectPath:
case McpStatus.CommunicationError:
case McpStatus.NoResponse:
clientStatusIndicator.AddToClassList("warning");
break;
default:
clientStatusIndicator.AddToClassList("not-configured");
break;
}
}
clientStatusLabel.style.color = StyleKeyword.Null;
configureButton.text = client.GetConfigureActionLabel();
// Notify listeners about the client's configured transport
OnClientTransportDetected?.Invoke(client.DisplayName, client.ConfiguredTransport);
}
}
}

View File

@ -20,7 +20,7 @@
<ui:TextField name="claude-cli-path" readonly="true" class="path-display-field" />
<ui:Button name="browse-claude-button" text="Browse" class="icon-button" />
</ui:VisualElement>
<ui:Foldout name="manual-config-foldout" text="Manual Configuration" class="manual-config-foldout">
<ui:Foldout name="manual-config-foldout" text="Manual Configuration" value="false" class="manual-config-foldout">
<ui:VisualElement class="manual-config-content">
<ui:Label text="Config Path:" class="config-label" />
<ui:VisualElement class="path-row">

View File

@ -10,29 +10,38 @@
font-size: 20px;
-unity-font-style: bold;
margin-bottom: 20px;
padding: 12px;
padding: 8px;
background-color: rgba(0, 0, 0, 0.1);
border-radius: 4px;
}
/* Section Styling */
.section {
margin-bottom: 16px;
padding: 12px;
background-color: rgba(0, 0, 0, 0.05);
border-radius: 6px;
margin-bottom: 14px;
padding: 8px;
background-color: rgba(0, 0, 0, 0.04);
border-radius: 4px;
border-width: 1px;
border-color: rgba(0, 0, 0, 0.2);
border-color: rgba(0, 0, 0, 0.15);
}
/* Remove bottom margin from last section in a stack */
.section-stack > .section:last-child {
margin-bottom: 0px;
}
.section-title {
font-size: 14px;
font-size: 13px;
-unity-font-style: bold;
margin-bottom: 12px;
margin-bottom: 8px;
padding-bottom: 6px;
letter-spacing: 0.3px;
border-bottom-width: 1px;
border-bottom-color: rgba(255, 255, 255, 0.08);
}
.section-content {
padding: 8px;
padding: 4px;
}
/* Setting Rows */
@ -41,7 +50,7 @@
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
margin-bottom: 4px;
min-height: 24px;
flex-shrink: 1;
min-width: 0;
@ -49,7 +58,7 @@
.setting-column {
flex-direction: column;
margin-bottom: 8px;
margin-bottom: 4px;
flex-shrink: 1;
min-width: 0;
}
@ -394,6 +403,23 @@
font-size: 12px;
}
/* Manual Command Foldout */
.manual-command-foldout {
margin-top: 8px;
margin-bottom: 8px;
}
.manual-command-foldout > .unity-foldout__toggle {
font-size: 11px;
padding: 4px;
background-color: rgba(0, 0, 0, 0.05);
border-radius: 3px;
}
.manual-command-foldout > .unity-foldout__content {
margin-top: 4px;
}
.advanced-settings-content {
padding: 8px;
margin-top: 8px;
@ -496,6 +522,10 @@
border-color: rgba(0, 0, 0, 0.15);
}
.unity-theme-light .section-title {
border-bottom-color: rgba(0, 0, 0, 0.1);
}
.unity-theme-dark .tool-tag {
color: rgba(220, 220, 220, 1);
background-color: rgba(80, 80, 80, 0.6);
@ -547,3 +577,37 @@
.unity-theme-light .path-display-field > .unity-text-field__input {
background-color: rgba(0, 0, 0, 0.05);
}
/* Warning Banner (for transport mismatch, etc.) */
.warning-banner {
display: none;
padding: 8px 12px;
margin-bottom: 6px;
background-color: rgba(255, 180, 0, 0.2);
border-radius: 4px;
border-width: 1px;
border-color: rgba(255, 180, 0, 0.5);
}
.warning-banner.visible {
display: flex;
}
.warning-banner-text {
font-size: 11px;
white-space: normal;
color: rgba(180, 120, 0, 1);
}
.unity-theme-dark .warning-banner {
background-color: rgba(255, 180, 0, 0.15);
border-color: rgba(255, 180, 0, 0.4);
}
.unity-theme-dark .warning-banner-text {
color: rgba(255, 200, 100, 1);
}
.unity-theme-light .manual-command-foldout > .unity-foldout__toggle {
background-color: rgba(0, 0, 0, 0.03);
}

View File

@ -2,6 +2,7 @@ using System;
using System.Threading.Tasks;
using MCPForUnity.Editor.Constants;
using MCPForUnity.Editor.Helpers;
using MCPForUnity.Editor.Models;
using MCPForUnity.Editor.Services;
using MCPForUnity.Editor.Services.Transport;
using UnityEditor;
@ -27,7 +28,11 @@ namespace MCPForUnity.Editor.Windows.Components.Connection
// UI Elements
private EnumField transportDropdown;
private VisualElement transportMismatchWarning;
private Label transportMismatchText;
private VisualElement httpUrlRow;
private VisualElement httpServerControlRow;
private Foldout manualCommandFoldout;
private VisualElement httpServerCommandSection;
private TextField httpServerCommandField;
private Button copyHttpServerCommandButton;
@ -35,17 +40,11 @@ namespace MCPForUnity.Editor.Windows.Components.Connection
private TextField httpUrlField;
private Button startHttpServerButton;
private Button stopHttpServerButton;
private VisualElement projectScopedToolsRow;
private Toggle projectScopedToolsToggle;
private VisualElement unitySocketPortRow;
private TextField unityPortField;
private VisualElement statusIndicator;
private Label connectionStatusLabel;
private Button connectionToggleButton;
private VisualElement healthIndicator;
private Label healthStatusLabel;
private VisualElement healthRow;
private Button testConnectionButton;
private bool connectionToggleInProgress;
private bool httpServerToggleInProgress;
@ -54,11 +53,8 @@ namespace MCPForUnity.Editor.Windows.Components.Connection
private double lastLocalServerRunningPollTime;
private bool lastLocalServerRunning;
// Health status constants
private const string HealthStatusUnknown = "Unknown";
private const string HealthStatusHealthy = "Healthy";
private const string HealthStatusPingFailed = "Ping Failed";
private const string HealthStatusUnhealthy = "Unhealthy";
// Reference to Advanced section for health status updates
private Action<bool, string> onHealthStatusUpdate;
// Events
public event Action OnManualConfigUpdateRequested;
@ -66,6 +62,11 @@ namespace MCPForUnity.Editor.Windows.Components.Connection
public VisualElement Root { get; private set; }
public void SetHealthStatusUpdateCallback(Action<bool, string> callback)
{
onHealthStatusUpdate = callback;
}
public McpConnectionSection(VisualElement root)
{
Root = root;
@ -77,7 +78,11 @@ namespace MCPForUnity.Editor.Windows.Components.Connection
private void CacheUIElements()
{
transportDropdown = Root.Q<EnumField>("transport-dropdown");
transportMismatchWarning = Root.Q<VisualElement>("transport-mismatch-warning");
transportMismatchText = Root.Q<Label>("transport-mismatch-text");
httpUrlRow = Root.Q<VisualElement>("http-url-row");
httpServerControlRow = Root.Q<VisualElement>("http-server-control-row");
manualCommandFoldout = Root.Q<Foldout>("manual-command-foldout");
httpServerCommandSection = Root.Q<VisualElement>("http-server-command-section");
httpServerCommandField = Root.Q<TextField>("http-server-command");
copyHttpServerCommandButton = Root.Q<Button>("copy-http-server-command-button");
@ -85,21 +90,21 @@ namespace MCPForUnity.Editor.Windows.Components.Connection
httpUrlField = Root.Q<TextField>("http-url");
startHttpServerButton = Root.Q<Button>("start-http-server-button");
stopHttpServerButton = Root.Q<Button>("stop-http-server-button");
projectScopedToolsRow = Root.Q<VisualElement>("project-scoped-tools-row");
projectScopedToolsToggle = Root.Q<Toggle>("project-scoped-tools-toggle");
unitySocketPortRow = Root.Q<VisualElement>("unity-socket-port-row");
unityPortField = Root.Q<TextField>("unity-port");
statusIndicator = Root.Q<VisualElement>("status-indicator");
connectionStatusLabel = Root.Q<Label>("connection-status");
connectionToggleButton = Root.Q<Button>("connection-toggle");
healthIndicator = Root.Q<VisualElement>("health-indicator");
healthStatusLabel = Root.Q<Label>("health-status");
healthRow = Root.Q<VisualElement>("health-row");
testConnectionButton = Root.Q<Button>("test-connection-button");
}
private void InitializeUI()
{
// Ensure manual command foldout starts collapsed
if (manualCommandFoldout != null)
{
manualCommandFoldout.value = false;
}
transportDropdown.Init(TransportProtocol.HTTPLocal);
bool useHttpTransport = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true);
if (!useHttpTransport)
@ -126,15 +131,15 @@ namespace MCPForUnity.Editor.Windows.Components.Connection
transportDropdown.value = scope == "remote" ? TransportProtocol.HTTPRemote : TransportProtocol.HTTPLocal;
}
httpUrlField.value = HttpEndpointUtility.GetBaseUrl();
// Set tooltips
if (httpUrlField != null)
httpUrlField.tooltip = "HTTP endpoint URL for the MCP server. Use localhost for local servers.";
if (unityPortField != null)
unityPortField.tooltip = "Port for Unity's internal MCP bridge socket. Used for stdio transport.";
if (connectionToggleButton != null)
connectionToggleButton.tooltip = "Start or end the MCP session between Unity and the server.";
if (projectScopedToolsToggle != null)
{
projectScopedToolsToggle.value = EditorPrefs.GetBool(
EditorPrefKeys.ProjectScopedToolsLocalHttp,
false
);
}
httpUrlField.value = HttpEndpointUtility.GetBaseUrl();
int unityPort = EditorPrefs.GetInt(EditorPrefKeys.UnitySocketPort, 0);
if (unityPort == 0)
@ -146,16 +151,6 @@ namespace MCPForUnity.Editor.Windows.Components.Connection
UpdateHttpFieldVisibility();
RefreshHttpUi();
UpdateConnectionStatus();
// Explain what "Health" means (it is a separate verify/ping check and can differ from session state).
if (healthStatusLabel != null)
{
healthStatusLabel.tooltip = "Health is a lightweight verify/ping of the active transport. A session can be active while health is degraded.";
}
if (healthIndicator != null)
{
healthIndicator.tooltip = healthStatusLabel?.tooltip;
}
}
private void RegisterCallbacks()
@ -243,16 +238,6 @@ namespace MCPForUnity.Editor.Windows.Components.Connection
};
}
if (projectScopedToolsToggle != null)
{
projectScopedToolsToggle.RegisterValueChangedCallback(evt =>
{
EditorPrefs.SetBool(EditorPrefKeys.ProjectScopedToolsLocalHttp, evt.newValue);
UpdateHttpServerCommandDisplay();
OnManualConfigUpdateRequested?.Invoke();
});
}
if (copyHttpServerCommandButton != null)
{
copyHttpServerCommandButton.clicked += () =>
@ -276,7 +261,6 @@ namespace MCPForUnity.Editor.Windows.Components.Connection
});
connectionToggleButton.clicked += OnConnectionToggleClicked;
testConnectionButton.clicked += OnTestConnectionClicked;
}
private void PersistHttpUrlFromField()
@ -327,20 +311,6 @@ namespace MCPForUnity.Editor.Windows.Components.Connection
connectionToggleButton.style.display = showSessionToggle ? DisplayStyle.Flex : DisplayStyle.None;
}
// Hide "Test" buttons unless Debug Mode is enabled.
if (testConnectionButton != null)
{
testConnectionButton.style.display = debugMode ? DisplayStyle.Flex : DisplayStyle.None;
}
// Health is useful mainly for diagnostics: hide it once we're "Healthy" unless Debug Mode is enabled.
// If health is degraded, keep it visible even outside Debug Mode so it can act as a signal.
if (healthRow != null)
{
bool showHealth = debugMode || (isRunning && lastHealthStatus != HealthStatusHealthy);
healthRow.style.display = showHealth ? DisplayStyle.Flex : DisplayStyle.None;
}
if (isRunning)
{
// Show instance name (project folder name) for better identification in multi-instance scenarios.
@ -387,11 +357,6 @@ namespace MCPForUnity.Editor.Windows.Components.Connection
unityPortField.SetEnabled(!isStdioResuming);
healthStatusLabel.text = HealthStatusUnknown;
healthIndicator.RemoveFromClassList("healthy");
healthIndicator.RemoveFromClassList("warning");
healthIndicator.AddToClassList("unknown");
int savedPort = EditorPrefs.GetInt(EditorPrefKeys.UnitySocketPort, 0);
unityPortField.value = (savedPort == 0
? bridgeService.CurrentPort
@ -487,26 +452,13 @@ namespace MCPForUnity.Editor.Windows.Components.Connection
private void UpdateHttpFieldVisibility()
{
bool useHttp = (TransportProtocol)transportDropdown.value != TransportProtocol.Stdio;
bool httpLocalSelected = IsHttpLocalSelected();
httpUrlRow.style.display = useHttp ? DisplayStyle.Flex : DisplayStyle.None;
UpdateProjectScopedToolsVisibility();
httpServerControlRow.style.display = useHttp && httpLocalSelected ? DisplayStyle.Flex : DisplayStyle.None;
unitySocketPortRow.style.display = useHttp ? DisplayStyle.None : DisplayStyle.Flex;
}
private void UpdateProjectScopedToolsVisibility()
{
if (projectScopedToolsRow == null)
{
return;
}
bool useHttp = transportDropdown != null && (TransportProtocol)transportDropdown.value != TransportProtocol.Stdio;
bool httpLocalSelected = IsHttpLocalSelected();
projectScopedToolsRow.style.display = useHttp && httpLocalSelected
? DisplayStyle.Flex
: DisplayStyle.None;
}
private bool IsHttpLocalSelected()
{
return transportDropdown != null && (TransportProtocol)transportDropdown.value == TransportProtocol.HTTPLocal;
@ -562,7 +514,6 @@ namespace MCPForUnity.Editor.Windows.Components.Connection
{
UpdateStartHttpButtonState();
UpdateHttpServerCommandDisplay();
UpdateProjectScopedToolsVisibility();
}
private async void OnHttpServerToggleClicked()
@ -759,11 +710,6 @@ namespace MCPForUnity.Editor.Windows.Components.Connection
}
}
private async void OnTestConnectionClicked()
{
await VerifyBridgeConnectionAsync();
}
private async Task EndOrphanedSessionAsync()
{
// Fire-and-forget cleanup of orphaned session when server is no longer running.
@ -808,33 +754,26 @@ namespace MCPForUnity.Editor.Windows.Components.Connection
var bridgeService = MCPServiceLocator.Bridge;
if (!bridgeService.IsRunning)
{
healthStatusLabel.text = HealthStatusUnknown;
healthIndicator.RemoveFromClassList("healthy");
healthIndicator.RemoveFromClassList("warning");
healthIndicator.AddToClassList("unknown");
onHealthStatusUpdate?.Invoke(false, HealthStatus.Unknown);
// Only log if state changed
if (lastHealthStatus != HealthStatusUnknown)
if (lastHealthStatus != HealthStatus.Unknown)
{
McpLog.Warn("Cannot verify connection: Bridge is not running");
lastHealthStatus = HealthStatusUnknown;
lastHealthStatus = HealthStatus.Unknown;
}
return;
}
var result = await bridgeService.VerifyAsync();
healthIndicator.RemoveFromClassList("healthy");
healthIndicator.RemoveFromClassList("warning");
healthIndicator.RemoveFromClassList("unknown");
string newStatus;
bool isHealthy;
if (result.Success && result.PingSucceeded)
{
newStatus = HealthStatusHealthy;
healthStatusLabel.text = newStatus;
healthIndicator.AddToClassList("healthy");
newStatus = HealthStatus.Healthy;
isHealthy = true;
// Only log if state changed
if (lastHealthStatus != newStatus)
{
@ -844,10 +783,9 @@ namespace MCPForUnity.Editor.Windows.Components.Connection
}
else if (result.HandshakeValid)
{
newStatus = HealthStatusPingFailed;
healthStatusLabel.text = newStatus;
healthIndicator.AddToClassList("warning");
newStatus = HealthStatus.PingFailed;
isHealthy = false;
// Log once per distinct warning state
if (lastHealthStatus != newStatus)
{
@ -857,10 +795,9 @@ namespace MCPForUnity.Editor.Windows.Components.Connection
}
else
{
newStatus = HealthStatusUnhealthy;
healthStatusLabel.text = newStatus;
healthIndicator.AddToClassList("warning");
newStatus = HealthStatus.Unhealthy;
isHealthy = false;
// Log once per distinct error state
if (lastHealthStatus != newStatus)
{
@ -868,6 +805,56 @@ namespace MCPForUnity.Editor.Windows.Components.Connection
lastHealthStatus = newStatus;
}
}
onHealthStatusUpdate?.Invoke(isHealthy, newStatus);
}
/// <summary>
/// Updates the transport mismatch warning banner based on the client's configured transport.
/// Shows a warning if the client's transport doesn't match the server's current transport setting.
/// </summary>
/// <param name="clientName">The display name of the client being checked.</param>
/// <param name="clientTransport">The transport the client is configured to use.</param>
public void UpdateTransportMismatchWarning(string clientName, ConfiguredTransport clientTransport)
{
if (transportMismatchWarning == null || transportMismatchText == null)
return;
// If client transport is unknown, hide the warning (we can't determine mismatch)
if (clientTransport == ConfiguredTransport.Unknown)
{
transportMismatchWarning.RemoveFromClassList("visible");
return;
}
// Determine the server's current transport setting
bool serverUsesHttp = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true);
ConfiguredTransport serverTransport = serverUsesHttp ? ConfiguredTransport.Http : ConfiguredTransport.Stdio;
// Check for mismatch
bool hasMismatch = clientTransport != serverTransport;
if (hasMismatch)
{
string clientTransportName = clientTransport == ConfiguredTransport.Http ? "HTTP" : "stdio";
string serverTransportName = serverTransport == ConfiguredTransport.Http ? "HTTP" : "stdio";
transportMismatchText.text = $"⚠ {clientName} is configured for \"{clientTransportName}\" but server is set to \"{serverTransportName}\". " +
"Click \"Configure\" in Client Configuration to update.";
transportMismatchWarning.AddToClassList("visible");
}
else
{
transportMismatchWarning.RemoveFromClassList("visible");
}
}
/// <summary>
/// Clears the transport mismatch warning banner.
/// </summary>
public void ClearTransportMismatchWarning()
{
transportMismatchWarning?.RemoveFromClassList("visible");
}
}
}

View File

@ -1,30 +1,22 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="True">
<Style src="../Common.uss" />
<ui:VisualElement name="connection-section" class="section">
<ui:Label text="Connection" class="section-title" />
<ui:Label text="Server" class="section-title" />
<ui:VisualElement class="section-content">
<ui:VisualElement class="setting-row">
<ui:Label text="Transport:" class="setting-label" />
<uie:EnumField name="transport-dropdown" class="setting-dropdown-inline" />
</ui:VisualElement>
<ui:VisualElement name="transport-mismatch-warning" class="warning-banner">
<ui:Label name="transport-mismatch-text" class="warning-banner-text" />
</ui:VisualElement>
<ui:VisualElement class="setting-row" name="http-url-row">
<ui:Label text="HTTP URL:" class="setting-label" />
<ui:TextField name="http-url" class="url-field" />
</ui:VisualElement>
<ui:VisualElement name="http-server-command-section" class="manual-config-content">
<ui:Label text="Use this command to launch the server manually:" class="config-label" />
<ui:VisualElement class="config-json-row">
<ui:TextField name="http-server-command" readonly="true" multiline="true" class="config-json-field" />
<ui:Button name="copy-http-server-command-button" text="Copy" class="icon-button-vertical" />
</ui:VisualElement>
<ui:Label name="http-server-command-hint" class="help-text" />
<ui:VisualElement style="flex-direction: row; justify-content: flex-start; margin-bottom: 6px;">
<ui:Button name="start-http-server-button" text="Start Server" class="action-button start-server-button" style="width: auto; flex-grow: 1;" />
</ui:VisualElement>
</ui:VisualElement>
<ui:VisualElement class="setting-row" name="project-scoped-tools-row">
<ui:Label text="Project-Scoped Tools:" class="setting-label" />
<ui:Toggle name="project-scoped-tools-toggle" />
<ui:VisualElement class="setting-row" name="http-server-control-row">
<ui:Label text="Local Server:" class="setting-label" />
<ui:Button name="start-http-server-button" text="Start Server" class="action-button start-server-button" />
</ui:VisualElement>
<ui:VisualElement class="setting-row" name="unity-socket-port-row">
<ui:Label text="Unity Socket Port:" class="setting-label" />
@ -37,14 +29,16 @@
</ui:VisualElement>
<ui:Button name="connection-toggle" text="Start" class="action-button" />
</ui:VisualElement>
<ui:VisualElement class="setting-row" name="health-row">
<ui:Label text="Health:" class="setting-label" />
<ui:VisualElement class="status-container">
<ui:VisualElement name="health-indicator" class="status-dot" />
<ui:Label name="health-status" text="Unknown" class="status-text" />
<ui:Foldout name="manual-command-foldout" text="Manual Server Launch" value="false" class="manual-config-foldout">
<ui:VisualElement name="http-server-command-section" class="manual-config-content">
<ui:Label text="Use this command to launch the server manually:" class="config-label" />
<ui:VisualElement class="config-json-row">
<ui:TextField name="http-server-command" readonly="true" multiline="true" class="config-json-field" />
<ui:Button name="copy-http-server-command-button" text="Copy" class="icon-button-vertical" />
</ui:VisualElement>
<ui:Label name="http-server-command-hint" class="help-text" />
</ui:VisualElement>
<ui:Button name="test-connection-button" text="Test" class="action-button" />
</ui:VisualElement>
</ui:Foldout>
</ui:VisualElement>
</ui:VisualElement>
</ui:UXML>

View File

@ -1,72 +0,0 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="True">
<Style src="../Common.uss" />
<ui:VisualElement name="settings-section" class="section">
<ui:Label text="Settings" class="section-title" />
<ui:VisualElement class="section-content">
<ui:VisualElement class="setting-row">
<ui:Label text="Version:" class="setting-label" />
<ui:Label text="..." name="version-label" class="setting-value" />
</ui:VisualElement>
<ui:VisualElement class="setting-row">
<ui:Label text="Debug Mode:" class="setting-label" />
<ui:Toggle name="debug-logs-toggle" class="setting-toggle" />
</ui:VisualElement>
<ui:VisualElement class="setting-column">
<ui:Label text="Script Validation Level:" class="setting-label" />
<uie:EnumField name="validation-level" class="setting-dropdown" />
<ui:Label name="validation-description" class="validation-description" />
</ui:VisualElement>
<ui:Foldout name="advanced-settings-foldout" text="Advanced Settings" class="advanced-settings-foldout">
<ui:VisualElement class="advanced-settings-content">
<ui:Label text="Path Overrides (leave empty for auto-detection):" class="advanced-label" />
<ui:VisualElement class="override-row">
<ui:Label text="UVX Path:" class="override-label" />
<ui:VisualElement class="status-indicator-small" name="uv-path-status" />
</ui:VisualElement>
<ui:VisualElement class="path-override-controls">
<ui:TextField name="uv-path-override" readonly="true" class="override-field" />
<ui:Button name="browse-uv-button" text="Browse" class="icon-button" />
<ui:Button name="clear-uv-button" text="Clear" class="icon-button" />
</ui:VisualElement>
<ui:Label text="Server Source Override:" class="advanced-label" style="margin-top: 10px;" />
<ui:Label text="Override the source used for uvx --from. Leave empty to use default package." class="help-text" />
<ui:VisualElement class="override-row">
<ui:Label text="Server Path/URL:" class="override-label" />
</ui:VisualElement>
<ui:VisualElement class="path-override-controls">
<ui:TextField name="git-url-override" placeholder-text="/path/to/Server or git+https://..." class="override-field" />
<ui:Button name="browse-git-url-button" text="Select" class="icon-button" />
<ui:Button name="clear-git-url-button" text="Clear" class="icon-button" />
</ui:VisualElement>
<ui:Label text="Override example (default uses PyPI):" class="help-text" style="margin-top: 5px;" />
<ui:Label text="• Local dev: /path/to/unity-mcp/Server" class="help-text" />
<ui:Label text="Dev Mode:" class="advanced-label" style="margin-top: 10px;" />
<ui:Label text="When enabled, generated uvx commands add '--no-cache --refresh' before launching (slower startup, but avoids stale cached builds while iterating on the Server)." class="help-text" />
<ui:VisualElement class="setting-row">
<ui:Label text="Force fresh server install:" class="setting-label" />
<ui:Toggle name="dev-mode-force-refresh-toggle" class="setting-toggle" />
</ui:VisualElement>
<ui:Label text="Local Package Deployment:" class="advanced-label" style="margin-top: 12px;" />
<ui:Label text="Copy a MCPForUnity folder into this project's package location." class="help-text" />
<ui:VisualElement class="override-row">
<ui:Label text="MCP For Unity Source Folder:" class="override-label" />
</ui:VisualElement>
<ui:VisualElement class="path-override-controls">
<ui:TextField name="deploy-source-path" class="override-field" />
<ui:Button name="browse-deploy-source-button" text="Select" class="icon-button" />
<ui:Button name="clear-deploy-source-button" text="Clear" class="icon-button" />
</ui:VisualElement>
<ui:Label name="deploy-target-label" class="help-text" />
<ui:Label name="deploy-backup-label" class="help-text" />
<ui:VisualElement class="path-override-controls" style="margin-top: 4px;">
<ui:Button name="deploy-button" text="Deploy to Project" class="icon-button" />
<ui:Button name="deploy-restore-button" text="Restore Last Backup" class="icon-button" />
</ui:VisualElement>
<ui:Label name="deploy-status-label" class="help-text" />
</ui:VisualElement>
</ui:Foldout>
</ui:VisualElement>
</ui:VisualElement>
</ui:UXML>

View File

@ -17,6 +17,7 @@ namespace MCPForUnity.Editor.Windows.Components.Tools
public class McpToolsSection
{
private readonly Dictionary<string, Toggle> toolToggleMap = new();
private Toggle projectScopedToolsToggle;
private Label summaryLabel;
private Label noteLabel;
private Button enableAllButton;
@ -36,6 +37,7 @@ namespace MCPForUnity.Editor.Windows.Components.Tools
private void CacheUIElements()
{
projectScopedToolsToggle = Root.Q<Toggle>("project-scoped-tools-toggle");
summaryLabel = Root.Q<Label>("tools-summary");
noteLabel = Root.Q<Label>("tools-note");
enableAllButton = Root.Q<Button>("enable-all-button");
@ -46,6 +48,19 @@ namespace MCPForUnity.Editor.Windows.Components.Tools
private void RegisterCallbacks()
{
if (projectScopedToolsToggle != null)
{
projectScopedToolsToggle.value = EditorPrefs.GetBool(
EditorPrefKeys.ProjectScopedToolsLocalHttp,
false
);
projectScopedToolsToggle.tooltip = "When enabled, register project-scoped tools with HTTP Local transport. Allows per-project tool customization.";
projectScopedToolsToggle.RegisterValueChangedCallback(evt =>
{
EditorPrefs.SetBool(EditorPrefKeys.ProjectScopedToolsLocalHttp, evt.newValue);
});
}
if (enableAllButton != null)
{
enableAllButton.AddToClassList("tool-action-button");

View File

@ -2,6 +2,10 @@
<ui:VisualElement name="tools-section" class="section">
<ui:Label text="Tools" class="section-title" />
<ui:VisualElement class="section-content">
<ui:VisualElement class="setting-row" name="project-scoped-tools-row">
<ui:Label text="Project-Scoped Tools:" class="setting-label" />
<ui:Toggle name="project-scoped-tools-toggle" />
</ui:VisualElement>
<ui:Label name="tools-summary" class="help-text" text="Discovering tools..." />
<ui:VisualElement name="tools-actions" class="tool-actions">
<ui:Button name="enable-all-button" text="Enable All" class="tool-action-button" />

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f68f3b0ff9e214244ad7e57b106d5c60
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,79 @@
using System;
using MCPForUnity.Editor.Constants;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace MCPForUnity.Editor.Windows.Components.Validation
{
/// <summary>
/// Controller for the Script Validation section.
/// Handles script validation level settings.
/// </summary>
public class McpValidationSection
{
// UI Elements
private EnumField validationLevelField;
private Label validationDescription;
// Data
private ValidationLevel currentValidationLevel = ValidationLevel.Standard;
// Validation levels
public enum ValidationLevel
{
Basic,
Standard,
Comprehensive,
Strict
}
public VisualElement Root { get; private set; }
public McpValidationSection(VisualElement root)
{
Root = root;
CacheUIElements();
InitializeUI();
RegisterCallbacks();
}
private void CacheUIElements()
{
validationLevelField = Root.Q<EnumField>("validation-level");
validationDescription = Root.Q<Label>("validation-description");
}
private void InitializeUI()
{
validationLevelField.Init(ValidationLevel.Standard);
int savedLevel = EditorPrefs.GetInt(EditorPrefKeys.ValidationLevel, 1);
currentValidationLevel = (ValidationLevel)Mathf.Clamp(savedLevel, 0, 3);
validationLevelField.value = currentValidationLevel;
UpdateValidationDescription();
}
private void RegisterCallbacks()
{
validationLevelField.RegisterValueChangedCallback(evt =>
{
currentValidationLevel = (ValidationLevel)evt.newValue;
EditorPrefs.SetInt(EditorPrefKeys.ValidationLevel, (int)currentValidationLevel);
UpdateValidationDescription();
});
}
private void UpdateValidationDescription()
{
validationDescription.text = currentValidationLevel switch
{
ValidationLevel.Basic => "Basic: Validates syntax only. Fast compilation checks.",
ValidationLevel.Standard => "Standard (Recommended): Checks syntax + common errors. Balanced speed and coverage.",
ValidationLevel.Comprehensive => "Comprehensive: Detailed validation including code quality. Slower but thorough.",
ValidationLevel.Strict => "Strict: Maximum validation + warnings as errors. Slowest but catches all issues.",
_ => "Unknown validation level"
};
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c65b5fd2ed3efbf469bbc0a089f845e3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,13 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="True">
<Style src="../Common.uss" />
<ui:VisualElement name="validation-section" class="section">
<ui:Label text="Script Validation" class="section-title" />
<ui:VisualElement class="section-content">
<ui:VisualElement class="setting-column">
<ui:Label text="Validation Level:" class="setting-label" />
<uie:EnumField name="validation-level" class="setting-dropdown" />
<ui:Label name="validation-description" class="validation-description" />
</ui:VisualElement>
</ui:VisualElement>
</ui:VisualElement>
</ui:UXML>

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 3f682815a83bb6841ac61f7f399d903c
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}

View File

@ -5,10 +5,11 @@ using System.Threading.Tasks;
using MCPForUnity.Editor.Constants;
using MCPForUnity.Editor.Helpers;
using MCPForUnity.Editor.Services;
using MCPForUnity.Editor.Windows.Components.Advanced;
using MCPForUnity.Editor.Windows.Components.ClientConfig;
using MCPForUnity.Editor.Windows.Components.Connection;
using MCPForUnity.Editor.Windows.Components.Settings;
using MCPForUnity.Editor.Windows.Components.Tools;
using MCPForUnity.Editor.Windows.Components.Validation;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
@ -19,14 +20,24 @@ namespace MCPForUnity.Editor.Windows
public class MCPForUnityEditorWindow : EditorWindow
{
// Section controllers
private McpSettingsSection settingsSection;
private McpConnectionSection connectionSection;
private McpClientConfigSection clientConfigSection;
private McpValidationSection validationSection;
private McpAdvancedSection advancedSection;
private McpToolsSection toolsSection;
private ToolbarToggle settingsTabToggle;
// UI Elements
private Label versionLabel;
private VisualElement updateNotification;
private Label updateNotificationText;
private ToolbarToggle clientsTabToggle;
private ToolbarToggle validationTabToggle;
private ToolbarToggle advancedTabToggle;
private ToolbarToggle toolsTabToggle;
private VisualElement settingsPanel;
private VisualElement clientsPanel;
private VisualElement validationPanel;
private VisualElement advancedPanel;
private VisualElement toolsPanel;
private static readonly HashSet<MCPForUnityEditorWindow> OpenWindows = new();
@ -37,7 +48,9 @@ namespace MCPForUnity.Editor.Windows
private enum ActivePanel
{
Settings,
Clients,
Validation,
Advanced,
Tools
}
@ -53,7 +66,7 @@ namespace MCPForUnity.Editor.Windows
public static void ShowWindow()
{
var window = GetWindow<MCPForUnityEditorWindow>("MCP For Unity");
window.minSize = new Vector2(500, 600);
window.minSize = new Vector2(500, 340);
}
// Helper to check and manage open windows from other classes
@ -124,20 +137,41 @@ namespace MCPForUnity.Editor.Windows
rootVisualElement.styleSheets.Add(commonStyleSheet);
}
settingsPanel = rootVisualElement.Q<VisualElement>("settings-panel");
// Cache UI elements
versionLabel = rootVisualElement.Q<Label>("version-label");
updateNotification = rootVisualElement.Q<VisualElement>("update-notification");
updateNotificationText = rootVisualElement.Q<Label>("update-notification-text");
clientsPanel = rootVisualElement.Q<VisualElement>("clients-panel");
validationPanel = rootVisualElement.Q<VisualElement>("validation-panel");
advancedPanel = rootVisualElement.Q<VisualElement>("advanced-panel");
toolsPanel = rootVisualElement.Q<VisualElement>("tools-panel");
var settingsContainer = rootVisualElement.Q<VisualElement>("settings-container");
var clientsContainer = rootVisualElement.Q<VisualElement>("clients-container");
var validationContainer = rootVisualElement.Q<VisualElement>("validation-container");
var advancedContainer = rootVisualElement.Q<VisualElement>("advanced-container");
var toolsContainer = rootVisualElement.Q<VisualElement>("tools-container");
if (settingsPanel == null || toolsPanel == null)
if (clientsPanel == null || validationPanel == null || advancedPanel == null || toolsPanel == null)
{
McpLog.Error("Failed to find tab panels in UXML");
return;
}
if (settingsContainer == null)
if (clientsContainer == null)
{
McpLog.Error("Failed to find settings-container in UXML");
McpLog.Error("Failed to find clients-container in UXML");
return;
}
if (validationContainer == null)
{
McpLog.Error("Failed to find validation-container in UXML");
return;
}
if (advancedContainer == null)
{
McpLog.Error("Failed to find advanced-container in UXML");
return;
}
@ -147,23 +181,15 @@ namespace MCPForUnity.Editor.Windows
return;
}
SetupTabs();
// Load and initialize Settings section
var settingsTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(
$"{basePath}/Editor/Windows/Components/Settings/McpSettingsSection.uxml"
);
if (settingsTree != null)
// Initialize version label
if (versionLabel != null)
{
var settingsRoot = settingsTree.Instantiate();
settingsContainer.Add(settingsRoot);
settingsSection = new McpSettingsSection(settingsRoot);
settingsSection.OnGitUrlChanged += () =>
clientConfigSection?.UpdateManualConfiguration();
settingsSection.OnHttpServerCommandUpdateRequested += () =>
connectionSection?.UpdateHttpServerCommandDisplay();
string version = AssetPathUtility.GetPackageVersion();
versionLabel.text = $"v{version}";
}
SetupTabs();
// Load and initialize Connection section
var connectionTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(
$"{basePath}/Editor/Windows/Components/Connection/McpConnectionSection.uxml"
@ -171,7 +197,7 @@ namespace MCPForUnity.Editor.Windows
if (connectionTree != null)
{
var connectionRoot = connectionTree.Instantiate();
settingsContainer.Add(connectionRoot);
clientsContainer.Add(connectionRoot);
connectionSection = new McpConnectionSection(connectionRoot);
connectionSection.OnManualConfigUpdateRequested += () =>
clientConfigSection?.UpdateManualConfiguration();
@ -186,8 +212,50 @@ namespace MCPForUnity.Editor.Windows
if (clientConfigTree != null)
{
var clientConfigRoot = clientConfigTree.Instantiate();
settingsContainer.Add(clientConfigRoot);
clientsContainer.Add(clientConfigRoot);
clientConfigSection = new McpClientConfigSection(clientConfigRoot);
// Wire up transport mismatch detection: when client status is checked,
// update the connection section's warning banner if there's a mismatch
clientConfigSection.OnClientTransportDetected += (clientName, transport) =>
connectionSection?.UpdateTransportMismatchWarning(clientName, transport);
}
// Load and initialize Validation section
var validationTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(
$"{basePath}/Editor/Windows/Components/Validation/McpValidationSection.uxml"
);
if (validationTree != null)
{
var validationRoot = validationTree.Instantiate();
validationContainer.Add(validationRoot);
validationSection = new McpValidationSection(validationRoot);
}
// Load and initialize Advanced section
var advancedTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(
$"{basePath}/Editor/Windows/Components/Advanced/McpAdvancedSection.uxml"
);
if (advancedTree != null)
{
var advancedRoot = advancedTree.Instantiate();
advancedContainer.Add(advancedRoot);
advancedSection = new McpAdvancedSection(advancedRoot);
// Wire up events from Advanced section
advancedSection.OnGitUrlChanged += () =>
clientConfigSection?.UpdateManualConfiguration();
advancedSection.OnHttpServerCommandUpdateRequested += () =>
connectionSection?.UpdateHttpServerCommandDisplay();
advancedSection.OnTestConnectionRequested += async () =>
{
if (connectionSection != null)
await connectionSection.VerifyBridgeConnectionAsync();
};
// Wire up health status updates from Connection to Advanced
connectionSection?.SetHealthStatusUpdateCallback((isHealthy, statusText) =>
advancedSection?.UpdateHealthStatus(isHealthy, statusText));
}
// Load and initialize Tools section
@ -295,32 +363,43 @@ namespace MCPForUnity.Editor.Windows
_ = connectionSection?.VerifyBridgeConnectionAsync();
}
settingsSection?.UpdatePathOverrides();
advancedSection?.UpdatePathOverrides();
clientConfigSection?.RefreshSelectedClient();
}
private void SetupTabs()
{
settingsTabToggle = rootVisualElement.Q<ToolbarToggle>("settings-tab");
clientsTabToggle = rootVisualElement.Q<ToolbarToggle>("clients-tab");
validationTabToggle = rootVisualElement.Q<ToolbarToggle>("validation-tab");
advancedTabToggle = rootVisualElement.Q<ToolbarToggle>("advanced-tab");
toolsTabToggle = rootVisualElement.Q<ToolbarToggle>("tools-tab");
settingsPanel?.RemoveFromClassList("hidden");
clientsPanel?.RemoveFromClassList("hidden");
validationPanel?.RemoveFromClassList("hidden");
advancedPanel?.RemoveFromClassList("hidden");
toolsPanel?.RemoveFromClassList("hidden");
if (settingsTabToggle != null)
if (clientsTabToggle != null)
{
settingsTabToggle.RegisterValueChangedCallback(evt =>
clientsTabToggle.RegisterValueChangedCallback(evt =>
{
if (!evt.newValue)
{
if (toolsTabToggle != null && !toolsTabToggle.value)
{
settingsTabToggle.SetValueWithoutNotify(true);
}
return;
}
if (evt.newValue) SwitchPanel(ActivePanel.Clients);
});
}
SwitchPanel(ActivePanel.Settings);
if (validationTabToggle != null)
{
validationTabToggle.RegisterValueChangedCallback(evt =>
{
if (evt.newValue) SwitchPanel(ActivePanel.Validation);
});
}
if (advancedTabToggle != null)
{
advancedTabToggle.RegisterValueChangedCallback(evt =>
{
if (evt.newValue) SwitchPanel(ActivePanel.Advanced);
});
}
@ -328,23 +407,14 @@ namespace MCPForUnity.Editor.Windows
{
toolsTabToggle.RegisterValueChangedCallback(evt =>
{
if (!evt.newValue)
{
if (settingsTabToggle != null && !settingsTabToggle.value)
{
toolsTabToggle.SetValueWithoutNotify(true);
}
return;
}
SwitchPanel(ActivePanel.Tools);
if (evt.newValue) SwitchPanel(ActivePanel.Tools);
});
}
var savedPanel = EditorPrefs.GetString(EditorPrefKeys.EditorWindowActivePanel, ActivePanel.Settings.ToString());
var savedPanel = EditorPrefs.GetString(EditorPrefKeys.EditorWindowActivePanel, ActivePanel.Clients.ToString());
if (!Enum.TryParse(savedPanel, out ActivePanel initialPanel))
{
initialPanel = ActivePanel.Settings;
initialPanel = ActivePanel.Clients;
}
SwitchPanel(initialPanel);
@ -352,26 +422,51 @@ namespace MCPForUnity.Editor.Windows
private void SwitchPanel(ActivePanel panel)
{
bool showSettings = panel == ActivePanel.Settings;
if (settingsPanel != null)
// Hide all panels
if (clientsPanel != null)
{
settingsPanel.style.display = showSettings ? DisplayStyle.Flex : DisplayStyle.None;
clientsPanel.style.display = DisplayStyle.None;
}
if (validationPanel != null)
{
validationPanel.style.display = DisplayStyle.None;
}
if (advancedPanel != null)
{
advancedPanel.style.display = DisplayStyle.None;
}
if (toolsPanel != null)
{
toolsPanel.style.display = showSettings ? DisplayStyle.None : DisplayStyle.Flex;
toolsPanel.style.display = DisplayStyle.None;
}
settingsTabToggle?.SetValueWithoutNotify(showSettings);
toolsTabToggle?.SetValueWithoutNotify(!showSettings);
if (!showSettings)
// Show selected panel
switch (panel)
{
EnsureToolsLoaded();
case ActivePanel.Clients:
if (clientsPanel != null) clientsPanel.style.display = DisplayStyle.Flex;
break;
case ActivePanel.Validation:
if (validationPanel != null) validationPanel.style.display = DisplayStyle.Flex;
break;
case ActivePanel.Advanced:
if (advancedPanel != null) advancedPanel.style.display = DisplayStyle.Flex;
break;
case ActivePanel.Tools:
if (toolsPanel != null) toolsPanel.style.display = DisplayStyle.Flex;
EnsureToolsLoaded();
break;
}
// Update toggle states
clientsTabToggle?.SetValueWithoutNotify(panel == ActivePanel.Clients);
validationTabToggle?.SetValueWithoutNotify(panel == ActivePanel.Validation);
advancedTabToggle?.SetValueWithoutNotify(panel == ActivePanel.Advanced);
toolsTabToggle?.SetValueWithoutNotify(panel == ActivePanel.Tools);
EditorPrefs.SetString(EditorPrefKeys.EditorWindowActivePanel, panel.ToString());
}

View File

@ -1,34 +1,102 @@
/* Root Layout */
#root-container {
padding: 16px;
padding: 0px;
flex-direction: column;
flex-grow: 1;
overflow: hidden;
}
/* Title */
.title {
font-size: 20px;
-unity-font-style: bold;
margin-bottom: 16px;
padding: 12px;
background-color: rgba(0, 0, 0, 0.1);
/* Header Bar */
.header-bar {
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 12px 18px;
margin: 12px 12px 5px 12px;
min-height: 44px;
flex-shrink: 0;
background-color: rgba(0, 0, 0, 0.05);
border-radius: 4px;
border-width: 1px;
border-color: rgba(0, 0, 0, 0.15);
}
.header-title {
font-size: 16px;
-unity-font-style: bold;
letter-spacing: 0.5px;
}
.header-version {
font-size: 11px;
color: rgba(180, 210, 255, 1);
padding: 3px 10px;
background-color: rgba(50, 120, 200, 0.25);
border-radius: 10px;
border-width: 1px;
border-color: rgba(80, 150, 220, 0.4);
}
/* Update Notification */
.update-notification {
display: none;
padding: 8px 16px;
margin: 0px 12px;
background-color: rgba(100, 200, 100, 0.15);
border-radius: 4px;
border-width: 1px;
border-color: rgba(100, 200, 100, 0.3);
}
.update-notification.visible {
display: flex;
}
.update-notification-text {
font-size: 11px;
color: rgba(100, 200, 100, 1);
white-space: normal;
}
/* Tabs */
.tab-toolbar {
margin-bottom: 8px;
margin: 8px 12px 0px 12px;
padding: 0px;
background-color: transparent;
border-width: 0px;
border-bottom-width: 1px;
border-bottom-color: rgba(0, 0, 0, 0.15);
}
.tab-toolbar .unity-toolbar-button {
flex-grow: 1;
min-height: 32px;
font-size: 12px;
border-width: 0px;
background-color: transparent;
margin: 0px 2px 0px 0px;
padding: 0px 12px;
border-radius: 4px 4px 0px 0px;
margin-bottom: -1px;
}
.tab-toolbar .unity-toolbar-button:hover {
background-color: rgba(255, 255, 255, 0.05);
}
.tab-toolbar .unity-toolbar-button:checked {
background-color: rgba(0, 0, 0, 0.04);
border-width: 1px;
border-color: rgba(0, 0, 0, 0.15);
border-bottom-width: 1px;
border-bottom-color: rgba(56, 56, 56, 1);
}
/* Panels */
.panel-scroll {
flex-grow: 1;
margin-top: 8px;
margin: 0px;
padding: 0px 8px 0px 8px;
}
.hidden {
@ -38,3 +106,28 @@
.section-stack {
flex-direction: column;
}
/* Light Theme */
.unity-theme-light .header-bar {
background-color: rgba(0, 0, 0, 0.04);
border-color: rgba(0, 0, 0, 0.15);
}
.unity-theme-light .header-version {
background-color: rgba(0, 0, 0, 0.08);
}
.unity-theme-light .tab-toolbar .unity-toolbar-button:checked {
background-color: rgba(255, 255, 255, 0.5);
border-color: rgba(0, 0, 0, 0.15);
border-bottom-color: rgba(194, 194, 194, 1);
}
.unity-theme-light .update-notification {
background-color: rgba(100, 200, 100, 0.1);
border-color: rgba(100, 200, 100, 0.25);
}
.unity-theme-dark .update-notification-text {
color: rgba(150, 255, 150, 1);
}

View File

@ -1,12 +1,26 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="True">
<ui:VisualElement name="root-container" class="root-layout">
<ui:Label text="MCP For Unity" name="title" class="title" />
<ui:VisualElement name="header-bar" class="header-bar">
<ui:Label text="MCP For Unity" name="title" class="header-title" />
<ui:Label text="v9.1.0" name="version-label" class="header-version" />
</ui:VisualElement>
<ui:VisualElement name="update-notification" class="update-notification">
<ui:Label name="update-notification-text" class="update-notification-text" />
</ui:VisualElement>
<uie:Toolbar name="tab-toolbar" class="tab-toolbar">
<uie:ToolbarToggle name="settings-tab" text="Settings" value="true" />
<uie:ToolbarToggle name="clients-tab" text="Connect" value="true" />
<uie:ToolbarToggle name="tools-tab" text="Tools" />
<uie:ToolbarToggle name="validation-tab" text="Scripts" />
<uie:ToolbarToggle name="advanced-tab" text="Advanced" />
</uie:Toolbar>
<ui:ScrollView name="settings-panel" class="panel-scroll" style="flex-grow: 1;">
<ui:VisualElement name="settings-container" class="section-stack" />
<ui:ScrollView name="clients-panel" class="panel-scroll" style="flex-grow: 1;">
<ui:VisualElement name="clients-container" class="section-stack" />
</ui:ScrollView>
<ui:ScrollView name="validation-panel" class="panel-scroll hidden" style="flex-grow: 1;">
<ui:VisualElement name="validation-container" class="section-stack" />
</ui:ScrollView>
<ui:ScrollView name="advanced-panel" class="panel-scroll hidden" style="flex-grow: 1;">
<ui:VisualElement name="advanced-container" class="section-stack" />
</ui:ScrollView>
<ui:ScrollView name="tools-panel" class="panel-scroll hidden" style="flex-grow: 1;">
<ui:VisualElement name="tools-container" class="section-stack" />