Skip to main content
Glama
Singtaa
by Singtaa
McpBridge.cs4.96 kB
using System; using UnityEditor; using UnityEngine; namespace UnityMcp { /// <summary> /// Main entry point and orchestrator for the Unity MCP Server. /// Handles lifecycle, Node.js server management, and TCP client connection. /// /// CRITICAL: Unity runs multiple processes that all execute [InitializeOnLoad] code: /// - Main Editor process (the one we want) /// - AssetImportWorker0, AssetImportWorker1, etc. (background import processes) /// /// The MCP bridge must ONLY run in the main editor process. /// </summary> [InitializeOnLoad] public static class McpBridge { static volatile bool _started; static readonly object _startLock = new object(); static McpTcpClient _client; static DateTime _startTime; public static bool IsStarted => _started; public static bool IsConnected => _client?.IsConnected ?? false; public static string ClientId => _client?.ClientId; public static int TotalCalls => _client?.TotalCalls ?? 0; public static TimeSpan Uptime => _started ? DateTime.UtcNow - _startTime : TimeSpan.Zero; static McpBridge() { // CRITICAL: Don't run in background processes like AssetImportWorker! if (IsBackgroundProcess()) { return; } // Start immediately - we're already on main thread during domain reload try { EnsureStarted(); } catch (Exception e) { Debug.LogException(e); } } public static async void EnsureStarted() { lock (_startLock) { if (_started && _client != null) return; _started = true; _startTime = DateTime.UtcNow; AppDomain.CurrentDomain.DomainUnload -= OnDomainUnload; AppDomain.CurrentDomain.DomainUnload += OnDomainUnload; AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeAssemblyReload; AssemblyReloadEvents.beforeAssemblyReload += OnBeforeAssemblyReload; MainThreadDispatcher.Install(); ConsoleCapture.EnsureStarted(); ToolRegistry.EnsureInitialized(); ResourceRegistry.EnsureInitialized(); // Clean up stale lock files McpTcpClient.CleanupStaleLockFiles(); try { _client?.Dispose(); } catch { } } // Start Node.js server if auto-start is enabled if (McpSettings.AutoStart) { var serverStarted = await NodeProcessManager.EnsureServerRunning(); if (!serverStarted) { Debug.LogWarning("[UnityMcp] Failed to start Node.js server. MCP bridge will wait for manual server start."); } } // Connect TCP client if auto-connect is enabled if (McpSettings.AutoConnect) { lock (_startLock) { _client = new McpTcpClient(McpSettings.Host, McpSettings.IpcPort); _client.Start(); } } } static void OnBeforeAssemblyReload() { Shutdown(keepServer: true); } static void OnDomainUnload(object sender, EventArgs e) { Shutdown(keepServer: true); } public static void Shutdown(bool keepServer = false) { if (!_started) return; _started = false; AppDomain.CurrentDomain.DomainUnload -= OnDomainUnload; AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeAssemblyReload; try { _client?.Dispose(); } catch { } _client = null; try { ConsoleCapture.Shutdown(); } catch { } try { MainThreadDispatcher.Shutdown(); } catch { } if (!keepServer) { NodeProcessManager.StopServer(); } } public static void Restart() { Shutdown(keepServer: false); EnsureStarted(); } public static void Reconnect() { lock (_startLock) { try { _client?.Dispose(); } catch { } McpTcpClient.CleanupStaleLockFiles(); _client = new McpTcpClient(McpSettings.Host, McpSettings.IpcPort); _client.Start(); } } /// <summary> /// Detects if we're running in a background/worker Unity process. /// </summary> static bool IsBackgroundProcess() { if (Application.isBatchMode) return true; var args = Environment.GetCommandLineArgs(); foreach (var arg in args) { if (arg == "-batchMode") return true; if (arg.Contains("AssetImport")) return true; if (arg.Contains("Worker")) return true; } return false; } } }

Latest Blog Posts

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/Singtaa/UnityMCP'

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