You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

utils.py 2.9 kB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. from typing import Any, List
  2. from fastapi import WebSocketDisconnect
  3. from pydantic.networks import AnyUrl
  4. class McpOperationError(Exception):
  5. """Raised when MCP operation fails"""
  6. pass
  7. def extract_real_error(e: Exception) -> str:
  8. """Extract the real error message from potentially wrapped exceptions"""
  9. error_parts: List[str] = []
  10. # Handle ExceptionGroup (Python 3.11+)
  11. if hasattr(e, "exceptions") and getattr(e, "exceptions", None):
  12. exceptions_list = getattr(e, "exceptions", [])
  13. for sub_exc in exceptions_list:
  14. error_parts.append(f"{type(sub_exc).__name__}: {str(sub_exc)}")
  15. # Handle chained exceptions
  16. elif hasattr(e, "__cause__") and e.__cause__:
  17. current = e
  18. while current:
  19. error_parts.append(f"{type(current).__name__}: {str(current)}")
  20. current = getattr(current, "__cause__", None)
  21. # Handle context exceptions
  22. elif hasattr(e, "__context__") and e.__context__:
  23. error_parts.append(f"Context: {type(e.__context__).__name__}: {str(e.__context__)}")
  24. error_parts.append(f"Error: {type(e).__name__}: {str(e)}")
  25. # Default case
  26. else:
  27. error_parts.append(f"{type(e).__name__}: {str(e)}")
  28. return " | ".join(error_parts)
  29. def serialize_for_json(obj: Any) -> Any:
  30. """Convert objects to JSON-serializable format"""
  31. if isinstance(obj, AnyUrl):
  32. return str(obj)
  33. elif isinstance(obj, dict):
  34. return {str(k): serialize_for_json(v) for k, v in obj.items()}
  35. elif isinstance(obj, list):
  36. return [serialize_for_json(item) for item in obj]
  37. elif hasattr(obj, "model_dump"):
  38. return serialize_for_json(obj.model_dump())
  39. else:
  40. return obj
  41. def is_websocket_disconnect(e: Exception) -> bool:
  42. """Check if an exception (potentially nested) is a WebSocket disconnect"""
  43. def check_exception(exc: BaseException) -> bool:
  44. if isinstance(exc, WebSocketDisconnect):
  45. return True
  46. exc_name = type(exc).__name__
  47. exc_str = str(exc)
  48. if "WebSocketDisconnect" in exc_name or "NO_STATUS_RCVD" in exc_str:
  49. return True
  50. # Recursively check ExceptionGroup
  51. if hasattr(exc, "exceptions") and getattr(exc, "exceptions", None):
  52. exceptions_list = getattr(exc, "exceptions", [])
  53. for sub_exc in exceptions_list:
  54. if check_exception(sub_exc):
  55. return True
  56. # Check chained exceptions
  57. if hasattr(exc, "__cause__") and exc.__cause__:
  58. if check_exception(exc.__cause__):
  59. return True
  60. # Check context exceptions
  61. if hasattr(exc, "__context__") and exc.__context__:
  62. if check_exception(exc.__context__):
  63. return True
  64. return False
  65. return check_exception(e)