Files
freecad-robust-mcp-fc111/addon/FreecadRobustMCP/InitGui.py
T
Sean P. KaneandGitHub 6a6cad9e65 feat: Add workbench and major cleanup, refactor, and updates (#21)
* FreeCAD addon support for Workbench and Plugins

* docs: refactor docs and clean up linters, etc.

* Remove mdformat

* test: improve test coverage

* test:Lots of general fixes

* chore: more general fixes
2026-01-06 15:44:27 -08:00

223 lines
7.6 KiB
Python

"""FreeCAD Robust MCP Workbench - GUI Initialization.
SPDX-License-Identifier: MIT
Copyright (c) 2025 Sean P. Kane (GitHub: spkane)
This module defines the workbench class and GUI commands for the
MCP Bridge. It provides toolbar buttons and menu items to start
and stop the MCP bridge server.
"""
from __future__ import annotations
from pathlib import Path
from typing import Any
import FreeCAD
import FreeCADGui
# Global reference to the plugin instance
_mcp_plugin: Any = None
def get_addon_path() -> str:
"""Get the path to this addon's directory."""
return str(Path(__file__).resolve().parent)
def get_icon_path(icon_name: str) -> str:
"""Get the full path to an icon file.
Args:
icon_name: Name of the icon file (e.g., "FreecadRobustMCP.svg")
Returns:
Full path to the icon file.
"""
return str(Path(get_addon_path()) / icon_name)
class StartMCPBridgeCommand:
"""Command to start the MCP bridge server."""
def GetResources(self) -> dict[str, str]:
"""Return the command resources (icon, menu text, tooltip)."""
return {
"Pixmap": get_icon_path("FreecadRobustMCP.svg"),
"MenuText": "Start MCP Bridge",
"ToolTip": (
"Start the MCP bridge server for AI assistant integration.\n"
"Listens on XML-RPC (port 9875) and Socket (port 9876)."
),
}
def IsActive(self) -> bool:
"""Return True if the command can be executed."""
global _mcp_plugin # noqa: PLW0602
# Can only start if not already running
return _mcp_plugin is None or not _mcp_plugin.is_running
def Activated(self) -> None:
"""Execute the command to start the MCP bridge."""
global _mcp_plugin
if _mcp_plugin is not None and _mcp_plugin.is_running:
FreeCAD.Console.PrintWarning("MCP Bridge is already running.\n")
return
try:
# Import the server module from the bundled code
from freecad_mcp_bridge.server import FreecadMCPPlugin
# Create and start the plugin
_mcp_plugin = FreecadMCPPlugin(
host="localhost",
port=9876, # JSON-RPC socket port
xmlrpc_port=9875, # XML-RPC port
enable_xmlrpc=True,
)
_mcp_plugin.start()
FreeCAD.Console.PrintMessage("\n")
FreeCAD.Console.PrintMessage("=" * 50 + "\n")
FreeCAD.Console.PrintMessage("MCP Bridge started!\n")
FreeCAD.Console.PrintMessage(" - XML-RPC: localhost:9875\n")
FreeCAD.Console.PrintMessage(" - Socket: localhost:9876\n")
FreeCAD.Console.PrintMessage("=" * 50 + "\n")
FreeCAD.Console.PrintMessage(
"\nYou can now connect your MCP client (Claude Code, etc.) to FreeCAD.\n"
)
except ImportError as e:
FreeCAD.Console.PrintError(f"Failed to import MCP Bridge module: {e}\n")
FreeCAD.Console.PrintError(
"Ensure the FreecadRobustMCP addon is properly installed.\n"
)
except Exception as e:
FreeCAD.Console.PrintError(f"Failed to start MCP Bridge: {e}\n")
class StopMCPBridgeCommand:
"""Command to stop the MCP bridge server."""
def GetResources(self) -> dict[str, str]:
"""Return the command resources (icon, menu text, tooltip)."""
return {
"Pixmap": get_icon_path("FreecadRobustMCP.svg"),
"MenuText": "Stop MCP Bridge",
"ToolTip": "Stop the running MCP bridge server.",
}
def IsActive(self) -> bool:
"""Return True if the command can be executed."""
global _mcp_plugin # noqa: PLW0602
# Can only stop if currently running
return _mcp_plugin is not None and _mcp_plugin.is_running
def Activated(self) -> None:
"""Execute the command to stop the MCP bridge."""
global _mcp_plugin
if _mcp_plugin is None or not _mcp_plugin.is_running:
FreeCAD.Console.PrintWarning("MCP Bridge is not running.\n")
return
try:
_mcp_plugin.stop()
_mcp_plugin = None
FreeCAD.Console.PrintMessage("\n")
FreeCAD.Console.PrintMessage("=" * 50 + "\n")
FreeCAD.Console.PrintMessage("MCP Bridge stopped.\n")
FreeCAD.Console.PrintMessage("=" * 50 + "\n")
except Exception as e:
FreeCAD.Console.PrintError(f"Failed to stop MCP Bridge: {e}\n")
class MCPBridgeStatusCommand:
"""Command to show MCP bridge status."""
def GetResources(self) -> dict[str, str]:
"""Return the command resources (icon, menu text, tooltip)."""
return {
"Pixmap": get_icon_path("FreecadRobustMCP.svg"),
"MenuText": "MCP Bridge Status",
"ToolTip": "Show the current status of the MCP bridge server.",
}
def IsActive(self) -> bool:
"""Return True if the command can be executed."""
# Always active - can always show status
return True
def Activated(self) -> None:
"""Execute the command to show MCP bridge status."""
global _mcp_plugin # noqa: PLW0602
FreeCAD.Console.PrintMessage("\n")
FreeCAD.Console.PrintMessage("=" * 50 + "\n")
FreeCAD.Console.PrintMessage("MCP Bridge Status\n")
FreeCAD.Console.PrintMessage("=" * 50 + "\n")
if _mcp_plugin is None:
FreeCAD.Console.PrintMessage("Status: Not initialized\n")
elif not _mcp_plugin.is_running:
FreeCAD.Console.PrintMessage("Status: Stopped\n")
else:
FreeCAD.Console.PrintMessage("Status: Running\n")
FreeCAD.Console.PrintMessage(f" Instance ID: {_mcp_plugin.instance_id}\n")
FreeCAD.Console.PrintMessage(f" XML-RPC Port: {_mcp_plugin.xmlrpc_port}\n")
FreeCAD.Console.PrintMessage(f" Socket Port: {_mcp_plugin.socket_port}\n")
FreeCAD.Console.PrintMessage(
f" Requests processed: {_mcp_plugin.request_count}\n"
)
FreeCAD.Console.PrintMessage("=" * 50 + "\n")
class FreecadRobustMCPWorkbench(FreeCADGui.Workbench):
"""FreeCAD Robust MCP Workbench.
Provides toolbar and menu commands to start, stop, and monitor
the MCP bridge server for AI assistant integration.
"""
MenuText = "MCP Bridge"
ToolTip = "MCP Bridge for AI assistant integration with FreeCAD"
Icon = get_icon_path("FreecadRobustMCP.svg")
def Initialize(self) -> None:
"""Initialize the workbench - called once when first activated."""
# Register commands
FreeCADGui.addCommand("Start_MCP_Bridge", StartMCPBridgeCommand())
FreeCADGui.addCommand("Stop_MCP_Bridge", StopMCPBridgeCommand())
FreeCADGui.addCommand("MCP_Bridge_Status", MCPBridgeStatusCommand())
# Create toolbar and menu
commands = ["Start_MCP_Bridge", "Stop_MCP_Bridge", "MCP_Bridge_Status"]
self.appendToolbar("MCP Bridge", commands)
self.appendMenu("MCP Bridge", commands)
FreeCAD.Console.PrintMessage("FreeCAD Robust MCP workbench initialized\n")
def Activated(self) -> None:
"""Called when the workbench is activated."""
pass
def Deactivated(self) -> None:
"""Called when the workbench is deactivated."""
pass
def ContextMenu(self, recipient: Any) -> None:
"""Called when right-clicking in the view or object tree."""
pass
def GetClassName(self) -> str:
"""Return the C++ class name for this workbench."""
return "Gui::PythonWorkbench"
# Register the workbench
FreeCADGui.addWorkbench(FreecadRobustMCPWorkbench())