Skip to main content
Glama

Unity MCP Integration

MCPDebugWindow.cs21.7 kB
using UnityEditor; using UnityEditor.UIElements; using UnityEngine; using UnityEngine.UIElements; using System; using System.Collections.Generic; using System.IO; using Newtonsoft.Json; namespace Plugins.GamePilot.Editor.MCP { [Serializable] public class MCPDebugSettings { public int port = 5010; public bool autoReconnect = false; public bool globalLoggingEnabled = false; public Dictionary<string, bool> componentLoggingEnabled = new Dictionary<string, bool>(); } public class MCPDebugWindow : EditorWindow { [SerializeField] private VisualTreeAsset uxml; [SerializeField] private StyleSheet uss; [SerializeField] private VisualTreeAsset m_VisualTreeAsset = default; private Label connectionStatusLabel; private Button connectButton; private Button disconnectButton; private Toggle autoReconnectToggle; private TextField serverPortField; // Component logging toggles private Dictionary<string, Toggle> logToggles = new Dictionary<string, Toggle>(); // Connection info labels private Label lastErrorLabel; private Label connectionTimeLabel; // Statistics elements private Label messagesSentLabel; private Label messagesReceivedLabel; private Label reconnectAttemptsLabel; // Statistics counters private int messagesSent = 0; private int messagesReceived = 0; private int reconnectAttempts = 0; private DateTime? connectionStartTime = null; // Settings private MCPDebugSettings settings; private string settingsPath; [MenuItem("Window/MCP Debug")] public static void ShowWindow() { MCPDebugWindow wnd = GetWindow<MCPDebugWindow>(); wnd.titleContent = new GUIContent("MCP Debug"); wnd.minSize = new Vector2(400, 500); } private void OnEnable() { // Get the path to save settings settingsPath = GetSettingsPath(); // Load or create settings LoadSettings(); } private string GetSettingsPath() { // Get the script location var script = MonoScript.FromScriptableObject(this); var scriptPath = AssetDatabase.GetAssetPath(script); var directoryPath = Path.GetDirectoryName(scriptPath); // Create settings path in the same directory return Path.Combine(directoryPath, "MCPDebugSettings.json"); } private void LoadSettings() { settings = new MCPDebugSettings(); try { // Check if settings file exists if (File.Exists(settingsPath)) { string json = File.ReadAllText(settingsPath); settings = JsonConvert.DeserializeObject<MCPDebugSettings>(json); Debug.Log($"[MCP] [MCPDebugWindow] Loaded settings from {settingsPath}"); } else { // Create default settings settings = new MCPDebugSettings(); SaveSettings(); Debug.Log($"[MCP] [MCPDebugWindow] Created default settings at {settingsPath}"); } } catch (Exception ex) { Debug.LogError($"[MCP] [MCPDebugWindow] Error loading settings: {ex.Message}"); settings = new MCPDebugSettings(); } // Apply settings to MCPLogger MCPLogger.GlobalLoggingEnabled = settings.globalLoggingEnabled; // Apply component logging settings foreach (var pair in settings.componentLoggingEnabled) { MCPLogger.SetComponentLoggingEnabled(pair.Key, pair.Value); } } private void SaveSettings() { try { // Save settings using Newtonsoft.Json which supports dictionaries directly string json = JsonConvert.SerializeObject(settings, Formatting.Indented); File.WriteAllText(settingsPath, json); Debug.Log($"[MCP] [MCPDebugWindow] Saved settings to {settingsPath}"); } catch (Exception ex) { Debug.LogError($"[MCP] [MCPDebugWindow] Error saving settings: {ex.Message}"); } } public void CreateGUI() { VisualElement root = rootVisualElement; if (uxml != null) { uxml.CloneTree(root); } else { Debug.LogError("VisualTreeAsset not found. Please check the path."); } if (uss != null) { root.styleSheets.Add(uss); } else { Debug.LogError("StyleSheet not found. Please check the path."); } // Get UI elements connectionStatusLabel = root.Q<Label>("connection-status"); connectButton = root.Q<Button>("connect-button"); disconnectButton = root.Q<Button>("disconnect-button"); autoReconnectToggle = root.Q<Toggle>("auto-reconnect-toggle"); serverPortField = root.Q<TextField>("server-port-field"); lastErrorLabel = root.Q<Label>("last-error-value"); connectionTimeLabel = root.Q<Label>("connection-time-value"); messagesSentLabel = root.Q<Label>("messages-sent-value"); messagesReceivedLabel = root.Q<Label>("messages-received-value"); reconnectAttemptsLabel = root.Q<Label>("reconnect-attempts-value"); // Apply settings to UI serverPortField.value = settings.port.ToString(); autoReconnectToggle.value = settings.autoReconnect; // Setup UI events connectButton.clicked += OnConnectClicked; disconnectButton.clicked += OnDisconnectClicked; autoReconnectToggle.RegisterValueChangedCallback(OnAutoReconnectChanged); serverPortField.RegisterValueChangedCallback(OnPortChanged); // Setup component logging toggles SetupComponentLoggingToggles(root); // Initialize UI with current state UpdateUIFromState(); // Register for updates EditorApplication.update += OnEditorUpdate; } private void OnPortChanged(ChangeEvent<string> evt) { if (int.TryParse(evt.newValue, out int port) && port >= 1 && port <= 65535) { settings.port = port; SaveSettings(); } } private void CreateFallbackUI(VisualElement root) { // Create a simple fallback UI if UXML fails to load root.Add(new Label("MCP Debug Window - UXML not found") { style = { fontSize = 16, marginBottom = 10 } }); // Removed serverUrlField - only using port field as requested serverPortField = new TextField("Port (Default: 5010)") { value = "5010" }; root.Add(serverPortField); var connectButton = new Button(OnConnectClicked) { text = "Connect" }; root.Add(connectButton); var disconnectButton = new Button(OnDisconnectClicked) { text = "Disconnect" }; root.Add(disconnectButton); var autoReconnectToggle = new Toggle("Auto Reconnect"); autoReconnectToggle.RegisterValueChangedCallback(OnAutoReconnectChanged); root.Add(autoReconnectToggle); connectionStatusLabel = new Label("Status: Not Connected"); root.Add(connectionStatusLabel); } private void SetupComponentLoggingToggles(VisualElement root) { var loggingContainer = root.Q<VisualElement>("logging-container"); // Register MCPDebugWindow as a component for logging MCPLogger.InitializeComponent("MCPDebugWindow", settings.componentLoggingEnabled.ContainsKey("MCPDebugWindow") ? settings.componentLoggingEnabled["MCPDebugWindow"] : false); // Global logging toggle var globalToggle = new Toggle("Enable All Logging"); globalToggle.value = settings.globalLoggingEnabled; globalToggle.RegisterValueChangedCallback(evt => { settings.globalLoggingEnabled = evt.newValue; MCPLogger.GlobalLoggingEnabled = evt.newValue; SaveSettings(); // First make sure all components are properly initialized before updating UI EnsureComponentsInitialized(); // Update all component toggles to show they're enabled/disabled foreach (var componentName in MCPLogger.GetRegisteredComponents()) { if (logToggles.TryGetValue(componentName, out var toggle)) { // Don't disable the toggle UI, just update its interactable state toggle.SetEnabled(true); } } }); loggingContainer.Add(globalToggle); // Add a separator var separator = new VisualElement(); separator.style.height = 1; separator.style.marginTop = 5; separator.style.marginBottom = 5; separator.style.backgroundColor = new Color(0.3f, 0.3f, 0.3f); loggingContainer.Add(separator); // Ensure all components are initialized EnsureComponentsInitialized(); // Create toggles for standard components string[] standardComponents = { "MCPManager", "MCPConnectionManager", "MCPDataCollector", "MCPMessageHandler", "MCPCodeExecutor", "MCPMessageSender", "MCPDebugWindow" // Add the debug window itself }; foreach (string componentName in standardComponents) { bool isEnabled = settings.componentLoggingEnabled.ContainsKey(componentName) ? settings.componentLoggingEnabled[componentName] : false; CreateLoggingToggle(loggingContainer, componentName, $"Enable {componentName} logging", isEnabled); } // Add any additional registered components not in our standard list foreach (var componentName in MCPLogger.GetRegisteredComponents()) { if (!logToggles.ContainsKey(componentName)) { bool isEnabled = settings.componentLoggingEnabled.ContainsKey(componentName) ? settings.componentLoggingEnabled[componentName] : false; CreateLoggingToggle(loggingContainer, componentName, $"Enable {componentName} logging", isEnabled); } } } // Make sure all components are initialized in the logger private void EnsureComponentsInitialized() { string[] standardComponents = { "MCPManager", "MCPConnectionManager", "MCPDataCollector", "MCPMessageHandler", "MCPCodeExecutor", "MCPMessageSender", "MCPDebugWindow" }; foreach (string componentName in standardComponents) { MCPLogger.InitializeComponent(componentName, false); } } private void CreateLoggingToggle(VisualElement container, string componentName, string label, bool initialValue) { var toggle = new Toggle(label); toggle.value = initialValue; // Make all toggles interactive, they'll work based on global enabled state toggle.SetEnabled(true); toggle.RegisterValueChangedCallback(evt => OnLoggingToggleChanged(componentName, evt.newValue)); container.Add(toggle); logToggles[componentName] = toggle; } private void OnLoggingToggleChanged(string componentName, bool enabled) { MCPLogger.SetComponentLoggingEnabled(componentName, enabled); settings.componentLoggingEnabled[componentName] = enabled; SaveSettings(); } private void OnConnectClicked() { // Always use localhost for the WebSocket URL string serverUrl = "ws://localhost"; // Get the server port from the text field string portText = serverPortField.value; // If port is empty, default to 5010 if (string.IsNullOrWhiteSpace(portText)) { portText = "5010"; serverPortField.value = portText; } // Validate port format if (!int.TryParse(portText, out int port) || port < 1 || port > 65535) { EditorUtility.DisplayDialog("Invalid Port", "Please enter a valid port number between 1 and 65535.", "OK"); return; } // Save the port setting settings.port = port; SaveSettings(); try { // Create the WebSocket URL with the specified port Uri uri = new Uri($"{serverUrl}:{port}"); // If we have access to the ConnectionManager, try to update its server URI var connectionManager = GetConnectionManager(); if (connectionManager != null) { // Use reflection to set the serverUri field if it exists var serverUriField = typeof(MCPConnectionManager).GetField("serverUri", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); if (serverUriField != null) { serverUriField.SetValue(connectionManager, uri); } } // Initiate manual connection if (MCPManager.IsInitialized) { MCPManager.RetryConnection(); connectionStartTime = DateTime.Now; UpdateUIFromState(); } else { MCPManager.Initialize(); connectionStartTime = DateTime.Now; UpdateUIFromState(); } } catch (UriFormatException) { EditorUtility.DisplayDialog("Invalid URL", "The URL format is invalid.", "OK"); } catch (Exception ex) { EditorUtility.DisplayDialog("Connection Error", $"Error connecting to server: {ex.Message}", "OK"); } } private void OnDisconnectClicked() { if (MCPManager.IsInitialized) { MCPManager.Shutdown(); connectionStartTime = null; UpdateUIFromState(); } } private void OnAutoReconnectChanged(ChangeEvent<bool> evt) { settings.autoReconnect = evt.newValue; SaveSettings(); if (MCPManager.IsInitialized) { MCPManager.EnableAutoReconnect(evt.newValue); } } private void OnEditorUpdate() { // Update connection status and statistics UpdateUIFromState(); } private void UpdateUIFromState() { bool isInitialized = MCPManager.IsInitialized; bool isConnected = MCPManager.IsConnected; // Only log status if logging is enabled if (MCPLogger.IsLoggingEnabled("MCPDebugWindow")) { Debug.Log($"[MCP] [MCPDebugWindow] Status check: IsInitialized={isInitialized}, IsConnected={isConnected}"); } // Update status label if (!isInitialized) { connectionStatusLabel.text = "Not Initialized"; connectionStatusLabel.RemoveFromClassList("status-connected"); connectionStatusLabel.RemoveFromClassList("status-connecting"); connectionStatusLabel.AddToClassList("status-disconnected"); } else if (isConnected) { connectionStatusLabel.text = "Connected"; connectionStatusLabel.RemoveFromClassList("status-disconnected"); connectionStatusLabel.RemoveFromClassList("status-connecting"); connectionStatusLabel.AddToClassList("status-connected"); // If we're in the connected state, make sure connectionStartTime is set // This ensures the timer works properly if (!connectionStartTime.HasValue) { connectionStartTime = DateTime.Now; } } else { connectionStatusLabel.text = "Disconnected"; connectionStatusLabel.RemoveFromClassList("status-connected"); connectionStatusLabel.RemoveFromClassList("status-connecting"); connectionStatusLabel.AddToClassList("status-disconnected"); // Reset connection time when disconnected connectionStartTime = null; } // Update button states connectButton.SetEnabled(!isConnected); disconnectButton.SetEnabled(isInitialized); serverPortField.SetEnabled(!isConnected); // Only allow port changes when disconnected // Update connection time if connected if (connectionStartTime.HasValue && isConnected) { TimeSpan duration = DateTime.Now - connectionStartTime.Value; connectionTimeLabel.text = $"{duration.Hours:00}:{duration.Minutes:00}:{duration.Seconds:00}"; } else { connectionTimeLabel.text = "00:00:00"; } // Update statistics if available if (isInitialized) { // Get connection statistics var connectionManager = GetConnectionManager(); if (connectionManager != null) { messagesSentLabel.text = connectionManager.MessagesSent.ToString(); messagesReceivedLabel.text = connectionManager.MessagesReceived.ToString(); reconnectAttemptsLabel.text = connectionManager.ReconnectAttempts.ToString(); lastErrorLabel.text = !string.IsNullOrEmpty(connectionManager.LastErrorMessage) ? connectionManager.LastErrorMessage : "None"; } else { messagesSentLabel.text = "0"; messagesReceivedLabel.text = "0"; reconnectAttemptsLabel.text = "0"; lastErrorLabel.text = "None"; } } } // Helper to access connection manager through reflection if needed private MCPConnectionManager GetConnectionManager() { if (!MCPManager.IsInitialized) return null; // Try to access the connection manager using reflection try { var managerType = typeof(MCPManager); var field = managerType.GetField("connectionManager", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); if (field != null) { return field.GetValue(null) as MCPConnectionManager; } } catch (Exception ex) { Debug.LogError($"Error accessing connection manager: {ex.Message}"); } return null; } private void OnDisable() { // Unregister from editor updates EditorApplication.update -= OnEditorUpdate; // Save settings one last time when window is closed SaveSettings(); } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/quazaai/UnityMCPIntegration'

If you have feedback or need assistance with the MCP directory API, please join our Discord server