Environment
- unity-mcp version: 9.6.6 (via Unity Package Manager)
- Unity: 6000.4.2f1
- Host OS: macOS 26.3.1 (Apple M1 Pro)
- Runtime: Mono 6.13.0
- Transport: HTTP Local
What happens
McpLogRecord.get_IsEnabled calls EditorPrefs.GetBool(...) directly. When a log record fires from a TransportCommandDispatcher async continuation (ThreadPool), EditorPrefs.GetBool throws UnityException: GetBool can only be called from the main thread.
Reproducible in any project that routes MCP commands at a reasonable rate — I've captured 24 occurrences over 4 days in my project.
Observed stack
UnityEngine.UnityException: GetBool can only be called from the main thread.
at Execute (System.Threading.Tasks.Task)
at InnerInvoke (System.Threading.Tasks.ContinuationTaskFromResultTask`1)
at ProcessCommand { } (Editor/Services/Transport/TransportCommandDispatcher.cs:397)
at Log (Editor/Helpers/McpLogRecord.cs:28)
at get_IsEnabled (Editor/Helpers/McpLogRecord.cs:22)
at GetBool (UnityEditor.EditorPrefs)
Wrapped as System.AggregateException via UnobservedTaskException mechanism because the continuation is on a ThreadPool worker.
Likely fix
Cache the IsEnabled value on the main thread (e.g. in a [InitializeOnLoad] static constructor) and refresh on EditorApplication.playModeStateChanged or AssemblyReloadEvents.afterAssemblyReload. Or gate the EditorPrefs.GetBool call behind a main-thread dispatcher / cached InternalEditorUtility.isApplicationActive-style approach. Anything that avoids calling EditorPrefs.* from ThreadPool.
Why it matters
- Every event gets captured by Sentry (or whichever crash reporter the project uses) as an unhandled task exception
- Creates dashboard noise for projects that integrate Sentry (the issue ID in my project: FARMZ-TD-5, 24 events in 4 days)
unity.is_main_thread: false tag confirms the thread context
Happy to PR
If a PR against Editor/Helpers/McpLogRecord.cs caching the enabled flag would be welcome, I can submit one.
Ref: https://github.com/CoplayDev/unity-mcp/blob/main/Editor/Helpers/McpLogRecord.cs#L22
Environment
What happens
McpLogRecord.get_IsEnabledcallsEditorPrefs.GetBool(...)directly. When a log record fires from a TransportCommandDispatcher async continuation (ThreadPool),EditorPrefs.GetBoolthrowsUnityException: GetBool can only be called from the main thread.Reproducible in any project that routes MCP commands at a reasonable rate — I've captured 24 occurrences over 4 days in my project.
Observed stack
UnityEngine.UnityException: GetBool can only be called from the main thread.
at Execute (System.Threading.Tasks.Task)
at InnerInvoke (System.Threading.Tasks.ContinuationTaskFromResultTask`1)
at ProcessCommand { } (Editor/Services/Transport/TransportCommandDispatcher.cs:397)
at Log (Editor/Helpers/McpLogRecord.cs:28)
at get_IsEnabled (Editor/Helpers/McpLogRecord.cs:22)
at GetBool (UnityEditor.EditorPrefs)
Wrapped as
System.AggregateExceptionviaUnobservedTaskExceptionmechanism because the continuation is on a ThreadPool worker.Likely fix
Cache the
IsEnabledvalue on the main thread (e.g. in a[InitializeOnLoad]static constructor) and refresh onEditorApplication.playModeStateChangedorAssemblyReloadEvents.afterAssemblyReload. Or gate theEditorPrefs.GetBoolcall behind a main-thread dispatcher / cachedInternalEditorUtility.isApplicationActive-style approach. Anything that avoids callingEditorPrefs.*from ThreadPool.Why it matters
unity.is_main_thread: falsetag confirms the thread contextHappy to PR
If a PR against
Editor/Helpers/McpLogRecord.cscaching the enabled flag would be welcome, I can submit one.Ref: https://github.com/CoplayDev/unity-mcp/blob/main/Editor/Helpers/McpLogRecord.cs#L22