feat: optional out-of-process broker transport (survives domain reloads)#13
Open
dehuaichendragonplus wants to merge 1 commit into
Conversation
funplay's HttpMCPTransport binds a TcpListener inside Unity's managed AppDomain. Every domain reload (script recompile / entering Play Mode) destroys that AppDomain, so the listener dies and the port is released; MCP client requests landing in the reload window fail with "connection refused", and the tool call that triggered the reload loses its response. A socket in the reloadable AppDomain fundamentally cannot survive an AppDomain unload — the only robust fix is to move the client-facing socket out of process. This adds an opt-in out-of-process broker, realizing the IsAttachedToExistingServer seam already present in the codebase. No external runtime is required: the broker runs under the Unity-bundled Mono. - keepalive-broker.cs: standalone broker (TcpListener, raw HTTP, conservative C# / Mono 4.5 profile). Owns the client-facing port; holds in-flight client requests across the reload gap and re-queues a request that was pulled-but-unanswered when the plugin reloaded mid-call. Header framing (X-Broker-ReqId) keeps it a dumb byte forwarder — no JSON parsing on the broker side. - BrokerClientTransport (IMCPTransport, IsAttachedToExistingServer => true): instead of binding a listener, long-polls the broker (GET /_b/pull), dispatches through the existing MCPRequestHandler, and pushes responses (POST /_b/push). Uses HttpWebRequest (editor BCL, no new asmdef refs). - BrokerProcessManager: launches the broker under the bundled Mono and kills it on switch-to-direct / editor quit (EditorApplication.quitting). Resolves Mono dynamically because its path differs by Unity version (Contents/MonoBleedingEdge vs Unity 6's Contents/Resources/Scripting/ MonoBleedingEdge) — known candidates + bounded recursive search, never a hard-coded path. If the prebuilt .exe is absent it compiles keepalive-broker.cs once with the bundled mcs (cached under Library/). Tracks the process via a Library/funplay-broker.pid file so it can be reacquired/ stopped after a domain reload. A port-open guard adopts an already-listening broker instead of launching a duplicate, so the broker is reused across reloads and editor restarts (stable PID, no churn / no orphan). - MCPServerService: selects BrokerClientTransport when broker mode is on and the broker started, else kills any stale broker (freeing the port) and uses HttpMCPTransport. If Mono is missing or the broker fails to launch it logs a warning and falls back to the in-process transport. - Settings (BrokerModeEnabled / BrokerMonoPath, persisted) + a "Broker mode" toggle, optional Mono path override, and status line in the server controls panel. The broker binds the same port as the server, so MCP clients need no config change — the switch is transparent. Default (broker mode off) is byte-for-byte the existing in-process behavior. Verified on macOS / Apple Silicon: - Compatibility: the bundled Mono exists in every Unity 2019+ editor; the same broker .exe (built with Unity 6 mcs) runs under both Unity 6000 and Unity 2022.3 Mono (6.13.0). - Domain-reload survival: an MCP client driven by a continuous request loop through a real RequestScriptReload kept 100% success (zero connection-refused); the request spanning the reload was held ~13s and returned 200; the broker process PID stayed stable across the reload (reused, not restarted). - Editor restart: closing Unity kills the broker (quitting hook); reopening auto-launches a fresh broker (persisted broker mode -> server auto-start -> EnsureRunning), confirmed end-to-end with a real editor restart. Windows (path-verified against Unity docs/community; not yet run on a Windows editor): the bundled Mono is mono.exe at <Editor>\Data\MonoBleedingEdge\bin\ — applicationContentsPath (=<Editor>\Data) plus the resolver's first candidate resolves it directly. Unity 6.3's Mono->.NET relocation moved MonoBleedingEdge only on macOS (to Contents/Resources/Scripting/MonoBleedingEdge); on Windows it stays at Data\MonoBleedingEdge. mcs for the compile-on-demand fallback is at ...\MonoBleedingEdge\lib\mono\4.5\mcs.exe. If Mono is ever absent the plugin falls back to the in-process HttpMCPTransport.
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.
funplay's HttpMCPTransport binds a TcpListener inside Unity's managed AppDomain. Every domain reload (script recompile / entering Play Mode) destroys that AppDomain, so the listener dies and the port is released; MCP client requests landing in the reload window fail with "connection refused", and the tool call that triggered the reload loses its response. A socket in the reloadable AppDomain fundamentally cannot survive an AppDomain unload — the only robust fix is to move the client-facing socket out of process.
This adds an opt-in out-of-process broker, realizing the IsAttachedToExistingServer seam already present in the codebase. No external runtime is required: the broker runs under the Unity-bundled Mono.
Default (broker mode off) is byte-for-byte the existing in-process behavior.
Verified on macOS / Apple Silicon:
Windows (path-verified against Unity docs/community; not yet run on a Windows editor): the bundled Mono is mono.exe at \Data\MonoBleedingEdge\bin\ — applicationContentsPath (=\Data) plus the resolver's first candidate resolves it directly. Unity 6.3's Mono->.NET relocation moved MonoBleedingEdge only on macOS (to Contents/Resources/Scripting/MonoBleedingEdge); on Windows it stays at Data\MonoBleedingEdge. mcs for the compile-on-demand fallback is at ...\MonoBleedingEdge\lib\mono\4.5\mcs.exe. If Mono is ever absent the plugin falls back to the in-process HttpMCPTransport.
What changed
Checklist
Funplay > MCP Serveropens and starts correctly.idea/or.DS_StoreCHANGELOG.mdwhen the change affects users