from typing import Any, List from fastapi import WebSocketDisconnect from pydantic.networks import AnyUrl class McpOperationError(Exception): """Raised when MCP operation fails""" pass def extract_real_error(e: Exception) -> str: """Extract the real error message from potentially wrapped exceptions""" error_parts: List[str] = [] # Handle ExceptionGroup (Python 3.11+) if hasattr(e, "exceptions") and getattr(e, "exceptions", None): exceptions_list = getattr(e, "exceptions", []) for sub_exc in exceptions_list: error_parts.append(f"{type(sub_exc).__name__}: {str(sub_exc)}") # Handle chained exceptions elif hasattr(e, "__cause__") and e.__cause__: current = e while current: error_parts.append(f"{type(current).__name__}: {str(current)}") current = getattr(current, "__cause__", None) # Handle context exceptions elif hasattr(e, "__context__") and e.__context__: error_parts.append(f"Context: {type(e.__context__).__name__}: {str(e.__context__)}") error_parts.append(f"Error: {type(e).__name__}: {str(e)}") # Default case else: error_parts.append(f"{type(e).__name__}: {str(e)}") return " | ".join(error_parts) def serialize_for_json(obj: Any) -> Any: """Convert objects to JSON-serializable format""" if isinstance(obj, AnyUrl): return str(obj) elif isinstance(obj, dict): return {str(k): serialize_for_json(v) for k, v in obj.items()} elif isinstance(obj, list): return [serialize_for_json(item) for item in obj] elif hasattr(obj, "model_dump"): return serialize_for_json(obj.model_dump()) else: return obj def is_websocket_disconnect(e: Exception) -> bool: """Check if an exception (potentially nested) is a WebSocket disconnect""" def check_exception(exc: BaseException) -> bool: if isinstance(exc, WebSocketDisconnect): return True exc_name = type(exc).__name__ exc_str = str(exc) if "WebSocketDisconnect" in exc_name or "NO_STATUS_RCVD" in exc_str: return True # Recursively check ExceptionGroup if hasattr(exc, "exceptions") and getattr(exc, "exceptions", None): exceptions_list = getattr(exc, "exceptions", []) for sub_exc in exceptions_list: if check_exception(sub_exc): return True # Check chained exceptions if hasattr(exc, "__cause__") and exc.__cause__: if check_exception(exc.__cause__): return True # Check context exceptions if hasattr(exc, "__context__") and exc.__context__: if check_exception(exc.__context__): return True return False return check_exception(e)