Add EditorPrefs management window for MCP configuration debugging (#491)

* Add EditorPrefs management window for MCP configuration debugging

Meant to help with dev and testing, not so much the average user

* Update MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Revert "Update MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs"

This reverts commit 09bb4e1d2582678bc87d0ace45f9d8c3c88c3203.

* Reapply "Update MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs"

This reverts commit 6ccbc5e478f0bd2b61c992ae60db0ca367d651ae.

* Fix EditorPrefs type detection using sentinel values and null handling

* Simplify EditorPrefs type detection using known type mapping and basic parsing

Replace complex sentinel-based type detection with a dictionary of known pref types and simple TryParse fallback for unknown keys. Remove null handling and HasKey checks for known keys since they're defined in EditorPrefKeys.

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
main
Marcus Sanatan 2025-12-29 13:30:45 -04:00 committed by GitHub
parent eea02d1a0d
commit c770a8c713
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 755 additions and 7 deletions

View File

@ -7,13 +7,7 @@ namespace MCPForUnity.Editor.MenuItems
{ {
public static class MCPForUnityMenu public static class MCPForUnityMenu
{ {
[MenuItem("Window/MCP For Unity/Setup Window", priority = 1)] [MenuItem("Window/MCP For Unity/Toggle MCP Window %#m", priority = 1)]
public static void ShowSetupWindow()
{
SetupWindowService.ShowSetupWindow();
}
[MenuItem("Window/MCP For Unity/Toggle MCP Window %#m", priority = 2)]
public static void ToggleMCPWindow() public static void ToggleMCPWindow()
{ {
if (MCPForUnityEditorWindow.HasAnyOpenWindow()) if (MCPForUnityEditorWindow.HasAnyOpenWindow())
@ -25,5 +19,18 @@ namespace MCPForUnity.Editor.MenuItems
MCPForUnityEditorWindow.ShowWindow(); MCPForUnityEditorWindow.ShowWindow();
} }
} }
[MenuItem("Window/MCP For Unity/Setup Window", priority = 2)]
public static void ShowSetupWindow()
{
SetupWindowService.ShowSetupWindow();
}
[MenuItem("Window/MCP For Unity/EditorPrefs", priority = 3)]
public static void ShowEditorPrefsWindow()
{
EditorPrefsWindow.ShowWindow();
}
} }
} }

View File

