diff --git a/.gitignore b/.gitignore index fd0d720..ce4b2f3 100644 --- a/.gitignore +++ b/.gitignore @@ -121,3 +121,6 @@ log.txt log.txt.meta Server~/build/* Server~/node_modules/* + +# Claude settings +.claude/settings.local.json diff --git a/Editor/Services/ConsoleLogsServiceUnity6.cs b/Editor/Services/ConsoleLogsServiceUnity6.cs new file mode 100644 index 0000000..0a7acd3 --- /dev/null +++ b/Editor/Services/ConsoleLogsServiceUnity6.cs @@ -0,0 +1,385 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using Newtonsoft.Json.Linq; +using UnityEditor; +using UnityEngine; + +namespace McpUnity.Services +{ + /// + /// Unity 6 specific implementation using ConsoleWindowUtility API + /// This implementation uses Unity's internal console APIs for more reliable log retrieval + /// + public class ConsoleLogsServiceUnity6 : IConsoleLogsService + { + // Static mapping for MCP log types to Unity log types + private static readonly Dictionary> LogTypeMapping = new Dictionary>(StringComparer.OrdinalIgnoreCase) + { + { "info", new HashSet(StringComparer.OrdinalIgnoreCase) { "Log" } }, + { "error", new HashSet(StringComparer.OrdinalIgnoreCase) { "Error", "Exception", "Assert" } }, + { "warning", new HashSet(StringComparer.OrdinalIgnoreCase) { "Warning" } } + }; + + // Reflection cache for internal Unity APIs + private static Type _logEntriesType; + private static MethodInfo _getCountMethod; + private static MethodInfo _getEntryInternalMethod; + private static MethodInfo _startGettingEntriesMethod; + private static MethodInfo _endGettingEntriesMethod; + private static Type _logEntryType; + private static FieldInfo _messageField; + private static FieldInfo _fileField; + private static FieldInfo _lineField; + private static FieldInfo _modeField; + private static FieldInfo _callstackTextStartUTF8Field; + private static FieldInfo _callstackTextStartUTF16Field; + + static ConsoleLogsServiceUnity6() + { + InitializeReflection(); + } + + private static void InitializeReflection() + { + // Get LogEntries type + _logEntriesType = Type.GetType("UnityEditor.LogEntries,UnityEditor"); + if (_logEntriesType == null) + { + Debug.LogError("[MCP Unity] Failed to find LogEntries type"); + return; + } + + // Get LogEntry type + _logEntryType = Type.GetType("UnityEditor.LogEntry,UnityEditor"); + if (_logEntryType == null) + { + Debug.LogError("[MCP Unity] Failed to find LogEntry type"); + return; + } + + // Get methods + _getCountMethod = _logEntriesType.GetMethod("GetCount", + BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic); + + _startGettingEntriesMethod = _logEntriesType.GetMethod("StartGettingEntries", + BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic); + + _endGettingEntriesMethod = _logEntriesType.GetMethod("EndGettingEntries", + BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic); + + _getEntryInternalMethod = _logEntriesType.GetMethod("GetEntryInternal", + BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic); + + // Get fields + _messageField = _logEntryType.GetField("message", BindingFlags.Public | BindingFlags.Instance); + _fileField = _logEntryType.GetField("file", BindingFlags.Public | BindingFlags.Instance); + _lineField = _logEntryType.GetField("line", BindingFlags.Public | BindingFlags.Instance); + _modeField = _logEntryType.GetField("mode", BindingFlags.Public | BindingFlags.Instance); + _callstackTextStartUTF8Field = _logEntryType.GetField("callstackTextStartUTF8", BindingFlags.Public | BindingFlags.Instance); + _callstackTextStartUTF16Field = _logEntryType.GetField("callstackTextStartUTF16", BindingFlags.Public | BindingFlags.Instance); + } + + public void StartListening() + { + // Unity 6: Register for console changes + ConsoleWindowUtility.consoleLogsChanged += OnConsoleLogsChanged; + } + + public void StopListening() + { + // Unity 6: Unregister from console changes + ConsoleWindowUtility.consoleLogsChanged -= OnConsoleLogsChanged; + } + + private void OnConsoleLogsChanged() + { + // This is called whenever console logs change: + // - New logs added + // - Console cleared + // - Logs filtered/collapsed + + ConsoleWindowUtility.GetConsoleLogCounts(out int error, out int warning, out int log); + int totalLogs = error + warning + log; + + if (totalLogs == 0) + { + Debug.Log("[MCP Unity] Console cleared detected via Unity 6 API"); + } + + // Since we query Unity directly, we don't need to maintain our own cache + // This event just helps us know when to notify clients that log state changed + } + + public JObject GetLogsAsJson(string logType = "", int offset = 0, int limit = 100, bool includeStackTrace = true) + { + if (_logEntriesType == null || _getCountMethod == null || _getEntryInternalMethod == null) + { + return new JObject + { + ["logs"] = new JArray(), + ["message"] = "LogEntries API not available", + ["success"] = false + }; + } + + JArray logsArray = new JArray(); + + try + { + // Get console log counts using Unity 6 API + ConsoleWindowUtility.GetConsoleLogCounts(out int errorCount, out int warningCount, out int logCount); + int totalCount = errorCount + warningCount + logCount; + + // Map MCP log types to Unity log types + HashSet unityLogTypes = null; + bool filter = !string.IsNullOrEmpty(logType); + if (filter) + { + if (LogTypeMapping.TryGetValue(logType, out var mapped)) + { + unityLogTypes = mapped; + } + else + { + unityLogTypes = new HashSet(StringComparer.OrdinalIgnoreCase) { logType }; + } + } + + // Start getting entries + _startGettingEntriesMethod?.Invoke(null, null); + + int currentCount = (int)_getCountMethod.Invoke(null, null); + var collectedLogs = new List(); + + // Iterate through all logs (newest first) + for (int i = currentCount - 1; i >= 0; i--) + { + // Create LogEntry instance + var logEntry = Activator.CreateInstance(_logEntryType); + + // GetEntryInternal(int row, LogEntry outputEntry) + bool success = (bool)_getEntryInternalMethod.Invoke(null, new object[] { i, logEntry }); + + if (!success) continue; + + // Extract fields (see Unity6InternalAPIReference.md for field details) + string fullMessage = _messageField?.GetValue(logEntry) as string ?? ""; + string file = _fileField?.GetValue(logEntry) as string ?? ""; + int line = _lineField?.GetValue(logEntry) as int? ?? 0; + int mode = _modeField?.GetValue(logEntry) as int? ?? 0; + int callstackStartUTF8 = _callstackTextStartUTF8Field?.GetValue(logEntry) as int? ?? 0; + int callstackStartUTF16 = _callstackTextStartUTF16Field?.GetValue(logEntry) as int? ?? 0; + + // Debug: Write mode values to file for analysis (disabled by default) + #if MCP_UNITY_DEBUG_MODE_VALUES + if (fullMessage.Contains("error") || fullMessage.Contains("Error") || + fullMessage.Contains("warning") || fullMessage.Contains("Warning") || + fullMessage.Contains("failed") || fullMessage.Contains("Failed") || + fullMessage.Contains("exception") || fullMessage.Contains("Exception")) + { + WriteDebugInfo(fullMessage, mode); + } + #endif + + // Parse message and stack trace using Unity's internal callstack position (prefer UTF-16) + var (actualMessage, stackTrace) = ParseMessageAndStackTrace(fullMessage, callstackStartUTF16, callstackStartUTF8); + + // Determine log type from mode and stack trace + string entryType = DetermineLogTypeFromModeAndContent(mode, stackTrace); + + // Skip if filtering and doesn't match + if (filter && !unityLogTypes.Contains(entryType)) + continue; + + // Create log object + var logObject = new JObject + { + ["message"] = actualMessage, + ["type"] = entryType, + ["timestamp"] = DateTime.Now.AddSeconds(-(currentCount - i)).ToString("yyyy-MM-dd HH:mm:ss.fff") + }; + + // Include stack trace if requested + if (includeStackTrace && !string.IsNullOrEmpty(stackTrace)) + { + logObject["stackTrace"] = stackTrace; + } + + collectedLogs.Add(logObject); + } + + // End getting entries + _endGettingEntriesMethod?.Invoke(null, null); + + // Apply pagination + var paginatedLogs = collectedLogs + .Skip(offset) + .Take(limit) + .ToList(); + + foreach (var log in paginatedLogs) + { + logsArray.Add(log); + } + + return new JObject + { + ["logs"] = logsArray, + ["_totalCount"] = totalCount, + ["_filteredCount"] = collectedLogs.Count, + ["_returnedCount"] = paginatedLogs.Count, + ["message"] = $"Retrieved {paginatedLogs.Count} of {collectedLogs.Count} log entries (offset: {offset}, limit: {limit}, total: {totalCount})", + ["success"] = true + }; + } + catch (Exception ex) + { + Debug.LogError($"[MCP Unity] Error getting logs: {ex.Message}"); + return new JObject + { + ["logs"] = new JArray(), + ["message"] = $"Error retrieving logs: {ex.Message}", + ["success"] = false + }; + } + } + + private (string message, string stackTrace) ParseMessageAndStackTrace(string fullMessage, int callstackStartUTF16, int callstackStartUTF8) + { + if (string.IsNullOrEmpty(fullMessage)) + return ("", ""); + + // Try UTF-16 position first (C# strings are UTF-16) + if (callstackStartUTF16 > 0 && callstackStartUTF16 < fullMessage.Length) + { + try + { + string message = fullMessage.Substring(0, callstackStartUTF16).TrimEnd('\n', '\r'); + string stackTrace = fullMessage.Substring(callstackStartUTF16); + return (message, stackTrace); + } + catch + { + // Continue to next attempt + } + } + + // Fallback to UTF-8 position + if (callstackStartUTF8 > 0 && callstackStartUTF8 < fullMessage.Length) + { + try + { + string message = fullMessage.Substring(0, callstackStartUTF8).TrimEnd('\n', '\r'); + string stackTrace = fullMessage.Substring(callstackStartUTF8); + return (message, stackTrace); + } + catch + { + // Continue to heuristic parsing + } + } + + // Fallback: heuristic parsing (previous method) + var lines = fullMessage.Split(new[] { '\n' }, StringSplitOptions.None); + + if (lines.Length == 1) + { + return (fullMessage, ""); + } + + // Find the first line that looks like a stack trace + int stackTraceStartIndex = -1; + for (int i = 1; i < lines.Length; i++) + { + var line = lines[i]; + if (line.StartsWith("UnityEngine.") || + line.StartsWith("System.") || + line.Contains(" (at ") || + line.Contains(":") && (line.Contains("(") && line.Contains(")"))) + { + stackTraceStartIndex = i; + break; + } + } + + if (stackTraceStartIndex == -1) + { + return (fullMessage, ""); + } + + string fallbackMessage = string.Join("\n", lines.Take(stackTraceStartIndex)); + string fallbackStackTrace = string.Join("\n", lines.Skip(stackTraceStartIndex)); + + return (fallbackMessage, fallbackStackTrace); + } + + private string DetermineLogTypeFromModeAndContent(int mode, string stackTrace) + { + // First try to determine from stack trace content + if (!string.IsNullOrEmpty(stackTrace)) + { + if (stackTrace.Contains("UnityEngine.Debug:LogError") || + stackTrace.Contains("UnityEngine.Logger:LogError")) + return "Error"; + + if (stackTrace.Contains("UnityEngine.Debug:LogWarning") || + stackTrace.Contains("UnityEngine.Logger:LogWarning")) + return "Warning"; + + if (stackTrace.Contains("UnityEngine.Debug:LogException") || + stackTrace.Contains("UnityEngine.Logger:LogException")) + return "Exception"; + + if (stackTrace.Contains("UnityEngine.Debug:LogAssertion") || + stackTrace.Contains("UnityEngine.Assertions.Assert")) + return "Assert"; + + if (stackTrace.Contains("UnityEngine.Debug:Log")) + return "Log"; + } + + // Fallback to mode flags + return GetLogTypeFromMode(mode); + } + + private string GetLogTypeFromMode(int mode) + { + // Use centralized mode flags logic + return LogEntryModeFlags.GetLogTypeFromMode(mode); + } + + public void CleanupOldLogs(int keepCount = 500) + { + // Not needed for Unity 6 implementation as we query directly from Unity + } + + public int GetLogCount() + { + ConsoleWindowUtility.GetConsoleLogCounts(out int error, out int warning, out int log); + return error + warning + log; + } + + #if MCP_UNITY_DEBUG_MODE_VALUES + /// + /// Debug method to write mode values to file for analysis + /// Enable by adding MCP_UNITY_DEBUG_MODE_VALUES to Project Settings > Player > Scripting Define Symbols + /// + private void WriteDebugInfo(string message, int mode) + { + try + { + string debugPath = Path.Combine(Application.dataPath, "..", "mcp-unity-debug.log"); + string debugLine = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} | Mode: {mode} (0x{mode:X}) Binary: {Convert.ToString(mode, 2)} | Message: {message.Substring(0, Math.Min(100, message.Length))}...\n"; + File.AppendAllText(debugPath, debugLine); + } + catch + { + // Ignore debug write errors + } + } + #endif + } +} \ No newline at end of file diff --git a/Editor/Services/ConsoleLogsServiceUnity6.cs.meta b/Editor/Services/ConsoleLogsServiceUnity6.cs.meta new file mode 100644 index 0000000..a9326b0 --- /dev/null +++ b/Editor/Services/ConsoleLogsServiceUnity6.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4534e9a288ddd411891ebe758d3bb332 \ No newline at end of file diff --git a/Editor/Services/LogEntryModeFlags.cs b/Editor/Services/LogEntryModeFlags.cs new file mode 100644 index 0000000..ea3fc1d --- /dev/null +++ b/Editor/Services/LogEntryModeFlags.cs @@ -0,0 +1,60 @@ +namespace McpUnity.Services +{ + /// + /// Unity LogEntry mode flags constants + /// Based on Unity's internal LogMessageFlags from UnityCsReference + /// https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/LogEntries.bindings.cs + /// + public static class LogEntryModeFlags + { + // Basic log type flags (bits 0-4) + public const int kModeError = 1 << 0; // 1 (0x1) + public const int kModeAssert = 1 << 1; // 2 (0x2) + public const int kModeLog = 1 << 2; // 4 (0x4) + public const int kModeWarning = 1 << 3; // 8 (0x8) + public const int kModeException = 1 << 4; // 16 (0x10) + + // Scripting related flags (bits 8-12) + public const int kScriptingError = 1 << 8; // 256 (0x100) + public const int kScriptingWarning = 1 << 9; // 512 (0x200) + public const int kScriptingLog = 1 << 10; // 1024 (0x400) + public const int kScriptCompileError = 1 << 11; // 2048 (0x800) + public const int kScriptCompileWarning = 1 << 12; // 4096 (0x1000) + + // Observed composite values from debugging + // These include additional undocumented high bits + public const int ObservedCompilerWarning = 266240; // 0x41000 (bits: 18, 10) + public const int ObservedCompilerError = 272384; // 0x42800 (bits: 18, 14, 10) + public const int ObservedShaderError = 262212; // 0x40044 (bits: 18, 6, 2) + public const int ObservedRuntimeWarning = 8405504; // 0x804200 (bits: 23, 18, 9) + public const int ObservedRuntimeError = 8405248; // 0x804100 (bits: 23, 18, 8) + + /// + /// Determine log type from mode flags + /// + public static string GetLogTypeFromMode(int mode) + { + // Check for observed compiler/shader message patterns first + if (mode == ObservedCompilerError) return "Error"; + if (mode == ObservedCompilerWarning) return "Warning"; + if (mode == ObservedShaderError) return "Error"; + + // Check for script compile errors/warnings + if ((mode & kScriptCompileError) != 0) return "Error"; + if ((mode & kScriptCompileWarning) != 0) return "Warning"; + + // Check for scripting errors/warnings + if ((mode & kScriptingError) != 0) return "Error"; + if ((mode & kScriptingWarning) != 0) return "Warning"; + + // Then check standard flags + if ((mode & kModeError) != 0) return "Error"; + if ((mode & kModeAssert) != 0) return "Assert"; + if ((mode & kModeException) != 0) return "Exception"; + if ((mode & kModeWarning) != 0) return "Warning"; + if ((mode & kModeLog) != 0) return "Log"; + + return "Log"; // Default to Log instead of Unknown + } + } +} \ No newline at end of file diff --git a/Editor/Services/LogEntryModeFlags.cs.meta b/Editor/Services/LogEntryModeFlags.cs.meta new file mode 100644 index 0000000..62d5b16 --- /dev/null +++ b/Editor/Services/LogEntryModeFlags.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a2b3c4d5e6f7890123456789abcdef01 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: \ No newline at end of file diff --git a/Editor/Services/Unity6InternalAPIReference.md b/Editor/Services/Unity6InternalAPIReference.md new file mode 100644 index 0000000..2fa665d --- /dev/null +++ b/Editor/Services/Unity6InternalAPIReference.md @@ -0,0 +1,84 @@ +# Unity 6 Internal API Reference + +## LogEntry Structure + +Unity's internal `LogEntry` structure contains the following fields: + +### Fields +| Field Name | Type | Description | +|------------|------|-------------| +| message | String | Full message including stack trace | +| file | String | Source file path | +| line | Int32 | Line number | +| column | Int32 | Column number (-1 if not available) | +| mode | Int32 | Log type flags (see Mode Flags section) | +| instanceID | Int32 | Instance ID of the object | +| identifier | Int32 | Unique identifier | +| globalLineIndex | Int32 | Global line index in console | +| callstackTextStartUTF8 | Int32 | UTF-8 byte position where stack trace starts | +| callstackTextStartUTF16 | Int32 | UTF-16 character position where stack trace starts | + +## Mode Flags + +### Standard Unity Log Flags (from UnityCsReference) +```csharp +// Basic log types +const int kModeError = 1 << 0; // 1 (0x1) +const int kModeAssert = 1 << 1; // 2 (0x2) +const int kModeLog = 1 << 2; // 4 (0x4) +const int kModeWarning = 1 << 3; // 8 (0x8) +const int kModeException = 1 << 4; // 16 (0x10) + +// Scripting related flags +const int kScriptingError = 1 << 8; // 256 (0x100) +const int kScriptingWarning = 1 << 9; // 512 (0x200) +const int kScriptingLog = 1 << 10; // 1024 (0x400) +const int kScriptCompileError = 1 << 11; // 2048 (0x800) +const int kScriptCompileWarning = 1 << 12; // 4096 (0x1000) +``` + +### Observed Compiler/Shader Message Values (from debugging) +```csharp +// These are composite values with multiple flags set +const int ObservedCompilerWarning = 266240; // 0x41000 (bits: 18, 10) +const int ObservedCompilerError = 272384; // 0x42800 (bits: 18, 14, 10) +const int ObservedShaderError = 262212; // 0x40044 (bits: 18, 6, 2) +``` + +Note: The observed values include additional high bits (2, 6, 10, 14, 18) beyond the documented flags, suggesting Unity may be using undocumented internal flags. + +## Important Findings + +1. **Compiler messages use special mode values** + - C# compilation errors (e.g., `error CS0103`) have mode = 272384 + - C# compilation warnings (e.g., `warning CS0414`) have mode = 266240 + - These do NOT use the standard error/warning flags! + +2. **Shader errors** + - Shader compilation errors appear to use standard Log mode (4) + - Need message content analysis to properly classify + +3. **Stack trace separation** + - Use `callstackTextStartUTF16` for C# strings (preferred) + - Fallback to `callstackTextStartUTF8` if needed + - Unity provides exact position where stack trace begins + +## Usage Notes + +- Always check special compiler flags before standard flags +- Message content analysis may still be needed for some error types +- The mode field alone is not sufficient for all classification needs + +## Example Mode Analysis + +``` +Warning CS0414: mode = 266240 = 0x41000 +Binary: 1000001000000000000 +Bits set: 18, 10 + +Error CS0103: mode = 272384 = 0x42800 +Binary: 1000010100000000000 +Bits set: 18, 14, 10 +``` + +The high bits (10, 14, 18) appear to indicate compiler-related messages. \ No newline at end of file diff --git a/Editor/Services/Unity6InternalAPIReference.md.meta b/Editor/Services/Unity6InternalAPIReference.md.meta new file mode 100644 index 0000000..2b7e18e --- /dev/null +++ b/Editor/Services/Unity6InternalAPIReference.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9f8e7d6c5b4a3214fa9b8c7d6e5f4321 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: \ No newline at end of file diff --git a/Editor/UnityBridge/McpUnityEditorWindow.cs b/Editor/UnityBridge/McpUnityEditorWindow.cs index 70c1eed..1a94cbd 100644 --- a/Editor/UnityBridge/McpUnityEditorWindow.cs +++ b/Editor/UnityBridge/McpUnityEditorWindow.cs @@ -143,6 +143,30 @@ private void DrawServerTab() settings.SaveSettings(); } + EditorGUILayout.Space(); + + // Console log service selection + ConsoleLogServiceType newConsoleLogService = (ConsoleLogServiceType)EditorGUILayout.EnumPopup( + new GUIContent("Console Log Service", "Select console log service implementation. EventBased is safe but may miss some logs. Unity6Enhanced uses internal APIs for better reliability but requires Unity 6+. Server restart required when changed."), + settings.ConsoleLogService); + if (newConsoleLogService != settings.ConsoleLogService) + { + settings.ConsoleLogService = newConsoleLogService; + settings.SaveSettings(); + + // Show warning for Unity6Enhanced + if (newConsoleLogService == ConsoleLogServiceType.Unity6Enhanced) + { +#if UNITY_6000_0_OR_NEWER + EditorUtility.DisplayDialog("Console Log Service Changed", + "Unity 6 enhanced console log service selected. This uses internal Unity APIs for better reliability but may break in future Unity versions.\n\nRestart the MCP server for changes to take effect.", "OK"); +#else + EditorUtility.DisplayDialog("Console Log Service Warning", + $"Unity 6 enhanced console log service selected but current Unity version is {Application.unityVersion}. The service will fall back to event-based implementation.\n\nRestart the MCP server for changes to take effect.", "OK"); +#endif + } + } + EditorGUILayout.Space(); // Server control buttons diff --git a/Editor/UnityBridge/McpUnityServer.cs b/Editor/UnityBridge/McpUnityServer.cs index dfabf2b..b059500 100644 --- a/Editor/UnityBridge/McpUnityServer.cs +++ b/Editor/UnityBridge/McpUnityServer.cs @@ -28,7 +28,7 @@ public class McpUnityServer private WebSocketServer _webSocketServer; private CancellationTokenSource _cts; private TestRunnerService _testRunnerService; - private ConsoleLogsService _consoleLogsService; + private IConsoleLogsService _consoleLogsService; /// /// Static constructor that gets called when Unity loads due to InitializeOnLoad attribute @@ -249,8 +249,26 @@ private void InitializeServices() // Initialize the test runner service _testRunnerService = new TestRunnerService(); - // Initialize the console logs service - _consoleLogsService = new ConsoleLogsService(); + // Initialize the console logs service based on settings and Unity version + // Default to safe event-based implementation + var consoleLogService = McpUnitySettings.Instance.ConsoleLogService; + + if (consoleLogService == ConsoleLogServiceType.Unity6Enhanced) + { +#if UNITY_6000_0_OR_NEWER + _consoleLogsService = new ConsoleLogsServiceUnity6(); + McpLogger.LogInfo($"[MCP Unity] Console Log Service: Unity 6 Enhanced (experimental, uses internal APIs)"); +#else + McpLogger.LogWarning($"[MCP Unity] Console Log Service: Unity 6 Enhanced requested but Unity version is {Application.unityVersion}. Falling back to Event-Based service."); + _consoleLogsService = new ConsoleLogsService(); +#endif + } + else + { + // Default to safe event-based implementation + _consoleLogsService = new ConsoleLogsService(); + McpLogger.LogInfo($"[MCP Unity] Console Log Service: Event-Based (default, safe)"); + } } } } diff --git a/Editor/UnityBridge/McpUnitySettings.cs b/Editor/UnityBridge/McpUnitySettings.cs index 1720fa1..676a657 100644 --- a/Editor/UnityBridge/McpUnitySettings.cs +++ b/Editor/UnityBridge/McpUnitySettings.cs @@ -6,6 +6,18 @@ namespace McpUnity.Unity { + /// + /// Console log service implementation options + /// + public enum ConsoleLogServiceType + { + [Tooltip("Event-based implementation (default, safe) - may occasionally miss logs")] + EventBased, + + [Tooltip("Unity 6 enhanced implementation (experimental) - uses internal APIs for better reliability")] + Unity6Enhanced + } + /// /// Handles persistence of MCP Unity settings /// @@ -36,6 +48,9 @@ public class McpUnitySettings [Tooltip("Optional: Full path to the npm executable (e.g., /Users/user/.asdf/shims/npm or C:\\path\\to\\npm.cmd). If not set, 'npm' from the system PATH will be used.")] public string NpmExecutablePath = string.Empty; + + [Tooltip("Console log service implementation to use. EventBased (default, safe) may occasionally miss logs. Unity6Enhanced uses internal APIs for better reliability but requires Unity 6+ and should only be used when explicitly needed. Server restart required when changed.")] + public ConsoleLogServiceType ConsoleLogService = ConsoleLogServiceType.EventBased; /// /// Singleton instance of settings