"""Robust MCP Bridge Workbench - Initialization. SPDX-License-Identifier: MIT Copyright (c) 2025 Sean P. Kane (GitHub: spkane) This module is executed when FreeCAD starts up. It handles initialization tasks for the Robust MCP Bridge workbench, including auto-start of the MCP bridge if configured. Works in both GUI and headless modes. Note: Status bar updates are handled by InitGui.py since Qt operations must run on the main thread. """ from __future__ import annotations from typing import TYPE_CHECKING, Any if TYPE_CHECKING: from freecad_mcp_bridge.bridge_utils import GuiWaiter import FreeCAD FreeCAD.Console.PrintMessage("Robust MCP Bridge: Init loaded\n") # Global reference to GuiWaiter and auto-start timer to prevent garbage collection # Type annotations use Any for timer since it could be QTimer from PySide2 or PySide6 _auto_start_timer: Any | None = None _gui_waiter: GuiWaiter | None = None def _auto_start_bridge() -> None: """Auto-start the MCP bridge if configured in preferences. This function is called via a deferred timer (GUI mode) or directly (headless mode) after FreeCAD finishes loading. It starts the bridge without requiring the workbench to be selected. """ try: from preferences import get_auto_start if not get_auto_start(): return # Check if bridge is already running from commands import _mcp_plugin if _mcp_plugin is not None and _mcp_plugin.is_running: return FreeCAD.Console.PrintMessage( "Auto-starting MCP Bridge (configured in preferences)...\n" ) # Import and start the bridge directly from freecad_mcp_bridge.server import FreecadMCPPlugin from preferences import get_socket_port, get_xmlrpc_port xmlrpc_port = get_xmlrpc_port() socket_port = get_socket_port() plugin = FreecadMCPPlugin( host="localhost", port=socket_port, xmlrpc_port=xmlrpc_port, enable_xmlrpc=True, ) plugin.start() # Register plugin with commands module for restart detection from freecad_mcp_bridge.bridge_utils import register_mcp_plugin register_mcp_plugin(plugin, xmlrpc_port, socket_port) FreeCAD.Console.PrintMessage("\n") FreeCAD.Console.PrintMessage("=" * 50 + "\n") FreeCAD.Console.PrintMessage("MCP Bridge started!\n") FreeCAD.Console.PrintMessage(f" - XML-RPC: localhost:{xmlrpc_port}\n") FreeCAD.Console.PrintMessage(f" - Socket: localhost:{socket_port}\n") FreeCAD.Console.PrintMessage("=" * 50 + "\n") FreeCAD.Console.PrintMessage( "\nYou can now connect your MCP client (Claude Code, etc.) to FreeCAD.\n" ) except Exception as e: FreeCAD.Console.PrintError(f"Failed to auto-start MCP Bridge: {e}\n") # Schedule auto-start after FreeCAD finishes loading # Strategy: # - If FreeCAD.GuiUp is True: Qt event loop is running, use timer for deferred start # - If FreeCAD.GuiUp is False but Qt is available: FreeCAD GUI is initializing. # Use GuiWaiter to wait for GuiUp to become True before starting. # This ensures the bridge uses Qt timer (not background thread) for queue processing. # - If Qt is not available: Pure headless mode, start bridge directly # # CRITICAL: We must wait for FreeCAD.GuiUp to be True before starting the bridge # in GUI mode. If we start when GuiUp is False, the bridge's _start_queue_processor() # will see GuiUp=False and use a background thread. Later, code executed on that # thread will try to do Qt operations, causing crashes (SIGABRT in QCocoaWindow). try: from preferences import get_auto_start if get_auto_start(): # Try to import Qt import contextlib QtCore = None try: from PySide2 import QtCore # type: ignore[assignment, no-redef] except ImportError: with contextlib.suppress(ImportError): from PySide6 import QtCore # type: ignore[assignment, no-redef] if FreeCAD.GuiUp: # GUI is already up - use timer for deferred start if QtCore is not None: _auto_start_timer = QtCore.QTimer() _auto_start_timer.setSingleShot(True) _auto_start_timer.timeout.connect(_auto_start_bridge) _auto_start_timer.start(1000) else: # GUI is up but Qt import failed - start directly _auto_start_bridge() elif QtCore is not None: # GUI not ready yet, but Qt is available (FreeCAD starting in GUI mode) # Use GuiWaiter to wait for GuiUp to become True before starting from freecad_mcp_bridge.bridge_utils import GuiWaiter _gui_waiter = GuiWaiter( callback=_auto_start_bridge, log_prefix="Robust MCP Bridge", timeout_error_extra=( "\nTo start the bridge manually, select the Robust MCP Bridge " "workbench\nand click 'Start MCP Bridge'.\n\n" ), ) _gui_waiter.start() else: # True headless mode - no Qt, no GUI _auto_start_bridge() except Exception as e: FreeCAD.Console.PrintWarning(f"Could not set up auto-start: {e}\n")