This is page 2 of 2. Use http://codebase.md/quazaai/unitymcpintegration?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .gitignore ├── CHANGELOG.md ├── CHANGELOG.md.meta ├── CODE_OF_CONDUCT.md ├── CODE_OF_CONDUCT.md.meta ├── Editor │ ├── GamePilot.UnityMCP.asmdef │ ├── GamePilot.UnityMCP.asmdef.meta │ ├── MCPCodeExecutor.cs │ ├── MCPCodeExecutor.cs.meta │ ├── MCPConnectionManager.cs │ ├── MCPConnectionManager.cs.meta │ ├── MCPDataCollector.cs │ ├── MCPDataCollector.cs.meta │ ├── MCPLogger.cs │ ├── MCPLogger.cs.meta │ ├── MCPManager.cs │ ├── MCPManager.cs.meta │ ├── MCPMessageHandler.cs │ ├── MCPMessageHandler.cs.meta │ ├── MCPMessageSender.cs │ ├── MCPMessageSender.cs.meta │ ├── Models │ │ ├── MCPEditorState.cs │ │ ├── MCPEditorState.cs.meta │ │ ├── MCPSceneInfo.cs │ │ └── MCPSceneInfo.cs.meta │ ├── Models.meta │ ├── UI │ │ ├── MCPDebugSettings.json │ │ ├── MCPDebugSettings.json.meta │ │ ├── MCPDebugWindow.cs │ │ ├── MCPDebugWindow.cs.meta │ │ ├── MCPDebugWindow.uss │ │ ├── MCPDebugWindow.uss.meta │ │ ├── MCPDebugWindow.uxml │ │ └── MCPDebugWindow.uxml.meta │ └── UI.meta ├── Editor.meta ├── LICENSE ├── LICENSE.meta ├── mcpInspector.png ├── mcpInspector.png.meta ├── mcpServer │ ├── .dockerignore │ ├── .env.example │ ├── build │ │ ├── filesystemTools.js │ │ ├── filesystemTools.js.meta │ │ ├── index.js │ │ ├── index.js.meta │ │ ├── toolDefinitions.js │ │ ├── toolDefinitions.js.meta │ │ ├── types.js │ │ ├── types.js.meta │ │ ├── websocketHandler.js │ │ └── websocketHandler.js.meta │ ├── build.meta │ ├── docker-compose.yml │ ├── docker-compose.yml.meta │ ├── Dockerfile │ ├── Dockerfile.meta │ ├── MCPSummary.md │ ├── MCPSummary.md.meta │ ├── node_modules.meta │ ├── package-lock.json │ ├── package-lock.json.meta │ ├── package.json │ ├── package.json.meta │ ├── smithery.yaml │ ├── src │ │ ├── filesystemTools.ts │ │ ├── filesystemTools.ts.meta │ │ ├── index.ts │ │ ├── index.ts.meta │ │ ├── toolDefinitions.ts │ │ ├── toolDefinitions.ts.meta │ │ ├── types.ts │ │ ├── types.ts.meta │ │ ├── websocketHandler.ts │ │ └── websocketHandler.ts.meta │ ├── src.meta │ ├── tsconfig.json │ └── tsconfig.json.meta ├── mcpServer.meta ├── package.json ├── package.json.meta ├── README.md └── README.md.meta ``` # Files -------------------------------------------------------------------------------- /Editor/UI/MCPDebugWindow.cs: -------------------------------------------------------------------------------- ```csharp 1 | using UnityEditor; 2 | using UnityEditor.UIElements; 3 | using UnityEngine; 4 | using UnityEngine.UIElements; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using Newtonsoft.Json; 9 | 10 | namespace Plugins.GamePilot.Editor.MCP 11 | { 12 | [Serializable] 13 | public class MCPDebugSettings 14 | { 15 | public int port = 5010; 16 | public bool autoReconnect = false; 17 | public bool globalLoggingEnabled = false; 18 | public Dictionary<string, bool> componentLoggingEnabled = new Dictionary<string, bool>(); 19 | } 20 | 21 | public class MCPDebugWindow : EditorWindow 22 | { 23 | [SerializeField] 24 | private VisualTreeAsset uxml; 25 | [SerializeField] 26 | private StyleSheet uss; 27 | 28 | [SerializeField] 29 | private VisualTreeAsset m_VisualTreeAsset = default; 30 | 31 | private Label connectionStatusLabel; 32 | private Button connectButton; 33 | private Button disconnectButton; 34 | private Toggle autoReconnectToggle; 35 | private TextField serverPortField; 36 | 37 | // Component logging toggles 38 | private Dictionary<string, Toggle> logToggles = new Dictionary<string, Toggle>(); 39 | 40 | // Connection info labels 41 | private Label lastErrorLabel; 42 | private Label connectionTimeLabel; 43 | 44 | // Statistics elements 45 | private Label messagesSentLabel; 46 | private Label messagesReceivedLabel; 47 | private Label reconnectAttemptsLabel; 48 | 49 | // Statistics counters 50 | private int messagesSent = 0; 51 | private int messagesReceived = 0; 52 | private int reconnectAttempts = 0; 53 | private DateTime? connectionStartTime = null; 54 | 55 | // Settings 56 | private MCPDebugSettings settings; 57 | private string settingsPath; 58 | 59 | [MenuItem("Window/MCP Debug")] 60 | public static void ShowWindow() 61 | { 62 | MCPDebugWindow wnd = GetWindow<MCPDebugWindow>(); 63 | wnd.titleContent = new GUIContent("MCP Debug"); 64 | wnd.minSize = new Vector2(400, 500); 65 | } 66 | 67 | private void OnEnable() 68 | { 69 | // Get the path to save settings 70 | settingsPath = GetSettingsPath(); 71 | 72 | // Load or create settings 73 | LoadSettings(); 74 | } 75 | 76 | private string GetSettingsPath() 77 | { 78 | // Get the script location 79 | var script = MonoScript.FromScriptableObject(this); 80 | var scriptPath = AssetDatabase.GetAssetPath(script); 81 | var directoryPath = Path.GetDirectoryName(scriptPath); 82 | 83 | // Create settings path in the same directory 84 | return Path.Combine(directoryPath, "MCPDebugSettings.json"); 85 | } 86 | 87 | private void LoadSettings() 88 | { 89 | settings = new MCPDebugSettings(); 90 | 91 | try 92 | { 93 | // Check if settings file exists 94 | if (File.Exists(settingsPath)) 95 | { 96 | string json = File.ReadAllText(settingsPath); 97 | settings = JsonConvert.DeserializeObject<MCPDebugSettings>(json); 98 | 99 | Debug.Log($"[MCP] [MCPDebugWindow] Loaded settings from {settingsPath}"); 100 | } 101 | else 102 | { 103 | // Create default settings 104 | settings = new MCPDebugSettings(); 105 | SaveSettings(); 106 | Debug.Log($"[MCP] [MCPDebugWindow] Created default settings at {settingsPath}"); 107 | } 108 | } 109 | catch (Exception ex) 110 | { 111 | Debug.LogError($"[MCP] [MCPDebugWindow] Error loading settings: {ex.Message}"); 112 | settings = new MCPDebugSettings(); 113 | } 114 | 115 | // Apply settings to MCPLogger 116 | MCPLogger.GlobalLoggingEnabled = settings.globalLoggingEnabled; 117 | 118 | // Apply component logging settings 119 | foreach (var pair in settings.componentLoggingEnabled) 120 | { 121 | MCPLogger.SetComponentLoggingEnabled(pair.Key, pair.Value); 122 | } 123 | } 124 | 125 | private void SaveSettings() 126 | { 127 | try 128 | { 129 | // Save settings using Newtonsoft.Json which supports dictionaries directly 130 | string json = JsonConvert.SerializeObject(settings, Formatting.Indented); 131 | File.WriteAllText(settingsPath, json); 132 | 133 | Debug.Log($"[MCP] [MCPDebugWindow] Saved settings to {settingsPath}"); 134 | } 135 | catch (Exception ex) 136 | { 137 | Debug.LogError($"[MCP] [MCPDebugWindow] Error saving settings: {ex.Message}"); 138 | } 139 | } 140 | 141 | public void CreateGUI() 142 | { 143 | VisualElement root = rootVisualElement; 144 | 145 | if (uxml != null) 146 | { 147 | uxml.CloneTree(root); 148 | } 149 | else 150 | { 151 | Debug.LogError("VisualTreeAsset not found. Please check the path."); 152 | } 153 | 154 | if (uss != null) 155 | { 156 | root.styleSheets.Add(uss); 157 | } else 158 | { 159 | Debug.LogError("StyleSheet not found. Please check the path."); 160 | } 161 | 162 | // Get UI elements 163 | connectionStatusLabel = root.Q<Label>("connection-status"); 164 | connectButton = root.Q<Button>("connect-button"); 165 | disconnectButton = root.Q<Button>("disconnect-button"); 166 | autoReconnectToggle = root.Q<Toggle>("auto-reconnect-toggle"); 167 | serverPortField = root.Q<TextField>("server-port-field"); 168 | 169 | lastErrorLabel = root.Q<Label>("last-error-value"); 170 | connectionTimeLabel = root.Q<Label>("connection-time-value"); 171 | 172 | messagesSentLabel = root.Q<Label>("messages-sent-value"); 173 | messagesReceivedLabel = root.Q<Label>("messages-received-value"); 174 | reconnectAttemptsLabel = root.Q<Label>("reconnect-attempts-value"); 175 | 176 | // Apply settings to UI 177 | serverPortField.value = settings.port.ToString(); 178 | autoReconnectToggle.value = settings.autoReconnect; 179 | 180 | // Setup UI events 181 | connectButton.clicked += OnConnectClicked; 182 | disconnectButton.clicked += OnDisconnectClicked; 183 | autoReconnectToggle.RegisterValueChangedCallback(OnAutoReconnectChanged); 184 | serverPortField.RegisterValueChangedCallback(OnPortChanged); 185 | 186 | // Setup component logging toggles 187 | SetupComponentLoggingToggles(root); 188 | 189 | // Initialize UI with current state 190 | UpdateUIFromState(); 191 | 192 | // Register for updates 193 | EditorApplication.update += OnEditorUpdate; 194 | } 195 | 196 | private void OnPortChanged(ChangeEvent<string> evt) 197 | { 198 | if (int.TryParse(evt.newValue, out int port) && port >= 1 && port <= 65535) 199 | { 200 | settings.port = port; 201 | SaveSettings(); 202 | } 203 | } 204 | 205 | private void CreateFallbackUI(VisualElement root) 206 | { 207 | // Create a simple fallback UI if UXML fails to load 208 | root.Add(new Label("MCP Debug Window - UXML not found") { style = { fontSize = 16, marginBottom = 10 } }); 209 | 210 | // Removed serverUrlField - only using port field as requested 211 | 212 | serverPortField = new TextField("Port (Default: 5010)") { value = "5010" }; 213 | root.Add(serverPortField); 214 | 215 | var connectButton = new Button(OnConnectClicked) { text = "Connect" }; 216 | root.Add(connectButton); 217 | 218 | var disconnectButton = new Button(OnDisconnectClicked) { text = "Disconnect" }; 219 | root.Add(disconnectButton); 220 | 221 | var autoReconnectToggle = new Toggle("Auto Reconnect"); 222 | autoReconnectToggle.RegisterValueChangedCallback(OnAutoReconnectChanged); 223 | root.Add(autoReconnectToggle); 224 | 225 | connectionStatusLabel = new Label("Status: Not Connected"); 226 | root.Add(connectionStatusLabel); 227 | } 228 | 229 | private void SetupComponentLoggingToggles(VisualElement root) 230 | { 231 | var loggingContainer = root.Q<VisualElement>("logging-container"); 232 | 233 | // Register MCPDebugWindow as a component for logging 234 | MCPLogger.InitializeComponent("MCPDebugWindow", settings.componentLoggingEnabled.ContainsKey("MCPDebugWindow") ? 235 | settings.componentLoggingEnabled["MCPDebugWindow"] : false); 236 | 237 | // Global logging toggle 238 | var globalToggle = new Toggle("Enable All Logging"); 239 | globalToggle.value = settings.globalLoggingEnabled; 240 | globalToggle.RegisterValueChangedCallback(evt => { 241 | settings.globalLoggingEnabled = evt.newValue; 242 | MCPLogger.GlobalLoggingEnabled = evt.newValue; 243 | SaveSettings(); 244 | 245 | // First make sure all components are properly initialized before updating UI 246 | EnsureComponentsInitialized(); 247 | 248 | // Update all component toggles to show they're enabled/disabled 249 | foreach (var componentName in MCPLogger.GetRegisteredComponents()) 250 | { 251 | if (logToggles.TryGetValue(componentName, out var toggle)) 252 | { 253 | // Don't disable the toggle UI, just update its interactable state 254 | toggle.SetEnabled(true); 255 | } 256 | } 257 | }); 258 | loggingContainer.Add(globalToggle); 259 | 260 | // Add a separator 261 | var separator = new VisualElement(); 262 | separator.style.height = 1; 263 | separator.style.marginTop = 5; 264 | separator.style.marginBottom = 5; 265 | separator.style.backgroundColor = new Color(0.3f, 0.3f, 0.3f); 266 | loggingContainer.Add(separator); 267 | 268 | // Ensure all components are initialized 269 | EnsureComponentsInitialized(); 270 | 271 | // Create toggles for standard components 272 | string[] standardComponents = { 273 | "MCPManager", 274 | "MCPConnectionManager", 275 | "MCPDataCollector", 276 | "MCPMessageHandler", 277 | "MCPCodeExecutor", 278 | "MCPMessageSender", 279 | "MCPDebugWindow" // Add the debug window itself 280 | }; 281 | 282 | foreach (string componentName in standardComponents) 283 | { 284 | bool isEnabled = settings.componentLoggingEnabled.ContainsKey(componentName) ? 285 | settings.componentLoggingEnabled[componentName] : false; 286 | 287 | CreateLoggingToggle(loggingContainer, componentName, $"Enable {componentName} logging", isEnabled); 288 | } 289 | 290 | // Add any additional registered components not in our standard list 291 | foreach (var componentName in MCPLogger.GetRegisteredComponents()) 292 | { 293 | if (!logToggles.ContainsKey(componentName)) 294 | { 295 | bool isEnabled = settings.componentLoggingEnabled.ContainsKey(componentName) ? 296 | settings.componentLoggingEnabled[componentName] : false; 297 | 298 | CreateLoggingToggle(loggingContainer, componentName, $"Enable {componentName} logging", isEnabled); 299 | } 300 | } 301 | } 302 | 303 | // Make sure all components are initialized in the logger 304 | private void EnsureComponentsInitialized() 305 | { 306 | string[] standardComponents = { 307 | "MCPManager", 308 | "MCPConnectionManager", 309 | "MCPDataCollector", 310 | "MCPMessageHandler", 311 | "MCPCodeExecutor", 312 | "MCPMessageSender", 313 | "MCPDebugWindow" 314 | }; 315 | 316 | foreach (string componentName in standardComponents) 317 | { 318 | MCPLogger.InitializeComponent(componentName, false); 319 | } 320 | } 321 | 322 | private void CreateLoggingToggle(VisualElement container, string componentName, string label, bool initialValue) 323 | { 324 | var toggle = new Toggle(label); 325 | toggle.value = initialValue; 326 | 327 | // Make all toggles interactive, they'll work based on global enabled state 328 | toggle.SetEnabled(true); 329 | 330 | toggle.RegisterValueChangedCallback(evt => OnLoggingToggleChanged(componentName, evt.newValue)); 331 | container.Add(toggle); 332 | logToggles[componentName] = toggle; 333 | } 334 | 335 | private void OnLoggingToggleChanged(string componentName, bool enabled) 336 | { 337 | MCPLogger.SetComponentLoggingEnabled(componentName, enabled); 338 | settings.componentLoggingEnabled[componentName] = enabled; 339 | SaveSettings(); 340 | } 341 | 342 | private void OnConnectClicked() 343 | { 344 | // Always use localhost for the WebSocket URL 345 | string serverUrl = "ws://localhost"; 346 | 347 | // Get the server port from the text field 348 | string portText = serverPortField.value; 349 | 350 | // If port is empty, default to 5010 351 | if (string.IsNullOrWhiteSpace(portText)) 352 | { 353 | portText = "5010"; 354 | serverPortField.value = portText; 355 | } 356 | 357 | // Validate port format 358 | if (!int.TryParse(portText, out int port) || port < 1 || port > 65535) 359 | { 360 | EditorUtility.DisplayDialog("Invalid Port", 361 | "Please enter a valid port number between 1 and 65535.", "OK"); 362 | return; 363 | } 364 | 365 | // Save the port setting 366 | settings.port = port; 367 | SaveSettings(); 368 | 369 | try { 370 | // Create the WebSocket URL with the specified port 371 | Uri uri = new Uri($"{serverUrl}:{port}"); 372 | 373 | // If we have access to the ConnectionManager, try to update its server URI 374 | var connectionManager = GetConnectionManager(); 375 | if (connectionManager != null) 376 | { 377 | // Use reflection to set the serverUri field if it exists 378 | var serverUriField = typeof(MCPConnectionManager).GetField("serverUri", 379 | System.Reflection.BindingFlags.NonPublic | 380 | System.Reflection.BindingFlags.Instance); 381 | 382 | if (serverUriField != null) 383 | { 384 | serverUriField.SetValue(connectionManager, uri); 385 | } 386 | } 387 | 388 | // Initiate manual connection 389 | if (MCPManager.IsInitialized) 390 | { 391 | MCPManager.RetryConnection(); 392 | connectionStartTime = DateTime.Now; 393 | UpdateUIFromState(); 394 | } 395 | else 396 | { 397 | MCPManager.Initialize(); 398 | connectionStartTime = DateTime.Now; 399 | UpdateUIFromState(); 400 | } 401 | } 402 | catch (UriFormatException) 403 | { 404 | EditorUtility.DisplayDialog("Invalid URL", 405 | "The URL format is invalid.", "OK"); 406 | } 407 | catch (Exception ex) 408 | { 409 | EditorUtility.DisplayDialog("Connection Error", 410 | $"Error connecting to server: {ex.Message}", "OK"); 411 | } 412 | } 413 | 414 | private void OnDisconnectClicked() 415 | { 416 | if (MCPManager.IsInitialized) 417 | { 418 | MCPManager.Shutdown(); 419 | connectionStartTime = null; 420 | UpdateUIFromState(); 421 | } 422 | } 423 | 424 | private void OnAutoReconnectChanged(ChangeEvent<bool> evt) 425 | { 426 | settings.autoReconnect = evt.newValue; 427 | SaveSettings(); 428 | 429 | if (MCPManager.IsInitialized) 430 | { 431 | MCPManager.EnableAutoReconnect(evt.newValue); 432 | } 433 | } 434 | 435 | private void OnEditorUpdate() 436 | { 437 | // Update connection status and statistics 438 | UpdateUIFromState(); 439 | } 440 | 441 | private void UpdateUIFromState() 442 | { 443 | bool isInitialized = MCPManager.IsInitialized; 444 | bool isConnected = MCPManager.IsConnected; 445 | 446 | // Only log status if logging is enabled 447 | if (MCPLogger.IsLoggingEnabled("MCPDebugWindow")) 448 | { 449 | Debug.Log($"[MCP] [MCPDebugWindow] Status check: IsInitialized={isInitialized}, IsConnected={isConnected}"); 450 | } 451 | 452 | // Update status label 453 | if (!isInitialized) 454 | { 455 | connectionStatusLabel.text = "Not Initialized"; 456 | connectionStatusLabel.RemoveFromClassList("status-connected"); 457 | connectionStatusLabel.RemoveFromClassList("status-connecting"); 458 | connectionStatusLabel.AddToClassList("status-disconnected"); 459 | } 460 | else if (isConnected) 461 | { 462 | connectionStatusLabel.text = "Connected"; 463 | connectionStatusLabel.RemoveFromClassList("status-disconnected"); 464 | connectionStatusLabel.RemoveFromClassList("status-connecting"); 465 | connectionStatusLabel.AddToClassList("status-connected"); 466 | 467 | // If we're in the connected state, make sure connectionStartTime is set 468 | // This ensures the timer works properly 469 | if (!connectionStartTime.HasValue) 470 | { 471 | connectionStartTime = DateTime.Now; 472 | } 473 | } 474 | else 475 | { 476 | connectionStatusLabel.text = "Disconnected"; 477 | connectionStatusLabel.RemoveFromClassList("status-connected"); 478 | connectionStatusLabel.RemoveFromClassList("status-connecting"); 479 | connectionStatusLabel.AddToClassList("status-disconnected"); 480 | 481 | // Reset connection time when disconnected 482 | connectionStartTime = null; 483 | } 484 | 485 | // Update button states 486 | connectButton.SetEnabled(!isConnected); 487 | disconnectButton.SetEnabled(isInitialized); 488 | serverPortField.SetEnabled(!isConnected); // Only allow port changes when disconnected 489 | 490 | // Update connection time if connected 491 | if (connectionStartTime.HasValue && isConnected) 492 | { 493 | TimeSpan duration = DateTime.Now - connectionStartTime.Value; 494 | connectionTimeLabel.text = $"{duration.Hours:00}:{duration.Minutes:00}:{duration.Seconds:00}"; 495 | } 496 | else 497 | { 498 | connectionTimeLabel.text = "00:00:00"; 499 | } 500 | 501 | // Update statistics if available 502 | if (isInitialized) 503 | { 504 | // Get connection statistics 505 | var connectionManager = GetConnectionManager(); 506 | if (connectionManager != null) 507 | { 508 | messagesSentLabel.text = connectionManager.MessagesSent.ToString(); 509 | messagesReceivedLabel.text = connectionManager.MessagesReceived.ToString(); 510 | reconnectAttemptsLabel.text = connectionManager.ReconnectAttempts.ToString(); 511 | lastErrorLabel.text = !string.IsNullOrEmpty(connectionManager.LastErrorMessage) 512 | ? connectionManager.LastErrorMessage : "None"; 513 | } 514 | else 515 | { 516 | messagesSentLabel.text = "0"; 517 | messagesReceivedLabel.text = "0"; 518 | reconnectAttemptsLabel.text = "0"; 519 | lastErrorLabel.text = "None"; 520 | } 521 | } 522 | } 523 | 524 | // Helper to access connection manager through reflection if needed 525 | private MCPConnectionManager GetConnectionManager() 526 | { 527 | if (!MCPManager.IsInitialized) 528 | return null; 529 | 530 | // Try to access the connection manager using reflection 531 | try 532 | { 533 | var managerType = typeof(MCPManager); 534 | var field = managerType.GetField("connectionManager", 535 | System.Reflection.BindingFlags.NonPublic | 536 | System.Reflection.BindingFlags.Static); 537 | 538 | if (field != null) 539 | { 540 | return field.GetValue(null) as MCPConnectionManager; 541 | } 542 | } 543 | catch (Exception ex) 544 | { 545 | Debug.LogError($"Error accessing connection manager: {ex.Message}"); 546 | } 547 | 548 | return null; 549 | } 550 | 551 | private void OnDisable() 552 | { 553 | // Unregister from editor updates 554 | EditorApplication.update -= OnEditorUpdate; 555 | 556 | // Save settings one last time when window is closed 557 | SaveSettings(); 558 | } 559 | } 560 | } 561 | ```