@ -0,0 +1,54 @@
using System;
using MCPForUnity.Editor.Windows;
using UnityEditor;
namespace MCPForUnity.Editor.Services
{
/// <summary>
/// Service for managing the EditorPrefs window
/// Follows the Class-level Singleton pattern
/// </summary>
public class EditorPrefsWindowService
{
private static EditorPrefsWindowService _instance;
/// <summary>
/// Get the singleton instance
/// </summary>
public static EditorPrefsWindowService Instance
{
get
{
if (_instance == null)
{
throw new Exception("EditorPrefsWindowService not initialized");
}
return _instance;
}
}
/// <summary>
/// Initialize the service
/// </summary>
public static void Initialize()
{
if (_instance == null)
{
_instance = new EditorPrefsWindowService();
}
}
private EditorPrefsWindowService()
{
// Private constructor for singleton
}
/// <summary>
/// Show the EditorPrefs window
/// </summary>
public void ShowWindow()
{
EditorPrefsWindow.ShowWindow();
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,21 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="True">
<ui:VisualElement class="pref-item">
<ui:VisualElement class="pref-row">
<!-- Key and Known indicator -->
<ui:VisualElement class="key-section">
<ui:Label name="key-label" class="key-label" />
</ui:VisualElement>
<!-- Value field -->
<ui:TextField name="value-field" class="value-field" />
<!-- Type dropdown -->
<ui:DropdownField name="type-dropdown" class="type-dropdown" choices="String,Int,Float,Bool" index="0" />
<!-- Action buttons -->
<ui:VisualElement class="action-buttons">
<ui:Button name="save-button" text="✓" class="save-button" tooltip="Save changes" />
</ui:VisualElement>
</ui:VisualElement>
</ui:VisualElement>
</ui:UXML>

View File

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

View File

@ -0,0 +1,347 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using MCPForUnity.Editor.Constants;
using MCPForUnity.Editor.Helpers;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace MCPForUnity.Editor.Windows
{
/// <summary>
/// Editor window for managing Unity EditorPrefs, specifically for MCP For Unity development
/// </summary>
public class EditorPrefsWindow : EditorWindow
{
// UI Elements
private ScrollView scrollView;
private VisualElement prefsContainer;
// Data
private List<EditorPrefItem> currentPrefs = new List<EditorPrefItem>();
private HashSet<string> knownMcpKeys = new HashSet<string>();
// Type mapping for known EditorPrefs
private readonly Dictionary<string, EditorPrefType> knownPrefTypes = new Dictionary<string, EditorPrefType>
{
// Boolean prefs
{ EditorPrefKeys.DebugLogs, EditorPrefType.Bool },
{ EditorPrefKeys.UseHttpTransport, EditorPrefType.Bool },
{ EditorPrefKeys.ResumeHttpAfterReload, EditorPrefType.Bool },
{ EditorPrefKeys.ResumeStdioAfterReload, EditorPrefType.Bool },
{ EditorPrefKeys.UseEmbeddedServer, EditorPrefType.Bool },
{ EditorPrefKeys.LockCursorConfig, EditorPrefType.Bool },
{ EditorPrefKeys.AutoRegisterEnabled, EditorPrefType.Bool },
{ EditorPrefKeys.SetupCompleted, EditorPrefType.Bool },
{ EditorPrefKeys.SetupDismissed, EditorPrefType.Bool },
{ EditorPrefKeys.CustomToolRegistrationEnabled, EditorPrefType.Bool },
{ EditorPrefKeys.TelemetryDisabled, EditorPrefType.Bool },
// Integer prefs
{ EditorPrefKeys.UnitySocketPort, EditorPrefType.Int },
{ EditorPrefKeys.ValidationLevel, EditorPrefType.Int },
{ EditorPrefKeys.LastUpdateCheck, EditorPrefType.Int },
{ EditorPrefKeys.LastStdIoUpgradeVersion, EditorPrefType.Int },
// String prefs
{ EditorPrefKeys.EditorWindowActivePanel, EditorPrefType.String },
{ EditorPrefKeys.ClaudeCliPathOverride, EditorPrefType.String },
{ EditorPrefKeys.UvxPathOverride, EditorPrefType.String },
{ EditorPrefKeys.HttpBaseUrl, EditorPrefType.String },
{ EditorPrefKeys.SessionId, EditorPrefType.String },
{ EditorPrefKeys.WebSocketUrlOverride, EditorPrefType.String },
{ EditorPrefKeys.GitUrlOverride, EditorPrefType.String },
{ EditorPrefKeys.PackageDeploySourcePath, EditorPrefType.String },
{ EditorPrefKeys.PackageDeployLastBackupPath, EditorPrefType.String },
{ EditorPrefKeys.PackageDeployLastTargetPath, EditorPrefType.String },
{ EditorPrefKeys.PackageDeployLastSourcePath, EditorPrefType.String },
{ EditorPrefKeys.ServerSrc, EditorPrefType.String },
{ EditorPrefKeys.LatestKnownVersion, EditorPrefType.String },
};
// Templates
private VisualTreeAsset itemTemplate;
/// <summary>
/// Show the EditorPrefs window
/// </summary>
public static void ShowWindow()
{
var window = GetWindow<EditorPrefsWindow>("EditorPrefs");
window.minSize = new Vector2(600, 400);
window.Show();
}
public void CreateGUI()
{
string basePath = AssetPathUtility.GetMcpPackageRootPath();
// Load UXML
var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(
$"{basePath}/Editor/Windows/EditorPrefs/EditorPrefsWindow.uxml"
);
if (visualTree == null)
{
Debug.LogError("Failed to load EditorPrefsWindow.uxml template");
return;
}
// Load item template
itemTemplate = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(
$"{basePath}/Editor/Windows/EditorPrefs/EditorPrefItem.uxml"
);
if (itemTemplate == null)
{
Debug.LogError("Failed to load EditorPrefItem.uxml template");
return;
}
visualTree.CloneTree(rootVisualElement);
// Get references
scrollView = rootVisualElement.Q<ScrollView>("scroll-view");
prefsContainer = rootVisualElement.Q<VisualElement>("prefs-container");
// Load known MCP keys
LoadKnownMcpKeys();
// Load initial data
RefreshPrefs();
}
private void LoadKnownMcpKeys()
{
knownMcpKeys.Clear();
var fields = typeof(EditorPrefKeys).GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
foreach (var field in fields)
{
if (field.IsLiteral && !field.IsInitOnly)
{
knownMcpKeys.Add(field.GetValue(null).ToString());
}
}
}
private void RefreshPrefs()
{
currentPrefs.Clear();
prefsContainer.Clear();
// Get all EditorPrefs keys
var allKeys = new List<string>();
// Always show all MCP keys
allKeys.AddRange(knownMcpKeys);
// Try to find additional MCP keys
var mcpKeys = GetAllMcpKeys();
foreach (var key in mcpKeys)
{
if (!allKeys.Contains(key))
{
allKeys.Add(key);
}
}
// Sort keys
allKeys.Sort();
// Create items for existing prefs
foreach (var key in allKeys)
{
// Skip Customer UUID but show everything else that's defined
if (key != EditorPrefKeys.CustomerUuid)
{
var item = CreateEditorPrefItem(key);
if (item != null)
{
currentPrefs.Add(item);
prefsContainer.Add(CreateItemUI(item));
}
}
}
}
private List<string> GetAllMcpKeys()
{
// This is a simplified approach - in reality, getting all EditorPrefs is platform-specific
// For now, we'll return known MCP keys that might exist
var keys = new List<string>();
// Add some common MCP keys that might not be in EditorPrefKeys
keys.Add("MCPForUnity.TestKey");
// Filter to only those that actually exist
return keys.Where(EditorPrefs.HasKey).ToList();
}
private EditorPrefItem CreateEditorPrefItem(string key)
{
var item = new EditorPrefItem { Key = key, IsKnown = knownMcpKeys.Contains(key) };
// Check if we know the type of this pref
if (knownPrefTypes.TryGetValue(key, out var knownType))
{
// Use the known type
switch (knownType)
{
case EditorPrefType.Bool:
item.Type = EditorPrefType.Bool;
item.Value = EditorPrefs.GetBool(key, false).ToString();
break;
case EditorPrefType.Int:
item.Type = EditorPrefType.Int;
item.Value = EditorPrefs.GetInt(key, 0).ToString();
break;
case EditorPrefType.Float:
item.Type = EditorPrefType.Float;
item.Value = EditorPrefs.GetFloat(key, 0f).ToString();
break;
case EditorPrefType.String:
item.Type = EditorPrefType.String;
item.Value = EditorPrefs.GetString(key, "");
break;
}
}
else
{
// Only try to detect type for unknown keys that actually exist
if (!EditorPrefs.HasKey(key))
{
// Key doesn't exist and we don't know its type, skip it
return null;
}
// Unknown pref - try to detect type
var stringValue = EditorPrefs.GetString(key, "");
if (int.TryParse(stringValue, out var intValue))
{
item.Type = EditorPrefType.Int;
item.Value = intValue.ToString();
}
else if (float.TryParse(stringValue, out var floatValue))
{
item.Type = EditorPrefType.Float;
item.Value = floatValue.ToString();
}
else if (bool.TryParse(stringValue, out var boolValue))
{
item.Type = EditorPrefType.Bool;
item.Value = boolValue.ToString();
}
else
{
item.Type = EditorPrefType.String;
item.Value = stringValue;
}
}
return item;
}
private VisualElement CreateItemUI(EditorPrefItem item)
{
if (itemTemplate == null)
{
Debug.LogError("Item template not loaded");
return new VisualElement();
}
var itemElement = itemTemplate.CloneTree();
// Set values
itemElement.Q<Label>("key-label").text = item.Key;
var valueField = itemElement.Q<TextField>("value-field");
valueField.value = item.Value;
var typeDropdown = itemElement.Q<DropdownField>("type-dropdown");
typeDropdown.index = (int)item.Type;
// Buttons
var saveButton = itemElement.Q<Button>("save-button");
// Callbacks
saveButton.clicked += () => SavePref(item, valueField.value, (EditorPrefType)typeDropdown.index);
return itemElement;
}
private void SavePref(EditorPrefItem item, string newValue, EditorPrefType newType)
{
SaveValue(item.Key, newValue, newType);
RefreshPrefs();
}
private void SaveValue(string key, string value, EditorPrefType type)
{
switch (type)
{
case EditorPrefType.String:
EditorPrefs.SetString(key, value);
break;
case EditorPrefType.Int:
if (int.TryParse(value, out var intValue))
{
EditorPrefs.SetInt(key, intValue);
}
else
{
EditorUtility.DisplayDialog("Error", $"Cannot convert '{value}' to int", "OK");
return;
}
break;
case EditorPrefType.Float:
if (float.TryParse(value, out var floatValue))
{
EditorPrefs.SetFloat(key, floatValue);
}
else
{
EditorUtility.DisplayDialog("Error", $"Cannot convert '{value}' to float", "OK");
return;
}
break;
case EditorPrefType.Bool:
if (bool.TryParse(value, out var boolValue))
{
EditorPrefs.SetBool(key, boolValue);
}
else
{
EditorUtility.DisplayDialog("Error", $"Cannot convert '{value}' to bool (use 'True' or 'False')", "OK");
return;
}
break;
}
}
}
/// <summary>
/// Represents an EditorPrefs item
/// </summary>
public class EditorPrefItem
{
public string Key { get; set; }
public string Value { get; set; }
public EditorPrefType Type { get; set; }
public bool IsKnown { get; set; }
}
/// <summary>
/// EditorPrefs value types
/// </summary>
public enum EditorPrefType
{
String,
Int,
Float,
Bool
}
}

View File

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

View File

@ -0,0 +1,228 @@
.header {
padding-bottom: 16px;
border-bottom-width: 1px;
border-bottom-color: #333333;
margin-bottom: 16px;
}
.title {
-unity-font-style: bold;
font-size: 18px;
margin-bottom: 4px;
}
.description {
color: #999999;
font-size: 12px;
white-space: normal;
margin-left: 4px;
}
.controls {
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding: 8px;
background-color: #393939;
border-radius: 4px;
}
.add-section {
margin-bottom: 16px;
}
.primary-button {
-unity-text-align: middle-left;
background-color: #4a90e2;
color: white;
border-radius: 4px;
padding: 8px 16px;
}
.primary-button:hover {
background-color: #357abd;
}
.secondary-button {
background-color: #393939;
border-left-width: 1px;
border-right-width: 1px;
border-top-width: 1px;
border-bottom-width: 1px;
border-left-color: #555555;
border-right-color: #555555;
border-top-color: #555555;
border-bottom-color: #555555;
border-radius: 4px;
padding: 6px 12px;
width: 80px;
}
.secondary-button:hover {
background-color: #484848;
}
/* Add New Row */
.add-new-row {
background-color: #393939;
border-left-width: 1px;
border-right-width: 1px;
border-top-width: 1px;
border-bottom-width: 1px;
border-left-color: #555555;
border-right-color: #555555;
border-top-color: #555555;
border-bottom-color: #555555;
border-radius: 4px;
padding: 16px;
margin-bottom: 16px;
}
.add-row-content {
flex-direction: row;
align-items: flex-end;
}
.add-row-content .key-field {
flex: 1;
min-width: 200px;
margin-right: 12px;
}
.add-row-content .value-field {
flex: 1;
min-width: 150px;
margin-right: 12px;
}
.add-row-content .type-dropdown {
width: 100px;
margin-right: 12px;
}
.add-buttons {
flex-direction: row;
justify-content: flex-end;
margin-top: 8px;
}
.add-buttons .cancel-button {
margin-left: 8px;
}
.save-button {
background-color: #4caf50;
color: white;
min-width: 60px;
border-radius: 4px;
}
.save-button:hover {
background-color: #45a049;
}
.cancel-button {
background-color: #393939;
border-left-width: 1px;
border-right-width: 1px;
border-top-width: 1px;
border-bottom-width: 1px;
border-left-color: #555555;
border-right-color: #555555;
border-top-color: #555555;
border-bottom-color: #555555;
min-width: 60px;
border-radius: 4px;
}
.cancel-button:hover {
background-color: #484848;
}
/* Pref Items */
.prefs-container {
flex-direction: column;
}
.pref-item {
margin-bottom: 8px;
background-color: #393939;
border-left-width: 1px;
border-right-width: 1px;
border-top-width: 1px;
border-bottom-width: 1px;
border-left-color: #555555;
border-right-color: #555555;
border-top-color: #555555;
border-bottom-color: #555555;
border-radius: 4px;
padding: 8px;
}
.pref-row {
flex-direction: row;
align-items: center;
flex-wrap: nowrap; /* Prevent wrapping */
}
.pref-row .key-section {
flex-shrink: 0;
width: 200px; /* Fixed width for key section */
margin-right: 12px;
}
.pref-row .value-field {
flex: 1;
min-width: 150px;
margin-right: 12px;
}
.pref-row .type-dropdown {
width: 100px;
margin-right: 12px;
}
.pref-row .action-buttons {
flex-direction: row;
width: 32px;
justify-content: flex-start;
}
.key-section {
flex-direction: column;
min-width: 200px;
}
.key-label {
-unity-font-style: bold;
color: white;
white-space: normal;
}
.value-field {
flex: 1;
min-width: 150px;
}
.type-dropdown {
width: 100px;
}
.action-buttons {
flex-direction: row;
}
.action-buttons .save-button {
background-color: #4caf50;
color: white;
min-width: 32px;
height: 28px;
padding: 0;
font-size: 16px;
-unity-font-style: bold;
}
.action-buttons .save-button:hover {
background-color: #45a049;
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3980375ed546e47abafcafe11e953e87
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
disableValidation: 0

View File

@ -0,0 +1,30 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="True">
<Style src="../Components/Common.uss" />
<Style src="EditorPrefsWindow.uss" />
<ui:ScrollView name="scroll-view" mode="Vertical">
<!-- Header -->
<ui:VisualElement class="header">
<ui:Label text="EditorPrefs Manager" class="title" />
<ui:Label text="Manage MCP for Unity EditorPrefs. Useful for development and testing." class="description" />
</ui:VisualElement>
<!-- Add New Row (initially hidden) -->
<ui:VisualElement name="add-new-row" class="add-new-row" style="display: none;">
<ui:VisualElement class="add-row-content">
<ui:TextField name="new-key-field" label="Key" class="key-field" />
<ui:TextField name="new-value-field" label="Value" class="value-field" />
<ui:DropdownField name="new-type-dropdown" label="Type" class="type-dropdown" choices="String,Int,Float,Bool" index="0" />
<ui:VisualElement class="add-buttons">
<ui:Button name="create-button" text="Create" class="save-button" />
<ui:Button name="cancel-button" text="Cancel" class="cancel-button" />
</ui:VisualElement>
</ui:VisualElement>
</ui:VisualElement>
<!-- Prefs List -->
<ui:VisualElement name="prefs-container" class="prefs-container">
<!-- Items will be added here programmatically -->
</ui:VisualElement>
</ui:ScrollView>
</ui:UXML>

View File

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