Skip to main content
Glama
ScriptUtils.cs9.54 kB
/* ┌──────────────────────────────────────────────────────────────────┐ │ Author: Ivan Murzak (https://github.com/IvanMurzak) │ │ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │ │ Copyright (c) 2025 Ivan Murzak │ │ Licensed under the Apache License, Version 2.0. │ │ See the LICENSE file in the project root for more information. │ └──────────────────────────────────────────────────────────────────┘ */ #nullable enable using System.Collections.Generic; using System.Linq; using com.IvanMurzak.McpPlugin.Common.Model; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using UnityEditor; using UnityEditor.Compilation; namespace com.IvanMurzak.Unity.MCP.Editor.Utils { [InitializeOnLoad] public static partial class ScriptUtils { private const string PendingNotificationKeysKey = "MCP_PendingNotificationKeys"; private const string NotificationDataSeparator = "<MCP_SEP>"; private static bool _processPendingScheduled = false; // Store compilation messages (thread-safe with lock) private static readonly List<CompilerMessage> _lastCompilationMessages = new List<CompilerMessage>(); private static readonly object _compilationMessagesLock = new object(); static ScriptUtils() { // Process any pending notifications after domain reload (successful compilation) ScheduleProcessPendingNotifications(); // Also listen for compilation event to handle both success and failure cases CompilationPipeline.assemblyCompilationFinished += OnAssemblyCompilationFinished; } private static void OnAssemblyCompilationFinished(string assemblyPath, CompilerMessage[] messages) { lock (_compilationMessagesLock) { _lastCompilationMessages.Clear(); _lastCompilationMessages.AddRange(messages); } ScheduleProcessPendingNotifications(); } private static void ScheduleProcessPendingNotifications() { if (_processPendingScheduled) return; _processPendingScheduled = true; EditorApplication.update += ProcessPendingNotificationsOnce; } private static void ProcessPendingNotificationsOnce() { EditorApplication.update -= ProcessPendingNotificationsOnce; if (!_processPendingScheduled) return; _processPendingScheduled = false; ProcessPendingNotifications(); } /// <summary> /// Checks if the provided C# code has valid syntax. /// This method uses Roslyn to parse the code and check for syntax errors. /// </summary> /// <param name="code"> /// <param name="errors"></param> /// <returns>True if the code has valid syntax; otherwise, false.</returns> public static bool IsValidCSharpSyntax(string code, out IEnumerable<Diagnostic> errors) { var syntaxTree = CSharpSyntaxTree.ParseText(code); var diagnostics = syntaxTree.GetDiagnostics(); errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error); return !errors.Any(); } /// <summary> /// Schedules a notification to be sent after Unity compilation completes. /// Uses SessionState to persist across domain reloads. /// </summary> /// <param name="requestId">The request ID to track</param> /// <param name="filePath">The file path that was modified</param> /// <param name="operationType">The type of operation performed</param> public static void SchedulePostCompilationNotification(string requestId, string filePath, string operationType) { var notificationKey = $"MCP_PendingNotification_{requestId}"; var notificationData = $"{requestId}{NotificationDataSeparator}{filePath}{NotificationDataSeparator}{operationType}"; // Store the notification data SessionState.SetString(notificationKey, notificationData); // Add to the key list var existingKeys = SessionState.GetString(PendingNotificationKeysKey, string.Empty); var keyList = string.IsNullOrEmpty(existingKeys) ? new List<string>() : existingKeys.Split(',').Where(k => !string.IsNullOrEmpty(k)).ToList(); if (!keyList.Contains(notificationKey)) { keyList.Add(notificationKey); SessionState.SetString(PendingNotificationKeysKey, string.Join(",", keyList)); } } /// <summary> /// Called by InitializeOnLoad to process any pending notifications after compilation. /// </summary> public static void ProcessPendingNotifications() { // Get the list of pending notification keys from SessionState var pendingKeys = SessionState.GetString(PendingNotificationKeysKey, string.Empty); if (string.IsNullOrEmpty(pendingKeys)) return; var keys = pendingKeys.Split(',').Where(k => !string.IsNullOrEmpty(k)).ToList(); if (keys.Count == 0) return; var processedKeys = new List<string>(); foreach (var key in keys) { var notificationData = SessionState.GetString(key, string.Empty); if (string.IsNullOrEmpty(notificationData)) { processedKeys.Add(key); continue; } var parts = notificationData.Split(NotificationDataSeparator); if (parts.Length != 3) { processedKeys.Add(key); continue; } var requestId = parts[0]; var filePath = parts[1]; var operationType = parts[2]; // Check for compilation errors var hasErrors = HasCompilationErrors(); ResponseCallTool response; if (hasErrors) { var errorDetails = GetCompilationErrorDetails(); var message = $"[Warning] {operationType} completed: {filePath}, but compilation errors occurred. Details:\n{errorDetails}"; response = ResponseCallTool.Success(message).SetRequestID(requestId); } else { var message = $"[Success] {operationType} completed: {filePath}"; response = ResponseCallTool.Success(message).SetRequestID(requestId); } // Send notification and mark for cleanup _ = UnityMcpPlugin.NotifyToolRequestCompleted(new RequestToolCompletedData { RequestId = requestId, Result = response }); processedKeys.Add(key); } // Clean up processed keys foreach (var key in processedKeys) SessionState.EraseString(key); // Update the key list var remainingKeys = keys.Except(processedKeys).ToList(); if (remainingKeys.Count > 0) { SessionState.SetString(PendingNotificationKeysKey, string.Join(",", remainingKeys)); } else { SessionState.EraseString(PendingNotificationKeysKey); } } /// <summary> /// Checks if there are any compilation errors by examining Unity's console and compilation pipeline. /// </summary> /// <returns>True if compilation errors exist</returns> public static bool HasCompilationErrors() => EditorUtility.scriptCompilationFailed; /// <summary> /// Retrieves detailed compilation error messages from the last compilation. /// </summary> /// <returns>Detailed compilation error messages</returns> public static string GetCompilationErrorDetails() { var errors = new List<string>(); lock (_compilationMessagesLock) { foreach (var message in _lastCompilationMessages) { if (message.type == CompilerMessageType.Error) errors.Add($"[{message.file}] {message.message} (Line: {message.line})"); } } if (errors.Count == 0) { // Since we can reliably check if compilation failed with EditorUtility.scriptCompilationFailed, // but getting detailed messages is complex, provide a simple fallback if (EditorUtility.scriptCompilationFailed) return "Compilation errors detected. Please check the Unity Console window for detailed error messages."; errors.Add("No detailed compilation errors found."); } return string.Join("\n", errors); } } }

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

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