-
Notifications
You must be signed in to change notification settings - Fork 752
feat(#672): Add Keep Server Running toggle in Unity Editor Window and HTTP mode support #725
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,12 +38,14 @@ public class McpAdvancedSection | |
| private VisualElement healthIndicator; | ||
| private Label healthStatus; | ||
| private Button testConnectionButton; | ||
| private Toggle keepServerRunningToggle; | ||
|
|
||
| // Events | ||
| public event Action OnGitUrlChanged; | ||
| public event Action OnHttpServerCommandUpdateRequested; | ||
| public event Action OnTestConnectionRequested; | ||
| public event Action<bool> OnBetaModeChanged; | ||
| public event Action<bool> OnKeepServerRunningChanged; | ||
|
|
||
| public VisualElement Root { get; private set; } | ||
|
|
||
|
|
@@ -77,6 +79,7 @@ private void CacheUIElements() | |
| deployStatusLabel = Root.Q<Label>("deploy-status-label"); | ||
| healthIndicator = Root.Q<VisualElement>("health-indicator"); | ||
| healthStatus = Root.Q<Label>("health-status"); | ||
| keepServerRunningToggle = Root.Q<Toggle>("keep-server-running-toggle"); | ||
| testConnectionButton = Root.Q<Button>("test-connection-button"); | ||
| } | ||
|
|
||
|
|
@@ -108,6 +111,26 @@ private void InitializeUI() | |
| if (betaServerLabel != null) | ||
| betaServerLabel.tooltip = useBetaServerToggle.tooltip; | ||
| } | ||
| if (keepServerRunningToggle != null) | ||
| { | ||
| // When enabled, server stays running after Unity disconnects (for auto-reconnect) | ||
| keepServerRunningToggle.tooltip = "When enabled, MCP server will stay running even when Unity disconnects. " | ||
| + "Useful during development to avoid manual server restart after Domain Reload, test runs, or entering Play Mode. " | ||
| + "Only works with HTTP transport mode."; | ||
| var keepRunningLabel = keepServerRunningToggle?.parent?.Q<Label>(); | ||
| if (keepRunningLabel != null) | ||
| { | ||
| keepRunningLabel.tooltip = keepServerRunningToggle.tooltip; | ||
| } | ||
| // Load current value and register callback | ||
| keepServerRunningToggle.value = EditorPrefs.GetBool(EditorPrefKeys.KeepServerRunning, false); | ||
| keepServerRunningToggle.RegisterValueChangedCallback(evt => | ||
| { | ||
| EditorPrefs.SetBool(EditorPrefKeys.KeepServerRunning, evt.newValue); | ||
| OnKeepServerRunningChanged?.Invoke(evt.newValue); | ||
| OnHttpServerCommandUpdateRequested?.Invoke(); | ||
| }); | ||
| } | ||
|
Comment on lines
+114
to
+133
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: rg -n -C5 'keepServerRunning|keep-server-running|KeepServerRunning' --type=cs -g '!*Test*'Repository: CoplayDev/unity-mcp Length of output: 9272 🏁 Script executed: rg -n 'SetEnabled.*keepServerRunning|keepServerRunning.*SetEnabled' --type=cs -g '!*Test*'Repository: CoplayDev/unity-mcp Length of output: 45 🏁 Script executed: rg -n -B5 -A5 'keepServerRunningToggle|KeepServerRunning' --type=cs -g '!*Test*' | grep -i 'enabled\|visible\|transport\|http\|stdio'Repository: CoplayDev/unity-mcp Length of output: 2488 🏁 Script executed: rg -n 'McpAdvancedSection' --type=cs -g '!*Test*'Repository: CoplayDev/unity-mcp Length of output: 685 🏁 Script executed: cat -n MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs | head -100Repository: CoplayDev/unity-mcp Length of output: 4234 🏁 Script executed: rg -n 'advancedSection|SetEnabled|transport.*mode|http.*mode' -A2 -B2 MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs --type=csRepository: CoplayDev/unity-mcp Length of output: 1869 🏁 Script executed: rg -n 'transport.*mode|TransportMode|HTTP|STDIO' --type=cs -g '!*Test*' | grep -i 'enabled\|visible\|toggle\|advanced' | head -20Repository: CoplayDev/unity-mcp Length of output: 1593 🏁 Script executed: rg -n 'public.*void.*Set|public.*void.*Update|public.*void.*Enable|public.*void.*Disable' MCPForUnity/Editor/Windows/Components/Advanced/McpAdvancedSection.cs --type=csRepository: CoplayDev/unity-mcp Length of output: 186 🏁 Script executed: rg -n -A10 'OnKeepServerRunningChanged' --type=cs -g '!*Test*'Repository: CoplayDev/unity-mcp Length of output: 2525 🏁 Script executed: rg -n 'transport.*mode\|TransportMode\|IsHttpMode\|IsStdioMode' --type=cs -g '!*Test*' | head -30Repository: CoplayDev/unity-mcp Length of output: 45 🏁 Script executed: rg -n 'TransportMode|transport.*mode|IsHttpMode|HttpMode|StdioMode' --type=cs -g '!*Test*' | head -40Repository: CoplayDev/unity-mcp Length of output: 5434 🏁 Script executed: rg -n 'enum.*Transport|class.*Transport' --type=cs -g '!*Test*' | head -20Repository: CoplayDev/unity-mcp Length of output: 1058 🏁 Script executed: rg -n -B3 -A3 'OnKeepServerRunningChanged' MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs --type=csRepository: CoplayDev/unity-mcp Length of output: 45 🏁 Script executed: wc -l MCPForUnity/Editor/Windows/Components/Advanced/McpAdvancedSection.csRepository: CoplayDev/unity-mcp Length of output: 135 🏁 Script executed: cat -n MCPForUnity/Editor/Windows/Components/Advanced/McpAdvancedSection.cs | tail -n +240 | head -n 100Repository: CoplayDev/unity-mcp Length of output: 4939 🏁 Script executed: rg -n 'public|private.*void.*Transport|SetEnabled|SetDisplay|style\.display' MCPForUnity/Editor/Windows/Components/Advanced/McpAdvancedSection.cs --type=csRepository: CoplayDev/unity-mcp Length of output: 711 Transport-mode gating is missing for the keep-server-running toggle. The PR description specifies the toggle should be "enabled only when HTTP transport is active; stdio mode shows an explanation," but McpAdvancedSection has no transport-mode detection or SetEnabled/visibility logic. The toggle will appear and remain functional regardless of the active transport mode. There is no public method to control the toggle's enabled state, and MCPForUnityEditorWindow does not wire up any transport-mode event handlers. Either add transport-mode gating to McpAdvancedSection or confirm this is managed by a parent coordinator. 🤖 Prompt for AI Agents |
||
| if (testConnectionButton != null) | ||
| testConnectionButton.tooltip = "Test the connection between Unity and the MCP server."; | ||
| if (deploySourcePath != null) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -133,11 +133,15 @@ def doRollover(self): | |
| # In-memory custom tool service initialized after MCP construction | ||
| custom_tool_service: CustomToolService | None = None | ||
|
|
||
| # Per-session keep_server_running flag, indexed by session_id | ||
| # Set by middleware when Unity registers with the flag enabled | ||
| keep_server_running: dict[str, bool] = {} | ||
|
Comment on lines
+136
to
+138
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The global As a result, 🔧 Option: query the registry directly at shutdown instead of maintaining a separate dict-# Per-session keep_server_running flag, indexed by session_id
-# Set by middleware when Unity registers with the flag enabled
-keep_server_running: dict[str, bool] = {}Then at shutdown (lines 256-271), query the registry: - has_keep_running = any(keep_server_running.values()) if keep_server_running else False
+ has_keep_running = False
+ if _plugin_registry is not None:
+ try:
+ sessions = await _plugin_registry.list_sessions()
+ has_keep_running = any(s.keep_server_running for s in sessions.values())
+ except Exception:
+ pass
if _unity_connection_pool and not has_keep_running:
_unity_connection_pool.disconnect_all()
shutdown_msg = "MCP for Unity Server shut down"
if has_keep_running:
shutdown_msg += " (Keep-Running mode: maintaining server for Unity reconnection)"
logger.info(shutdown_msg)
-
- # Note: keep_server_running state is per-session, not server-global.
- # Cleared when sessions expire, used by middleware to pass state.
- keep_server_running.clear()🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| @asynccontextmanager | ||
| async def server_lifespan(server: FastMCP) -> AsyncIterator[dict[str, Any]]: | ||
| """Handle server startup and shutdown.""" | ||
| global _unity_connection_pool, _server_version | ||
| global _unity_connection_pool, _server_version, keep_server_running | ||
| _server_version = get_package_version() | ||
| logger.info(f"MCP for Unity Server v{_server_version} starting up") | ||
|
|
||
|
|
@@ -183,9 +187,17 @@ def _emit_startup(): | |
| logger.info( | ||
| "Skipping Unity connection on startup (UNITY_MCP_SKIP_STARTUP_CONNECT=1)") | ||
| else: | ||
| # Initialize connection pool and discover instances | ||
| _unity_connection_pool = get_unity_connection_pool() | ||
| instances = _unity_connection_pool.discover_all_instances() | ||
| # Initialize connection pool and discover instances (stdio mode only) | ||
| # In HTTP mode, PluginHub handles connections directly | ||
| instances = [] # Initialize to avoid UnboundLocalError in HTTP mode | ||
| if enable_http_server: | ||
| _unity_connection_pool = None # No legacy pool in HTTP mode | ||
| logger.info("HTTP mode enabled - PluginHub will manage Unity connections") | ||
| # Skip stdio-specific connection logic in HTTP mode. | ||
| # Lifespan must still continue to reach the context yield. | ||
| else: | ||
| _unity_connection_pool = get_unity_connection_pool() | ||
| instances = _unity_connection_pool.discover_all_instances() | ||
|
|
||
| if instances: | ||
| logger.info( | ||
|
|
@@ -244,9 +256,23 @@ def _emit_startup(): | |
| "plugin_registry": _plugin_registry, | ||
| } | ||
| finally: | ||
| if _unity_connection_pool: | ||
| # In HTTP mode, PluginHub manages connections independently. | ||
| # Only disconnect legacy pool if it was initialized (stdio mode). | ||
|
Comment on lines
258
to
+260
|
||
| # Check if any session has keep_server_running enabled. | ||
| has_keep_running = any(keep_server_running.values()) if keep_server_running else False | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The shutdown path now evaluates Useful? React with 👍 / 👎. |
||
|
|
||
| if _unity_connection_pool and not has_keep_running: | ||
| _unity_connection_pool.disconnect_all() | ||
| logger.info("MCP for Unity Server shut down") | ||
|
|
||
| shutdown_msg = "MCP for Unity Server shut down" | ||
| if has_keep_running: | ||
| shutdown_msg += " (Keep-Running mode: maintaining server for Unity reconnection)" | ||
| logger.info(shutdown_msg) | ||
|
|
||
| # Note: keep_server_running state is per-session, not server-global. | ||
| # Cleared when sessions expire, used by middleware to pass state. | ||
| keep_server_running.clear() | ||
|
|
||
|
|
||
|
|
||
| def _build_instructions(project_scoped_tools: bool) -> str: | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -191,6 +191,13 @@ async def on_disconnect(self, websocket: WebSocket, close_code: int) -> None: | |||||
| ping_task.cancel() | ||||||
| # Clean up last pong tracking | ||||||
| cls._last_pong.pop(session_id, None) | ||||||
| # Clean up keep_server_running flag to avoid stale entries | ||||||
| try: | ||||||
| import main as main_module | ||||||
| main_module.keep_server_running.pop(session_id, None) | ||||||
| except (ImportError, AttributeError, KeyError): | ||||||
| # main module not yet imported or key already removed | ||||||
| pass | ||||||
| # Fail-fast any in-flight commands for this session to avoid waiting for COMMAND_TIMEOUT. | ||||||
| pending_ids = [ | ||||||
| command_id | ||||||
|
|
@@ -364,6 +371,7 @@ async def _handle_register(self, websocket: WebSocket, payload: RegisterMessage) | |||||
| project_hash = payload.project_hash | ||||||
| unity_version = payload.unity_version | ||||||
| project_path = payload.project_path | ||||||
| keep_server_running = getattr(payload, 'keep_server_running', False) | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎.
|
||||||
| keep_server_running = getattr(payload, 'keep_server_running', False) | |
| keep_server_running = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR description mentions a Unity Editor UI toggle and sending a
keep_server_runningflag during WebSocket registration, but the only Unity-side change in this PR appears to be adding the EditorPrefs key. A repo-wide search shows no other references toEditorPrefKeys.KeepServerRunningor any C# code emittingkeep_server_runningin a registration message, so the feature looks incomplete or the description is ahead of the code.