We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/jango-blockchained/mcp-freecad'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
import logging
import sys
import time
import traceback
from typing import Any, Dict, List, Optional
try:
from .base import EventProvider
from .import_utils import safe_import_freecad_util
freecad_safe_emit = safe_import_freecad_util("freecad_safe_emit")
except ImportError:
# Fallback for when module is loaded by FreeCAD
import os
addon_dir = os.path.dirname(os.path.dirname(__file__))
if addon_dir not in sys.path:
sys.path.insert(0, addon_dir)
from events.base import EventProvider
from events.import_utils import safe_import_freecad_util
freecad_safe_emit = safe_import_freecad_util("freecad_safe_emit")
logger = logging.getLogger(__name__)
class ErrorEventProvider(EventProvider):
"""Event provider for FreeCAD error events."""
def __init__(
self,
freecad_app=None,
event_router=None,
max_history_size=50,
install_global_handler=False,
):
"""
Initialize the error event provider.
Args:
freecad_app: Optional FreeCAD application instance. If None, will try to import FreeCAD.
event_router: Router for broadcasting events
max_history_size: Maximum number of errors to keep in history
install_global_handler: Whether to install global exception handler (dangerous, default False)
"""
super().__init__(max_history_size)
self.app = freecad_app
self.event_router = event_router
self.error_history: List[Dict[str, Any]] = []
self.original_excepthook = None
self._global_handler_installed = False
if self.app is None:
try:
import FreeCAD
self.app = FreeCAD
logger.info("Connected to FreeCAD")
except ImportError:
logger.warning(
"Could not import FreeCAD. Make sure it's installed and in your Python path."
)
self.app = None
# Only install global exception handler if explicitly requested
if install_global_handler:
self._install_global_exception_handler()
self._setup_signal_handlers()
def _install_global_exception_handler(self):
"""Install global exception handler if not already installed."""
if not self._global_handler_installed and hasattr(sys, "excepthook"):
self.original_excepthook = sys.excepthook
sys.excepthook = self._global_exception_handler
self._global_handler_installed = True
logger.info("Installed global exception handler for error tracking")
def _uninstall_global_exception_handler(self):
"""Restore original exception handler."""
if self._global_handler_installed and self.original_excepthook:
sys.excepthook = self.original_excepthook
self._global_handler_installed = False
logger.info("Restored original exception handler")
def _setup_signal_handlers(self):
"""Set up signal handlers for FreeCAD error events."""
if self.app is None:
logger.warning("FreeCAD not available, cannot set up signal handlers")
return
try:
# Connect to error signals if available
if hasattr(self.app, "signalError"):
self.app.signalError.connect(self._on_error)
logger.info("Connected to FreeCAD error signal")
# Try to connect to the Console observer for Python errors
if hasattr(self.app, "Gui") and hasattr(self.app.Gui, "getMainWindow"):
try:
# Some FreeCAD versions provide a console viewer with error signals
console = self.app.Gui.getMainWindow().findChild(
"QTextEdit", "Report view"
)
if console and hasattr(console, "signalError"):
console.signalError.connect(self._on_error)
logger.info("Connected to FreeCAD console error signal")
except Exception as console_error:
logger.debug(
f"Could not connect to console error signal: {console_error}"
)
except Exception as e:
logger.error(f"Error setting up FreeCAD error signal handlers: {e}")
def _on_error(self, error_msg):
"""Handle error event from FreeCAD signals."""
logger.error(f"FreeCAD error: {error_msg}")
# Create event data
event_data = {
"type": "error",
"message": error_msg,
"timestamp": time.time(),
"source": "freecad_signal",
}
# Record the error
self._record_error(event_data)
def _global_exception_handler(self, exc_type, exc_value, exc_traceback):
"""Handle unhandled exceptions globally."""
# Still call the original handler
self.original_excepthook(exc_type, exc_value, exc_traceback)
# Format the exception
tb_str = "".join(traceback.format_exception(exc_type, exc_value, exc_traceback))
# Create event data
event_data = {
"type": "error",
"message": str(exc_value),
"traceback": tb_str,
"timestamp": time.time(),
"source": "unhandled_exception",
"exception_type": exc_type.__name__,
}
# Record the error
self._record_error(event_data)
def _record_error(self, event_data):
"""Record an error and emit an event."""
# Store in history (limit to configured max size)
self.error_history.append(event_data)
if len(self.error_history) > self.max_history_size:
self.error_history.pop(0)
# Emit event safely
if freecad_safe_emit:
freecad_safe_emit(self.emit_event, "error", event_data, "error_recorded")
def get_error_history(self, limit: Optional[int] = None) -> List[Dict[str, Any]]:
"""
Get the error history.
Args:
limit: Optional limit on the number of history items to return
Returns:
List of error events
"""
if limit and 0 < limit < len(self.error_history):
return self.error_history[-limit:]
return self.error_history
def report_error(
self,
error_message: str,
error_type: str = "manual",
details: Dict[str, Any] = None,
):
"""
Manually report an error.
Args:
error_message: Error message
error_type: Type of error
details: Additional error details
"""
error_data = {
"type": "error",
"message": error_message,
"error_type": error_type,
"timestamp": time.time(),
"source": "manual_report",
}
if details:
error_data.update(details)
self._record_error(error_data)
def get_status(self) -> Dict[str, Any]:
"""Get the current status of the error event provider."""
return {
"freecad_available": self.app is not None,
"global_handler_installed": self._global_handler_installed,
"signals_connected": len(self._signal_connections),
"error_history_size": len(self.error_history),
"max_history_size": self.max_history_size,
"is_shutdown": self._is_shutdown,
}
async def shutdown(self) -> None:
"""Clean up resources when the provider is shutdown."""
# Restore original exception handler first
self._uninstall_global_exception_handler()
# Then call parent shutdown
await super().shutdown()
async def emit_event(self, event_type: str, event_data: Dict[str, Any]) -> None:
"""
Emit an event to all listeners.
Args:
event_type: The type of event
event_data: The event data
"""
if self.event_router is not None:
await self.event_router.broadcast_event(event_type, event_data)
else:
logger.warning(f"No event router available to broadcast {event_type} event")