fix: deliver WebSocket messages while Editor is unfocused (update-drained queue instead of delayCall)#151
Open
Feetschaa wants to merge 1 commit into
Conversation
…delayCall McpUnitySocketHandler.OnMessage marshalled each request onto the main thread with `EditorApplication.delayCall += ...` from the WebSocketSharp background thread. delayCall is a plain static delegate; a `+=` from a foreign thread is not reliably observed or drained by the main thread while the Editor is idle in the background. As a result, requests that arrived while Unity was not the foreground application were never executed and the MCP client timed out (a common complaint when driving Unity from an AI client in another window). Add McpMainThreadDispatcher: a ConcurrentQueue<Action> drained from EditorApplication.update (which keeps firing in the background), and enqueue the handler into it instead of using delayCall. No cross-thread delegate mutation, so requests are delivered regardless of Editor focus. Note: for delivery while fully backgrounded on macOS, the editor loop must keep ticking - disabling App Nap for the Editor process helps (`defaults write com.unity3d.UnityEditor5.x NSAppSleepDisabled -bool YES`).
apantin
added a commit
to secondmapstudio/mcp-unity
that referenced
this pull request
Jul 20, 2026
Track the temporary fork + embedded install procedure (PR CoderGamester#151 unfocused fix).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
MCP tool calls time out whenever the Unity Editor is not the foreground application, and start working again the moment the user clicks back into Unity. This is a common failure mode when driving Unity from an AI client (Claude Code, Cursor, …) running in another window.
Root cause
McpUnitySocketHandler.OnMessagemarshals every request onto the Unity main thread with:OnMessageruns on the WebSocketSharp background thread.EditorApplication.delayCallis a plainstaticmulticast delegate, so this+=is a cross-thread read-modify-write, and — more importantly — the main thread's draining ofdelayCalldoes not reliably observe a handler added from another thread while the Editor is idle in the background. When Unity has focus there is enough main-thread churn that it eventually fires; unfocused, the queued handler is never invoked and the client times out.How it was confirmed
EditorApplication.updateprovesupdatekeeps firing while the Editor is backgrounded (~1 Hz on macOS with App Nap disabled).ws://localhost:8090/McpUnitywhile unfocused received no response with thedelayCalldispatch, and an immediate response once dispatch was switched to a queue drained fromEditorApplication.update.Fix
Add
McpMainThreadDispatcher— aConcurrentQueue<Action>drained fromEditorApplication.update— and enqueue the handler into it instead of usingdelayCall. No cross-thread delegate mutation; requests are delivered regardless of Editor focus. Behavior when focused is unchanged.Notes / scope
McpUnitySocketHandler.cs(one new internal helper + the one dispatch line). The server-start path still usesdelayCall/updateas before.defaults write com.unity3d.UnityEditor5.x NSAppSleepDisabled -bool YES(bundle id varies by Unity major version).Testing
get_play_mode_statusand all other tools time out while another app is focused.Relationship to #147 and #150
Addresses #147 ("Unity is required to be in focus").
This is complementary to, and different from, #150:
SetTimer→QueuePlayerLoopUpdate()every 100 ms). It addresses the loop being parked.EditorApplication.delayCall +=and instead drains a thread-safe queue fromEditorApplication.update. Platform-independent, no native code.They stack. Importantly, on macOS keeping the loop ticking alone was not sufficient in my testing: with
EditorApplication.QueuePlayerLoopUpdate()pumped every editor tick (i.e. the loop demonstrably ticking,updatefiring at ~1 Hz in the background),delayCall-dispatched requests still never ran and timed out — they only started working after switching dispatch to the update-drained queue in this PR. So this change is needed beyond simply un-parking the loop, and it also removes a latent cross-thread-delegate race on every platform.For full background delivery on macOS the editor process must also not be suspended by App Nap (
defaults write com.unity3d.UnityEditor5.x NSAppSleepDisabled -bool YES), as noted above.