How the MCP Server Handles Unity Domain Reload and Script Compilation

The MCP server treats domain reload disconnects as expected events, implements automatic reconnection with a 20-second timeout, and persists job state to survive Unity's scripting domain teardown.

When Unity recompiles scripts or reloads its scripting domain, the entire Editor process restarts the internal C# runtime, wiping static state and closing every TCP socket. The MCP server (the Python side of the bridge in the CoplayDev/unity-mcp repository) must survive this abrupt connection loss, re-establish a fresh session, and continue serving the same logical client without losing pending work.

Understanding Unity Domain Reload Impact

A domain reload occurs whenever Unity recompiles scripts, imports assets, or explicitly resets its scripting environment. This process destroys all static variables, closes active network connections, and resets the Unity Editor API. For the MCP bridge, this means the Python server loses its TCP socket or WebSocket connection to Unity instantly.

The server handles this not as a fatal error, but as a routine operational condition. Both the legacy TCP bridge in Server/src/transport/legacy/unity_connection.py (around line 134) and the WebSocket hub in Server/src/transport/plugin_hub.py (around line 833) explicitly recognize this disconnection pattern. By treating the closed socket as expected rather than exceptional, the server avoids crashing and enters recovery mode immediately.

Three-Layer Resilience Strategy

The MCP server implements three coordinated mechanisms to maintain continuity across domain reloads.

Graceful TCP and WebSocket Teardown

When Unity initiates a reload, every open socket closes. The transport layer detects this condition immediately. In unity_connection.py, the connection logic monitors for socket closure specifically during the reload window. Similarly, plugin_hub.py handles the WebSocket disconnect without raising fatal exceptions. This graceful teardown prevents resource leaks and leaves the server in a clean state ready for reconnection.

Automatic Reconnection with Back-Off

After detecting a disconnect, the Python side enters a retry loop with a capped wait time of 20 seconds. This window covers the typical 10–20 second compilation period for scripts and assets. The reconnection logic appears in unity_connection.py (lines 815–819) and plugin_hub.py (lines 846–849), where the server attempts to reconnect with exponential back-off until the timeout expires.

async def ensure_connection():
    timeout = time.time() + 20   # 20-second retry window

    while not unity_bridge.is_connected():
        try:
            await unity_bridge.connect()
        except ConnectionError:
            await asyncio.sleep(0.5)   # back-off

        if time.time() > timeout:
            raise RuntimeError("Failed to reconnect after domain reload")

State Persistence Across Reloads

Any state that must survive a reload is stored in Unity's SessionState or on disk. Tools that perform long-running work (such as RefreshUnity, RunTests, or custom user tools) persist their progress via McpJobStateStore. This mechanism, documented in website/docs/guides/custom-tools.md (line 207), ensures that when the connection re-establishes, the server can resume the previous job rather than starting over.

// C# helper used by long-running tools

McpJobStateStore.Save(jobId, jobState);   // writes into Library/

Script Compilation Workflow

When editing scripts through MCP tools like manage_script or script_apply_edits, the server follows a specific pattern to handle the inevitable domain reload.

Detecting Compilation Start

The client polls the editor_state resource to monitor the isCompiling flag. This allows the server to know exactly when Unity enters the compilation phase. The documentation in website/docs/reference/tools/core/manage_script.md (line 87) outlines this polling pattern.


# Python side – typical pattern used by many tools

while True:
    editor_state = await read_resource("editor_state")
    if not editor_state["isCompiling"]:
        break
    await asyncio.sleep(0.2)   # brief poll interval

Waiting for Domain Reload

Tools do not assume the connection remains alive during compilation. Instead, they rely on the reconnection logic described above. The RefreshUnity tool explicitly tolerates disconnects during compile requests, logging them as expected events rather than errors (see Server/src/services/tools/refresh_unity.py, lines 78–84, and the C# counterpart RefreshUnity.cs).

Verifying Success Post-Reload

After the reload completes, the server reads the Unity console via read_console to surface any compilation errors before returning success. This verification step appears in Server/src/services/tools/manage_script.py (line 331) and ensures that callers receive a definitive result only after the domain has stabilized. The final response includes a "verified after domain reload" flag, making the post-reload success explicit (lines 331, 435, 470 in manage_script.py).

Unity Plugin Integration

The Unity-side plugin participates in reload handling through defensive coding practices. Static fields clear automatically on reload, so tools that rely on static caches (such as BuildJob and CommandRegistry) guard against half-loaded types (CommandRegistry.cs, line 116). UI windows including MCPForUnityEditorWindow and McpConnectionSection prevent duplicated CreateGUI calls and re-initialize their transport selection after a reload (lines 149 and 327–332 in the Editor Windows directory).

Integration Testing for Reliability

The integration test Server/tests/integration/test_domain_reload_resilience.py validates this resilience by simulating dozens of concurrent MCP calls while a script is created (forcing a domain reload). The test asserts that every request either succeeds or is retried transparently, confirming the server survives the brief period when Unity's bridge is unavailable.

Summary

  • Domain reloads are expected: The MCP server treats Unity's scripting domain teardown as a normal operational condition rather than a connection failure.
  • Automatic reconnection: The Python server implements a 20-second retry window with back-off to cover typical compilation periods.
  • State persistence: Long-running jobs use McpJobStateStore to save progress to disk, enabling seamless continuation after reconnect.
  • Script workflow: Tools like manage_script poll isCompiling, tolerate disconnects, and verify console output post-reload to ensure success.
  • Cross-platform resilience: Both legacy TCP and WebSocket transports implement graceful teardown and reconnection logic.

Frequently Asked Questions

How long does the MCP server wait to reconnect after a domain reload?

The server caps its reconnection attempts at 20 seconds. This timeout, implemented in unity_connection.py (lines 815–819) and plugin_hub.py (lines 846–849), covers the typical 10–20 second window required for Unity to recompile scripts and reload the domain.

What happens to active MCP jobs when Unity recompiles scripts?

Active jobs persist their state using McpJobStateStore, which writes to Unity's SessionState or disk. When the connection re-establishes after the reload, the server retrieves this persisted state and resumes the job from where it left off rather than restarting.

Does the MCP server crash when Unity closes the socket during compilation?

No. The server explicitly recognizes the socket closure as an expected event during domain reload. Both the legacy TCP bridge (unity_connection.py, line 134) and WebSocket hub (plugin_hub.py, line 833) handle this gracefully without entering fatal exception paths.

How do MCP tools verify that script edits succeeded after a domain reload?

Tools like manage_script poll the editor_state resource to detect when isCompiling returns to false. After reconnection, the server reads the Unity console via read_console to check for compilation errors before returning a success response flagged as "verified after domain reload."

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →