From 04553da06e3f32f8d16c7adb29352a8531a5fec0 Mon Sep 17 00:00:00 2001 From: David Sarno Date: Mon, 16 Feb 2026 11:56:19 -0800 Subject: [PATCH 1/3] Fix read_console reflection crash on Unity 6.5 and batch-mode test crash (#761) - Remove unused LogEntry.instanceID reflection that fatally blocked read_console initialization on Unity 6000.5.0a6 - Skip Collider.GeometryHolder in GameObjectSerializer to prevent native PhysX SEGV in batch-mode test runs - Fix CodexConfigHelperTests to work with both release and prerelease package versions Co-Authored-By: Claude Opus 4.6 --- .../Editor/Helpers/GameObjectSerializer.cs | 9 +- MCPForUnity/Editor/Tools/ReadConsole.cs | 10 +-- .../Helpers/CodexConfigHelperTests.cs | 83 +++++++------------ 3 files changed, 40 insertions(+), 62 deletions(-) diff --git a/MCPForUnity/Editor/Helpers/GameObjectSerializer.cs b/MCPForUnity/Editor/Helpers/GameObjectSerializer.cs index 688546b7d..da24771d6 100644 --- a/MCPForUnity/Editor/Helpers/GameObjectSerializer.cs +++ b/MCPForUnity/Editor/Helpers/GameObjectSerializer.cs @@ -450,11 +450,18 @@ public static object GetComponentData(Component c, bool includeNonPublicSerializ propName == "worldToLocalMatrix" || propName == "localToWorldMatrix")) { - // McpLog.Info($"[GetComponentData] Explicitly skipping Transform property: {propName}"); skipProperty = true; } // --- End Skip Transform Properties --- + // --- Skip Collider properties that cause native crashes via PhysX --- + if (typeof(Collider).IsAssignableFrom(componentType) && + propName == "GeometryHolder") + { + skipProperty = true; + } + // --- End Skip Collider Properties --- + // Skip if flagged if (skipProperty) { diff --git a/MCPForUnity/Editor/Tools/ReadConsole.cs b/MCPForUnity/Editor/Tools/ReadConsole.cs index 342f7b1fc..c6a6ee683 100644 --- a/MCPForUnity/Editor/Tools/ReadConsole.cs +++ b/MCPForUnity/Editor/Tools/ReadConsole.cs @@ -30,8 +30,6 @@ public static class ReadConsole private static FieldInfo _messageField; private static FieldInfo _fileField; private static FieldInfo _lineField; - private static FieldInfo _instanceIdField; - // Note: Timestamp is not directly available in LogEntry; need to parse message or find alternative? // Static constructor for reflection setup @@ -102,10 +100,6 @@ static ReadConsole() if (_lineField == null) throw new Exception("Failed to reflect LogEntry.line"); - _instanceIdField = logEntryType.GetField("instanceID", instanceFlags); - if (_instanceIdField == null) - throw new Exception("Failed to reflect LogEntry.instanceID"); - // (Calibration removed) } @@ -121,7 +115,7 @@ static ReadConsole() _getCountMethod = _getEntryMethod = null; - _modeField = _messageField = _fileField = _lineField = _instanceIdField = null; + _modeField = _messageField = _fileField = _lineField = null; } } @@ -140,7 +134,6 @@ public static object HandleCommand(JObject @params) || _messageField == null || _fileField == null || _lineField == null - || _instanceIdField == null ) { // Log the error here as well for easier debugging in Unity Console @@ -290,7 +283,6 @@ bool includeStacktrace string file = (string)_fileField.GetValue(logEntryInstance); int line = (int)_lineField.GetValue(logEntryInstance); - // int instanceId = (int)_instanceIdField.GetValue(logEntryInstance); if (string.IsNullOrEmpty(message)) { diff --git a/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/CodexConfigHelperTests.cs b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/CodexConfigHelperTests.cs index 0ffda8643..32a5aef37 100644 --- a/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/CodexConfigHelperTests.cs +++ b/TestProjects/UnityMCPTests/Assets/Tests/EditMode/Helpers/CodexConfigHelperTests.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using MCPForUnity.Editor.Helpers; using MCPForUnity.External.Tommy; @@ -10,6 +12,31 @@ namespace MCPForUnityTests.Editor.Helpers { public class CodexConfigHelperTests { + /// + /// Validates that a TOML args array contains the expected uvx structure: + /// --from, a mcpforunityserver reference, mcp-for-unity package name, + /// and optionally --prerelease/explicit (only for prerelease builds). + /// + private static void AssertValidUvxArgs(TomlArray args) + { + var argValues = new List(); + foreach (TomlNode child in args.Children) + argValues.Add((child as TomlString).Value); + + Assert.IsTrue(argValues.Contains("--from"), "Args should contain --from"); + Assert.IsTrue(argValues.Any(a => a.Contains("mcpforunityserver")), "Args should contain PyPI package reference"); + Assert.IsTrue(argValues.Contains("mcp-for-unity"), "Args should contain package name"); + + // Prerelease builds include --prerelease explicit before --from + int fromIndex = argValues.IndexOf("--from"); + int prereleaseIndex = argValues.IndexOf("--prerelease"); + if (prereleaseIndex >= 0) + { + Assert.IsTrue(prereleaseIndex < fromIndex, "--prerelease should come before --from"); + Assert.AreEqual("explicit", argValues[prereleaseIndex + 1], "--prerelease should be followed by explicit"); + } + } + /// /// Mock platform service for testing /// @@ -244,19 +271,7 @@ public void BuildCodexServerBlock_OnWindows_IncludesSystemRootEnv() // Verify args contains the proper uvx command structure var args = argsNode as TomlArray; - Assert.IsTrue(args.ChildrenCount >= 5, "Args should contain --prerelease, explicit, --from, PyPI package reference, and package name"); - - var firstArg = (args[0] as TomlString).Value; - var secondArg = (args[1] as TomlString).Value; - var thirdArg = (args[2] as TomlString).Value; - var fourthArg = (args[3] as TomlString).Value; - var fifthArg = (args[4] as TomlString).Value; - - Assert.AreEqual("--prerelease", firstArg, "First arg should be --prerelease"); - Assert.AreEqual("explicit", secondArg, "Second arg should be explicit"); - Assert.AreEqual("--from", thirdArg, "Third arg should be --from"); - Assert.IsTrue(fourthArg.Contains("mcpforunityserver"), "Fourth arg should be PyPI package reference"); - Assert.AreEqual("mcp-for-unity", fifthArg, "Fifth arg should be mcp-for-unity"); + AssertValidUvxArgs(args); // Verify env.SystemRoot is present on Windows bool hasEnv = unityMcp.TryGetNode("env", out var envNode); @@ -313,19 +328,7 @@ public void BuildCodexServerBlock_OnNonWindows_ExcludesEnv() // Verify args contains the proper uvx command structure var args = argsNode as TomlArray; - Assert.IsTrue(args.ChildrenCount >= 5, "Args should contain --prerelease, explicit, --from, PyPI package reference, and package name"); - - var firstArg = (args[0] as TomlString).Value; - var secondArg = (args[1] as TomlString).Value; - var thirdArg = (args[2] as TomlString).Value; - var fourthArg = (args[3] as TomlString).Value; - var fifthArg = (args[4] as TomlString).Value; - - Assert.AreEqual("--prerelease", firstArg, "First arg should be --prerelease"); - Assert.AreEqual("explicit", secondArg, "Second arg should be explicit"); - Assert.AreEqual("--from", thirdArg, "Third arg should be --from"); - Assert.IsTrue(fourthArg.Contains("mcpforunityserver"), "Fourth arg should be PyPI package reference"); - Assert.AreEqual("mcp-for-unity", fifthArg, "Fifth arg should be mcp-for-unity"); + AssertValidUvxArgs(args); // Verify env is NOT present on non-Windows platforms bool hasEnv = unityMcp.TryGetNode("env", out _); @@ -384,19 +387,7 @@ public void UpsertCodexServerBlock_OnWindows_IncludesSystemRootEnv() // Verify args contains the proper uvx command structure var args = argsNode as TomlArray; - Assert.IsTrue(args.ChildrenCount >= 5, "Args should contain --prerelease, explicit, --from, PyPI package reference, and package name"); - - var firstArg = (args[0] as TomlString).Value; - var secondArg = (args[1] as TomlString).Value; - var thirdArg = (args[2] as TomlString).Value; - var fourthArg = (args[3] as TomlString).Value; - var fifthArg = (args[4] as TomlString).Value; - - Assert.AreEqual("--prerelease", firstArg, "First arg should be --prerelease"); - Assert.AreEqual("explicit", secondArg, "Second arg should be explicit"); - Assert.AreEqual("--from", thirdArg, "Third arg should be --from"); - Assert.IsTrue(fourthArg.Contains("mcpforunityserver"), "Fourth arg should be PyPI package reference"); - Assert.AreEqual("mcp-for-unity", fifthArg, "Fifth arg should be mcp-for-unity"); + AssertValidUvxArgs(args); // Verify env.SystemRoot is present on Windows bool hasEnv = unityMcp.TryGetNode("env", out var envNode); @@ -462,19 +453,7 @@ public void UpsertCodexServerBlock_OnNonWindows_ExcludesEnv() // Verify args contains the proper uvx command structure var args = argsNode as TomlArray; - Assert.IsTrue(args.ChildrenCount >= 5, "Args should contain --prerelease, explicit, --from, PyPI package reference, and package name"); - - var firstArg = (args[0] as TomlString).Value; - var secondArg = (args[1] as TomlString).Value; - var thirdArg = (args[2] as TomlString).Value; - var fourthArg = (args[3] as TomlString).Value; - var fifthArg = (args[4] as TomlString).Value; - - Assert.AreEqual("--prerelease", firstArg, "First arg should be --prerelease"); - Assert.AreEqual("explicit", secondArg, "Second arg should be explicit"); - Assert.AreEqual("--from", thirdArg, "Third arg should be --from"); - Assert.IsTrue(fourthArg.Contains("mcpforunityserver"), "Fourth arg should be PyPI package reference"); - Assert.AreEqual("mcp-for-unity", fifthArg, "Fifth arg should be mcp-for-unity"); + AssertValidUvxArgs(args); // Verify env is NOT present on non-Windows platforms bool hasEnv = unityMcp.TryGetNode("env", out _); From 46dd236658e86e7195a8a5b4105c860cff7c8340 Mon Sep 17 00:00:00 2001 From: David Sarno Date: Mon, 16 Feb 2026 12:02:47 -0800 Subject: [PATCH 2/3] Add com.unity.test-framework as package dependency The package has a hard compile dependency on Test Framework (RunTests, TestJobManager, TestRunnerService, etc.) but did not declare it in package.json. Projects without Test Framework installed would get CS0234/CS0246 errors on import. Co-Authored-By: Claude Opus 4.6 --- MCPForUnity/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MCPForUnity/package.json b/MCPForUnity/package.json index c1de0a072..fa0bfe34a 100644 --- a/MCPForUnity/package.json +++ b/MCPForUnity/package.json @@ -7,7 +7,8 @@ "documentationUrl": "https://github.com/CoplayDev/unity-mcp", "licensesUrl": "https://github.com/CoplayDev/unity-mcp/blob/main/LICENSE", "dependencies": { - "com.unity.nuget.newtonsoft-json": "3.0.2" + "com.unity.nuget.newtonsoft-json": "3.0.2", + "com.unity.test-framework": "1.1.31" }, "keywords": [ "unity", From 3f95d6ca43f15c4e0a9fc9efcb740d8d126656a4 Mon Sep 17 00:00:00 2001 From: David Sarno Date: Mon, 16 Feb 2026 13:03:16 -0800 Subject: [PATCH 3/3] Address review: dispose SerializedObject, fix null stringValue, remove dead method - Wrap SerializedObject in using block in SetViaSerializedProperty - Use string.Empty instead of null for SerializedProperty.stringValue - Remove unused GetRemappedTypeForFiltering from ReadConsole Co-Authored-By: Claude Opus 4.6 --- MCPForUnity/Editor/Helpers/ComponentOps.cs | 4 ++-- MCPForUnity/Editor/Tools/ReadConsole.cs | 23 ---------------------- 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/MCPForUnity/Editor/Helpers/ComponentOps.cs b/MCPForUnity/Editor/Helpers/ComponentOps.cs index 9f20330bf..33a911a1d 100644 --- a/MCPForUnity/Editor/Helpers/ComponentOps.cs +++ b/MCPForUnity/Editor/Helpers/ComponentOps.cs @@ -412,7 +412,7 @@ private static Type ResolveMemberType(Type componentType, string propertyName, s private static bool SetViaSerializedProperty(Component component, string propertyName, string normalizedName, JToken value, out string error) { error = null; - var so = new SerializedObject(component); + using var so = new SerializedObject(component); SerializedProperty prop = so.FindProperty(propertyName) ?? so.FindProperty(normalizedName); @@ -515,7 +515,7 @@ private static bool SetSerializedPropertyRecursive(SerializedProperty prop, JTok return true; case SerializedPropertyType.String: - prop.stringValue = value == null || value.Type == JTokenType.Null ? null : value.ToString(); + prop.stringValue = value == null || value.Type == JTokenType.Null ? string.Empty : value.ToString(); return true; case SerializedPropertyType.Enum: diff --git a/MCPForUnity/Editor/Tools/ReadConsole.cs b/MCPForUnity/Editor/Tools/ReadConsole.cs index c6a6ee683..100938f33 100644 --- a/MCPForUnity/Editor/Tools/ReadConsole.cs +++ b/MCPForUnity/Editor/Tools/ReadConsole.cs @@ -507,29 +507,6 @@ private static bool IsExplicitDebugLog(string fullMessage) return false; } - /// - /// Applies the "one level lower" remapping for filtering, like the old version. - /// This ensures compatibility with the filtering logic that expects remapped types. - /// - private static LogType GetRemappedTypeForFiltering(LogType unityType) - { - switch (unityType) - { - case LogType.Error: - return LogType.Warning; // Error becomes Warning - case LogType.Warning: - return LogType.Log; // Warning becomes Log - case LogType.Assert: - return LogType.Assert; // Assert remains Assert - case LogType.Log: - return LogType.Log; // Log remains Log - case LogType.Exception: - return LogType.Warning; // Exception becomes Warning - default: - return LogType.Log; // Default fallback - } - } - /// /// Attempts to extract the stack trace part from a log message. /// Unity log messages often have the stack trace appended after the main message,