Skip to main content
Glama
UnityMCPEditorExtension.cs47.7 kB
using UnityEditor; using UnityEngine; using System; using System.Collections.Generic; using System.Threading.Tasks; using System.Net; using System.Threading; using System.Text; using System.IO; using Debug = UnityEngine.Debug; using UnityMCP.Client.Editor.Commands; namespace UnityMCP.Client.Editor { /// <summary> /// Data structure for log entries /// </summary> [System.Serializable] public class LogEntryData { public string message; public string data; public string level; public string timestamp; } /// <summary> /// Result of a code execution operation /// </summary> public class CodeExecutionResult { /// <summary> /// Whether the execution was successful /// </summary> public bool Success { get; set; } /// <summary> /// The result of the execution (if successful) /// </summary> public string Result { get; set; } /// <summary> /// The error message (if unsuccessful) /// </summary> public string Error { get; set; } /// <summary> /// Logs from the execution /// </summary> public List<string> Logs { get; set; } = new List<string>(); /// <summary> /// The time taken to execute the code (in milliseconds) /// </summary> public long ExecutionTime { get; set; } } /// <summary> /// Represents the current state of the game /// </summary> public class GameState { /// <summary> /// Whether the game is currently playing /// </summary> public bool IsPlaying { get; set; } /// <summary> /// Whether the game is currently paused /// </summary> public bool IsPaused { get; set; } /// <summary> /// Whether the game is currently compiling /// </summary> public bool IsCompiling { get; set; } /// <summary> /// The name of the current scene /// </summary> public string CurrentScene { get; set; } = string.Empty; /// <summary> /// The current time scale /// </summary> public float TimeScale { get; set; } /// <summary> /// The current frame count /// </summary> public int FrameCount { get; set; } /// <summary> /// The time since the game started /// </summary> public float RealtimeSinceStartup { get; set; } } /// <summary> /// Simple logger for AI integration /// </summary> public class AILogger { private string _logName; private const string LogEndpoint = "http://localhost:3030/logs"; /// <summary> /// Constructor /// </summary> /// <param name="logName">The name of the log</param> public AILogger(string logName) { _logName = logName; } /// <summary> /// Log a debug message /// </summary> /// <param name="message">The message to log</param> /// <param name="data">Additional data (optional)</param> /// <returns>Task</returns> public async Task LogDebug(string message, object data = null) { UnityEngine.Debug.Log($"[{_logName}] DEBUG: {message}"); await AppendToLog(message, data, "DEBUG"); } /// <summary> /// Log an info message /// </summary> /// <param name="message">The message to log</param> /// <param name="data">Additional data (optional)</param> /// <returns>Task</returns> public async Task Info(string message, object data = null) { UnityEngine.Debug.Log($"[{_logName}] INFO: {message}"); await AppendToLog(message, data, "INFO"); } /// <summary> /// Log a warning message /// </summary> /// <param name="message">The message to log</param> /// <param name="data">Additional data (optional)</param> /// <returns>Task</returns> public async Task Warning(string message, object data = null) { UnityEngine.Debug.LogWarning($"[{_logName}] WARNING: {message}"); await AppendToLog(message, data, "WARNING"); } /// <summary> /// Log an error message /// </summary> /// <param name="message">The message to log</param> /// <param name="data">Additional data (optional)</param> /// <returns>Task</returns> public async Task Error(string message, object data = null) { UnityEngine.Debug.LogError($"[{_logName}] ERROR: {message}"); await AppendToLog(message, data, "ERROR"); } /// <summary> /// Append to log /// </summary> /// <param name="message">The message to log</param> /// <param name="data">Additional data</param> /// <param name="level">Log level</param> /// <returns>Task</returns> private async Task AppendToLog(string message, object data, string level) { try { // Log to Unity console first (in case the HTTP request fails) UnityEngine.Debug.Log($"[{_logName}] {level}: {message}"); // Create a log entry object var logEntry = new LogEntryData { message = message, data = data != null ? data.ToString() : null, level = level, timestamp = DateTime.UtcNow.ToString("o") }; // Serialize to JSON using a simple approach string json = $"{{\"message\":\"{message.Replace("\"", "\\\"")}\",\"level\":\"{level}\",\"timestamp\":\"{DateTime.UtcNow.ToString("o")}\"}}"; // Create the request var request = WebRequest.Create($"{LogEndpoint}/{_logName}"); request.Method = "POST"; request.ContentType = "application/json"; // Write the data using (var streamWriter = new StreamWriter(await request.GetRequestStreamAsync())) { await streamWriter.WriteAsync(json); } // Get the response using (var response = await request.GetResponseAsync()) { using (var streamReader = new StreamReader(response.GetResponseStream())) { var result = await streamReader.ReadToEndAsync(); UnityEngine.Debug.Log($"[{_logName}] Log appended: {result}"); } } } catch (WebException webEx) when (webEx.Response is HttpWebResponse response) { // Handle HTTP errors specifically UnityEngine.Debug.LogError($"[{_logName}] HTTP error appending to log: {(int)response.StatusCode} {response.StatusDescription}"); // Try to read the response body for more details try { using (var streamReader = new StreamReader(webEx.Response.GetResponseStream())) { var errorResponse = await streamReader.ReadToEndAsync(); UnityEngine.Debug.LogError($"[{_logName}] Error details: {errorResponse}"); } } catch { // Ignore errors when trying to read the error response } } catch (Exception ex) { // Handle other errors UnityEngine.Debug.LogError($"[{_logName}] Error appending to log: {ex.Message}"); UnityEngine.Debug.LogError($"[{_logName}] Stack trace: {ex.StackTrace}"); } } } /// <summary> /// Editor extension for Unity MCP integration /// </summary> [InitializeOnLoad] public class UnityMCPEditorExtension { private static bool isInitialized = false; private static bool autoStartServer = true; private static bool serverStarted = false; private static HttpListener httpListener; private static Thread serverThread; private static int serverPort = 8082; private static CancellationTokenSource cancellationTokenSource; // Dictionary to store log entries private static Dictionary<string, object> logEntries = new Dictionary<string, object>(); private static object logLock = new object(); // List to store captured logs private static List<string> capturedLogs = new List<string>(); private static object capturedLogsLock = new object(); // Flag to indicate if we're capturing logs private static bool isCapturingLogs = false; /// <summary> /// Store a log entry /// </summary> /// <param name="logName">The name of the log</param> /// <param name="data">The data to store</param> public static void StoreLogEntry(string logName, object data) { lock (logLock) { logEntries[logName] = data; Debug.Log($"[Unity MCP] Stored log entry: {logName}"); } } /// <summary> /// Get a log entry /// </summary> /// <param name="logName">The name of the log</param> /// <returns>The log entry data</returns> private static object GetLogEntry(string logName) { lock (logLock) { if (logEntries.TryGetValue(logName, out var data)) { Debug.Log($"[Unity MCP] Retrieved log entry: {logName}"); return data; } Debug.LogWarning($"[Unity MCP] Log entry not found: {logName}"); return null; } } /// <summary> /// Static constructor called on Unity Editor startup /// </summary> static UnityMCPEditorExtension() { // Initialize when Unity Editor starts EditorApplication.delayCall += Initialize; // Register for play mode state changes EditorApplication.playModeStateChanged += OnPlayModeStateChanged; // Make sure we clean up when the editor is closing EditorApplication.quitting += Shutdown; } /// <summary> /// Initialize the Unity MCP Editor Extension /// </summary> private static void Initialize() { if (isInitialized) return; Debug.Log("[Unity MCP] Initializing Unity MCP Editor Extension"); // Load settings autoStartServer = EditorPrefs.GetBool("UnityMCP_AutoStartServer", true); // Start the server if auto-start is enabled if (autoStartServer && !serverStarted) { StartServer(); } isInitialized = true; } /// <summary> /// Start the Unity MCP server /// </summary> public static void StartServer() { if (serverStarted) return; Debug.Log("[Unity MCP] Starting Unity MCP server"); try { // Create a new cancellation token source cancellationTokenSource = new CancellationTokenSource(); // Create a new HTTP listener httpListener = new HttpListener(); // Add prefixes for both IPv4 and IPv6 string localhostPrefix = $"http://localhost:{serverPort}/"; string ipv4Prefix = $"http://127.0.0.1:{serverPort}/"; Debug.Log($"[Unity MCP] Adding HTTP listener prefixes: {localhostPrefix}, {ipv4Prefix}"); httpListener.Prefixes.Add(localhostPrefix); httpListener.Prefixes.Add(ipv4Prefix); Debug.Log($"[Unity MCP] Starting HTTP listener"); httpListener.Start(); Debug.Log($"[Unity MCP] HTTP listener started: {httpListener.IsListening}"); // Log the HTTP listener details var setupLogger = new AILogger("unity-http-setup"); _ = setupLogger.Info($"HTTP listener created and started", new { port = serverPort, prefixes = new[] { localhostPrefix, ipv4Prefix } }); // Start a thread to handle HTTP requests serverThread = new Thread(() => HandleHttpRequests(cancellationTokenSource.Token)); serverThread.IsBackground = true; serverThread.Start(); // Log that the thread was started _ = setupLogger.Info("HTTP server thread started"); // Set the flag serverStarted = true; // Register for domain unload to ensure clean shutdown AppDomain.CurrentDomain.DomainUnload += (sender, e) => StopServer(); Debug.Log($"[Unity MCP] Unity MCP server started on port {serverPort}"); } catch (Exception ex) { Debug.LogError($"[Unity MCP] Error starting Unity MCP server: {ex.Message}"); } } /// <summary> /// Handle HTTP requests /// </summary> /// <param name="cancellationToken">Cancellation token to stop the thread</param> private static void HandleHttpRequests(CancellationToken cancellationToken) { // Create a debug logger var debugLogger = new AILogger("unity-http-debug"); _ = debugLogger.Info("HTTP server thread started", new { port = serverPort, isListening = httpListener?.IsListening ?? false }); // Log the HTTP listener details foreach (string prefix in httpListener.Prefixes) { _ = debugLogger.Info($"HTTP listener prefix: {prefix}"); } while (httpListener != null && httpListener.IsListening && !cancellationToken.IsCancellationRequested) { try { // Get the context directly - this is a blocking call var context = httpListener.GetContext(); var request = context.Request; var response = context.Response; // Log the request Debug.Log($"[Unity MCP] Received request: {request.HttpMethod} {request.Url.AbsolutePath}"); // Super simple response for all requests string responseText = "{ \"success\": true, \"message\": \"Hello from Unity MCP!\" }"; // Special cases for different endpoints if (request.Url.AbsolutePath == "/ping") { responseText = "pong"; } else if (request.Url.AbsolutePath == "/api/CodeExecution/start-game") { Debug.Log("[Unity MCP] Starting game"); // Create a cancellation token source var cts = new CancellationTokenSource(); var token = cts.Token; // Schedule StartGame to run on the main thread EditorApplication.delayCall += () => { try { Debug.Log("[Unity MCP] Executing StartGame on main thread"); StartGame(); Debug.Log("[Unity MCP] Game started successfully"); } catch (Exception ex) { Debug.LogError($"[Unity MCP] Error starting game: {ex.Message}"); } finally { // Signal that the operation is complete cts.Cancel(); } }; // Wait for the operation to complete or timeout try { // Wait for a short time to see if the operation completes quickly Task.Delay(100, token).Wait(); responseText = "{ \"success\": true, \"message\": \"Game started\" }"; } catch (OperationCanceledException) { // The operation completed successfully responseText = "{ \"success\": true, \"message\": \"Game started\" }"; } catch (Exception ex) { // An error occurred Debug.LogError($"[Unity MCP] Error waiting for game to start: {ex.Message}"); responseText = $"{{ \"success\": false, \"message\": \"Error starting game: {ex.Message}\" }}"; } } else if (request.Url.AbsolutePath == "/api/CodeExecution/stop-game") { Debug.Log("[Unity MCP] Stopping game"); // Create a cancellation token source var cts = new CancellationTokenSource(); var token = cts.Token; // Schedule StopGame to run on the main thread EditorApplication.delayCall += () => { try { Debug.Log("[Unity MCP] Executing StopGame on main thread"); StopGame(); Debug.Log("[Unity MCP] Game stopped successfully"); } catch (Exception ex) { Debug.LogError($"[Unity MCP] Error stopping game: {ex.Message}"); } finally { // Signal that the operation is complete cts.Cancel(); } }; // Wait for the operation to complete or timeout try { // Wait for a short time to see if the operation completes quickly Task.Delay(100, token).Wait(); responseText = "{ \"success\": true, \"message\": \"Game stopped\" }"; } catch (OperationCanceledException) { // The operation completed successfully responseText = "{ \"success\": true, \"message\": \"Game stopped\" }"; } catch (Exception ex) { // An error occurred Debug.LogError($"[Unity MCP] Error waiting for game to stop: {ex.Message}"); responseText = $"{{ \"success\": false, \"message\": \"Error stopping game: {ex.Message}\" }}"; } } else if (request.Url.AbsolutePath == "/api/CodeExecution/game-state") { Debug.Log("[Unity MCP] Getting game state"); // Create a cancellation token source var cts = new CancellationTokenSource(); var token = cts.Token; // Default game state var gameState = new EditorGameState { IsPlaying = false, IsPaused = false, IsCompiling = false, CurrentScene = "SampleScene", TimeScale = 1.0f, FrameCount = 0, RealtimeSinceStartup = 0.0f }; // Schedule a task to get the actual game state on the main thread EditorApplication.delayCall += () => { try { Debug.Log("[Unity MCP] Getting actual game state on main thread"); gameState = new EditorGameState { IsPlaying = EditorApplication.isPlaying, IsPaused = EditorApplication.isPaused, IsCompiling = EditorApplication.isCompiling, CurrentScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name, TimeScale = Time.timeScale, FrameCount = Time.frameCount, RealtimeSinceStartup = Time.realtimeSinceStartup }; Debug.Log($"[Unity MCP] Actual game state: IsPlaying={gameState.IsPlaying}, IsPaused={gameState.IsPaused}, CurrentScene={gameState.CurrentScene}"); } catch (Exception ex) { Debug.LogError($"[Unity MCP] Error getting game state: {ex.Message}"); } finally { // Signal that the operation is complete cts.Cancel(); } }; // Wait for the operation to complete or timeout try { // Wait for a short time to see if the operation completes quickly Task.Delay(100, token).Wait(); // Convert to JSON responseText = $"{{ \"isPlaying\": {gameState.IsPlaying.ToString().ToLower()}, \"isPaused\": {gameState.IsPaused.ToString().ToLower()}, \"isCompiling\": {gameState.IsCompiling.ToString().ToLower()}, \"currentScene\": \"{gameState.CurrentScene}\", \"timeScale\": {gameState.TimeScale}, \"frameCount\": {gameState.FrameCount}, \"realtimeSinceStartup\": {gameState.RealtimeSinceStartup} }}"; } catch (OperationCanceledException) { // The operation completed successfully // Convert to JSON responseText = $"{{ \"isPlaying\": {gameState.IsPlaying.ToString().ToLower()}, \"isPaused\": {gameState.IsPaused.ToString().ToLower()}, \"isCompiling\": {gameState.IsCompiling.ToString().ToLower()}, \"currentScene\": \"{gameState.CurrentScene}\", \"timeScale\": {gameState.TimeScale}, \"frameCount\": {gameState.FrameCount}, \"realtimeSinceStartup\": {gameState.RealtimeSinceStartup} }}"; } catch (Exception ex) { // An error occurred Debug.LogError($"[Unity MCP] Error waiting for game state: {ex.Message}"); responseText = $"{{ \"success\": false, \"message\": \"Error getting game state: {ex.Message}\" }}"; } } else if (request.Url.AbsolutePath == "/api/CodeExecution/execute-code" && request.HttpMethod == "POST") { Debug.Log("[Unity MCP] Executing code"); // Read the request body to get the code to execute string code = ""; using (var reader = new StreamReader(request.InputStream, request.ContentEncoding)) { code = reader.ReadToEnd(); } Debug.Log($"[Unity MCP] Code to execute: {code}"); // Create a cancellation token source var cts = new CancellationTokenSource(); var token = cts.Token; // Result variables bool success = false; string result = ""; string error = ""; string commandType = ""; // Schedule the code execution on the main thread EditorApplication.delayCall += () => { try { Debug.Log("[Unity MCP] Executing code on main thread"); // Execute the code using C# scripting Debug.Log($"[Unity MCP] Executing code: {code}"); // Actually execute the code try { // Execute the code directly // This is a simple implementation that just executes the code directly // In a real implementation, you would use a more robust approach // like Microsoft.CodeAnalysis.CSharp.Scripting // For now, we'll just execute the code directly // This is not safe, but it's simple and works for demo purposes // The code will be executed in the context of the Unity Editor // and can access all Unity APIs // Execute the code and get the result object executionResult = ExecuteCodeInEditor(code); success = true; result = executionResult != null ? executionResult.ToString() : "null"; commandType = executionResult != null ? executionResult.GetType().Name : "null"; Debug.Log($"[Unity MCP] Code execution result: {result}"); // Store the result in a log entry StoreLogEntry("unity-execute-detailed-result", new { code, result, commandType = executionResult?.GetType().Name, success = true }); } catch (Exception ex) { // Log the error Debug.LogError($"[Unity MCP] Error executing code: {ex.Message}"); success = false; error = ex.Message; // Store the error in a log entry StoreLogEntry("unity-execute-detailed-result", new { code, error = ex.Message, stackTrace = ex.StackTrace, success = false }); } Debug.Log("[Unity MCP] Code executed successfully"); } catch (Exception ex) { // Log the error Debug.LogError($"[Unity MCP] Error executing code: {ex.Message}"); success = false; error = ex.Message; } finally { // Signal that the operation is complete cts.Cancel(); } }; // Wait for the operation to complete or timeout try { // Wait for a short time to see if the operation completes quickly Task.Delay(100, token).Wait(); // Return the result if (success) { // Create a detailed response var responseObj = new { success = true, result = result, code = code, commandType = commandType, logs = new[] { "Code executed successfully" }, executionTime = 0, details = "Command executed successfully" }; // Convert to JSON responseText = JsonUtility.ToJson(responseObj); } else { responseText = $"{{ \"success\": false, \"error\": \"{error}\", \"code\": \"{code}\", \"logs\": [\"Error executing code\"], \"executionTime\": 0 }}"; } } catch (OperationCanceledException) { // The operation completed successfully if (success) { responseText = $"{{ \"success\": true, \"result\": \"{result}\", \"logs\": [\"Code executed successfully\"], \"executionTime\": 0 }}"; } else { responseText = $"{{ \"success\": false, \"error\": \"{error}\", \"logs\": [\"Error executing code\"], \"executionTime\": 0 }}"; } } catch (Exception ex) { // An error occurred Debug.LogError($"[Unity MCP] Error waiting for code execution: {ex.Message}"); responseText = $"{{ \"success\": false, \"error\": \"Error executing code: {ex.Message}\", \"logs\": [\"Error executing code\"], \"executionTime\": 0 }}"; } } // Send the response byte[] buffer = Encoding.UTF8.GetBytes(responseText); response.ContentLength64 = buffer.Length; response.ContentType = "application/json"; response.StatusCode = 200; response.OutputStream.Write(buffer, 0, buffer.Length); response.Close(); Debug.Log($"[Unity MCP] Response sent: {responseText}"); } catch (OperationCanceledException) { // This is expected when cancellation is requested Debug.Log("[Unity MCP] HTTP server thread was cancelled"); _ = debugLogger.Info("HTTP server thread was cancelled"); break; } catch (ThreadAbortException) { // This is expected when Unity is shutting down Debug.Log("[Unity MCP] HTTP server thread was aborted"); _ = debugLogger.Info("HTTP server thread was aborted"); break; } catch (Exception ex) { // Only log the error if we're not being cancelled if (!cancellationToken.IsCancellationRequested) { Debug.LogError($"[Unity MCP] Error handling HTTP request: {ex.Message}"); _ = debugLogger.Error($"Error handling HTTP request: {ex.Message}", new { stackTrace = ex.StackTrace }); } // Add a small delay to prevent CPU spinning in case of repeated errors Thread.Sleep(100); } } Debug.Log("[Unity MCP] HTTP server thread exiting"); } /// <summary> /// Stop the Unity MCP server /// </summary> public static void StopServer() { if (!serverStarted) return; Debug.Log("[Unity MCP] Stopping Unity MCP server"); try { // Signal cancellation to stop the thread gracefully if (cancellationTokenSource != null) { cancellationTokenSource.Cancel(); } // Stop the HTTP listener if (httpListener != null) { httpListener.Stop(); httpListener.Close(); httpListener = null; } // Wait for the server thread to exit if (serverThread != null && serverThread.IsAlive) { serverThread.Join(5000); // Wait up to 5 seconds serverThread = null; } // Dispose the cancellation token source if (cancellationTokenSource != null) { cancellationTokenSource.Dispose(); cancellationTokenSource = null; } // Set the flag serverStarted = false; Debug.Log("[Unity MCP] Unity MCP server stopped"); } catch (Exception ex) { Debug.LogError($"[Unity MCP] Error stopping Unity MCP server: {ex.Message}"); } } /// <summary> /// Shutdown the Unity MCP Editor Extension /// </summary> private static void Shutdown() { Debug.Log("[Unity MCP] Shutting down Unity MCP Editor Extension"); // Stop the server if it's running if (serverStarted) { StopServer(); } isInitialized = false; } /// <summary> /// Handle play mode state changes /// </summary> private static void OnPlayModeStateChanged(PlayModeStateChange state) { Debug.Log($"[Unity MCP] Play mode state changed: {state}"); // You can add custom logic here for different play mode states switch (state) { case PlayModeStateChange.EnteredPlayMode: Debug.Log("[Unity MCP] Entered play mode"); break; case PlayModeStateChange.ExitingPlayMode: Debug.Log("[Unity MCP] Exiting play mode"); break; case PlayModeStateChange.EnteredEditMode: Debug.Log("[Unity MCP] Entered edit mode"); break; case PlayModeStateChange.ExitingEditMode: Debug.Log("[Unity MCP] Exiting edit mode"); break; } } /// <summary> /// Start the game (enter play mode) /// </summary> public static void StartGame() { if (EditorApplication.isPlaying) return; Debug.Log("[Unity MCP] Starting game (entering play mode)"); EditorApplication.isPlaying = true; } /// <summary> /// Stop the game (exit play mode) /// </summary> public static void StopGame() { if (!EditorApplication.isPlaying) return; Debug.Log("[Unity MCP] Stopping game (exiting play mode)"); EditorApplication.isPlaying = false; } /// <summary> /// Get the current game state /// </summary> public static EditorGameState GetGameState() { return new EditorGameState { IsPlaying = EditorApplication.isPlaying, IsPaused = EditorApplication.isPaused, IsCompiling = EditorApplication.isCompiling, CurrentScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name, TimeScale = Time.timeScale, FrameCount = Time.frameCount, RealtimeSinceStartup = Time.realtimeSinceStartup }; } /// <summary> /// Execute code in the Unity Editor /// </summary> /// <param name="code">The code to execute</param> /// <returns>The result of the code execution</returns> private static object ExecuteCodeInEditor(string code) { try { // Log the code we're executing Debug.Log($"[Unity MCP] Executing code: {code}"); StoreLogEntry("unity-execute-input", new { code }); // Create a command context var context = new CommandContext(); // Parse the code into a command ICommand command = CommandFactory.CreateCommand(code); Debug.Log($"[Unity MCP] Created command of type: {command.GetType().Name}"); StoreLogEntry("unity-execute-command", new { commandType = command.GetType().Name }); // Execute the command object result = command.Execute(context); // Log the result Debug.Log($"[Unity MCP] Code executed successfully. Result: {result}"); StoreLogEntry("unity-execute-result", new { result = result?.ToString() }); // Return the result return result; } catch (Exception ex) { // Log the error Debug.LogError($"[Unity MCP] Error executing code: {ex.Message}"); StoreLogEntry("unity-execute-error", new { error = ex.Message, stackTrace = ex.StackTrace }); throw; // Rethrow the exception to be caught by the caller } } /// <summary> /// Extract the last statement from a code block /// </summary> /// <param name="code">The code block</param> /// <returns>The last statement</returns> private static string ExtractLastStatement(string code) { // This is a very simple implementation that just returns the last line // In a real implementation, you would use a more robust approach // like parsing the code with Roslyn // Split the code into lines string[] lines = code.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); // Find the last non-empty line that's not a comment for (int i = lines.Length - 1; i >= 0; i--) { string line = lines[i].Trim(); if (!string.IsNullOrEmpty(line) && !line.StartsWith("//") && !line.StartsWith("/*") && !line.StartsWith("*")) { return line; } } // If we couldn't find a valid last statement, return null return null; } /// <summary> /// Evaluate a statement and return its value /// </summary> /// <param name="statement">The statement to evaluate</param> /// <returns>The value of the statement</returns> private static object EvaluateStatement(string statement) { // This is a very simple implementation that just returns the statement as a string // In a real implementation, you would use a more robust approach // like evaluating the statement with Roslyn // For demo purposes, we'll just return the statement as a string return statement; } /// <summary> /// Execute code using Unity's built-in Mono runtime /// </summary> /// <param name="code">The code to execute</param> /// <returns>The result of the code execution</returns> private static object CompileMethod(string code) { // This implementation uses Unity's built-in Mono runtime to execute the code // It's not as robust as using Roslyn, but it's better than just returning the code try { // First, let's try to extract the last expression from the code // This is a simple implementation that just looks for the last non-comment line string lastExpression = ExtractLastStatement(code); // If we couldn't find a valid last expression, return null if (string.IsNullOrEmpty(lastExpression)) { Debug.LogWarning("[Unity MCP] Couldn't find a valid last expression in the code"); return null; } // Now, let's try to evaluate the expression using Unity's built-in capabilities // We'll use reflection to access Unity's internal evaluation capabilities // First, let's try to execute the code directly in the Unity Editor // We'll create a temporary GameObject and execute the code in its context // Create a temporary GameObject var tempGO = new GameObject("CodeExecutor"); try { // Execute the code in the context of the GameObject // This is a simple implementation that just executes the code // and returns the result of the last expression // For simple expressions, we can try to evaluate them directly if (lastExpression.Contains("GameObject") || lastExpression.Contains("UnityEngine")) { // For Unity-specific expressions, we'll just return the expression itself // In a real implementation, we would use a more robust approach Debug.Log($"[Unity MCP] Executing Unity-specific expression: {lastExpression}"); // For demo purposes, we'll just return the expression return lastExpression; } else { // For simple expressions, we can try to evaluate them directly Debug.Log($"[Unity MCP] Executing simple expression: {lastExpression}"); // Try to evaluate the expression object result = EvaluateExpression(lastExpression); // Return the result return result; } } finally { // Clean up the temporary GameObject UnityEngine.Object.DestroyImmediate(tempGO); } } catch (Exception ex) { // Log the error Debug.LogError($"[Unity MCP] Error compiling code: {ex.Message}"); throw; // Rethrow the exception to be caught by the caller } } /// <summary> /// Evaluate a simple expression /// </summary> /// <param name="expression">The expression to evaluate</param> /// <returns>The result of the expression</returns> private static object EvaluateExpression(string expression) { // This is a simple implementation that evaluates basic expressions // In a real implementation, you would use a more robust approach like Roslyn // For demo purposes, we'll just handle a few simple cases // Remove any trailing semicolon expression = expression.TrimEnd(';'); // Check if it's a string literal if (expression.StartsWith("\"") && expression.EndsWith("\"")) { // It's a string literal, return the string without the quotes return expression.Substring(1, expression.Length - 2); } // Check if it's a numeric literal if (int.TryParse(expression, out int intValue)) { // It's an integer literal return intValue; } if (float.TryParse(expression, out float floatValue)) { // It's a float literal return floatValue; } if (bool.TryParse(expression, out bool boolValue)) { // It's a boolean literal return boolValue; } // For other expressions, we'll just return the expression itself return expression; } /// <summary> /// Game state information /// </summary> [Serializable] public class EditorGameState { /// <summary> /// Whether the game is currently playing /// </summary> public bool IsPlaying { get; set; } /// <summary> /// Whether the game is currently paused /// </summary> public bool IsPaused { get; set; } /// <summary> /// Whether the editor is currently compiling /// </summary> public bool IsCompiling { get; set; } /// <summary> /// The name of the current scene /// </summary> public string CurrentScene { get; set; } /// <summary> /// The current time scale /// </summary> public float TimeScale { get; set; } /// <summary> /// The current frame count /// </summary> public int FrameCount { get; set; } /// <summary> /// The time in seconds since the start of the game /// </summary> public float RealtimeSinceStartup { get; set; } } } }

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/TSavo/Unity-MCP'

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