"""
Custom exceptions for LinkedIn MCP Server.
Provides a hierarchy of exceptions for proper error handling and reporting.
"""
from typing import Any
class LinkedInMCPError(Exception):
"""Base exception for all LinkedIn MCP errors."""
def __init__(
self,
message: str,
*,
details: dict[str, Any] | None = None,
cause: Exception | None = None,
) -> None:
super().__init__(message)
self.message = message
self.details = details or {}
self.cause = cause
def __str__(self) -> str:
base = self.message
if self.details:
base += f" | Details: {self.details}"
if self.cause:
base += f" | Caused by: {self.cause}"
return base
class LinkedInAuthError(LinkedInMCPError):
"""Authentication-related errors."""
def __init__(
self,
message: str = "LinkedIn authentication failed",
*,
details: dict[str, Any] | None = None,
cause: Exception | None = None,
) -> None:
super().__init__(message, details=details, cause=cause)
class LinkedInRateLimitError(LinkedInMCPError):
"""Rate limiting errors from LinkedIn API."""
def __init__(
self,
message: str = "LinkedIn rate limit exceeded",
*,
retry_after: int | None = None,
details: dict[str, Any] | None = None,
cause: Exception | None = None,
) -> None:
details = details or {}
if retry_after:
details["retry_after_seconds"] = retry_after
super().__init__(message, details=details, cause=cause)
self.retry_after = retry_after
class LinkedInAPIError(LinkedInMCPError):
"""General API errors from LinkedIn."""
def __init__(
self,
message: str = "LinkedIn API error",
*,
status_code: int | None = None,
endpoint: str | None = None,
details: dict[str, Any] | None = None,
cause: Exception | None = None,
) -> None:
details = details or {}
if status_code:
details["status_code"] = status_code
if endpoint:
details["endpoint"] = endpoint
super().__init__(message, details=details, cause=cause)
self.status_code = status_code
self.endpoint = endpoint
class LinkedInSessionError(LinkedInMCPError):
"""Session management errors."""
def __init__(
self,
message: str = "LinkedIn session error",
*,
session_expired: bool = False,
details: dict[str, Any] | None = None,
cause: Exception | None = None,
) -> None:
details = details or {}
details["session_expired"] = session_expired
super().__init__(message, details=details, cause=cause)
self.session_expired = session_expired
class LinkedInProfileError(LinkedInMCPError):
"""Profile-related errors."""
def __init__(
self,
message: str = "LinkedIn profile error",
*,
profile_id: str | None = None,
details: dict[str, Any] | None = None,
cause: Exception | None = None,
) -> None:
details = details or {}
if profile_id:
details["profile_id"] = profile_id
super().__init__(message, details=details, cause=cause)
self.profile_id = profile_id
class LinkedInPostError(LinkedInMCPError):
"""Post creation/management errors."""
def __init__(
self,
message: str = "LinkedIn post error",
*,
post_id: str | None = None,
details: dict[str, Any] | None = None,
cause: Exception | None = None,
) -> None:
details = details or {}
if post_id:
details["post_id"] = post_id
super().__init__(message, details=details, cause=cause)
self.post_id = post_id
class LinkedInMessageError(LinkedInMCPError):
"""Messaging-related errors."""
def __init__(
self,
message: str = "LinkedIn messaging error",
*,
conversation_id: str | None = None,
recipient_id: str | None = None,
details: dict[str, Any] | None = None,
cause: Exception | None = None,
) -> None:
details = details or {}
if conversation_id:
details["conversation_id"] = conversation_id
if recipient_id:
details["recipient_id"] = recipient_id
super().__init__(message, details=details, cause=cause)
self.conversation_id = conversation_id
self.recipient_id = recipient_id
class FeatureDisabledError(LinkedInMCPError):
"""Feature flag disabled error."""
def __init__(
self,
feature_name: str,
*,
details: dict[str, Any] | None = None,
) -> None:
message = f"Feature '{feature_name}' is disabled"
details = details or {}
details["feature_name"] = feature_name
super().__init__(message, details=details)
self.feature_name = feature_name
class BrowserAutomationError(LinkedInMCPError):
"""Browser automation (Playwright) errors."""
def __init__(
self,
message: str = "Browser automation error",
*,
selector: str | None = None,
url: str | None = None,
details: dict[str, Any] | None = None,
cause: Exception | None = None,
) -> None:
details = details or {}
if selector:
details["selector"] = selector
if url:
details["url"] = url
super().__init__(message, details=details, cause=cause)
self.selector = selector
self.url = url
class SchedulerError(LinkedInMCPError):
"""Scheduler-related errors."""
def __init__(
self,
message: str = "Scheduler error",
*,
job_id: str | None = None,
details: dict[str, Any] | None = None,
cause: Exception | None = None,
) -> None:
details = details or {}
if job_id:
details["job_id"] = job_id
super().__init__(message, details=details, cause=cause)
self.job_id = job_id
class DatabaseError(LinkedInMCPError):
"""Database-related errors."""
def __init__(
self,
message: str = "Database error",
*,
operation: str | None = None,
table: str | None = None,
details: dict[str, Any] | None = None,
cause: Exception | None = None,
) -> None:
details = details or {}
if operation:
details["operation"] = operation
if table:
details["table"] = table
super().__init__(message, details=details, cause=cause)
self.operation = operation
self.table = table
class ValidationError(LinkedInMCPError):
"""Input validation errors."""
def __init__(
self,
message: str = "Validation error",
*,
field: str | None = None,
value: Any = None,
details: dict[str, Any] | None = None,
) -> None:
details = details or {}
if field:
details["field"] = field
if value is not None:
details["value"] = str(value)[:100] # Truncate for safety
super().__init__(message, details=details)
self.field = field
self.value = value
# =============================================================================
# Error Interpretation Utility
# =============================================================================
# Common error patterns and their user-friendly translations
ERROR_PATTERNS: list[tuple[str, str, str]] = [
# (pattern to match, user-friendly message, suggestion)
(
"Expecting value: line 1 column 1",
"LinkedIn blocked this request",
"Try refreshing cookies: linkedin-mcp-auth extract-cookies"
),
(
"Exceeded 30 redirects",
"LinkedIn session redirect loop detected",
"Re-authenticate: linkedin-mcp-auth extract-cookies --browser chrome"
),
(
"RetryError",
"LinkedIn is temporarily blocking API access",
"Wait a few minutes and try again, or use official API tools instead"
),
(
"JSONDecodeError",
"LinkedIn returned invalid data (likely bot detection)",
"Refresh cookies or try official API tools"
),
(
"401",
"Authentication expired or invalid",
"Re-authenticate: linkedin-mcp-auth oauth --force"
),
(
"403",
"Access forbidden - LinkedIn is blocking this request",
"This feature may require additional permissions or is blocked by LinkedIn"
),
(
"429",
"Rate limit exceeded",
"Wait before making more requests"
),
(
"timeout",
"Request timed out",
"LinkedIn may be slow or blocking requests. Try again later"
),
(
"connection",
"Connection error",
"Check your internet connection and try again"
),
(
"CHALLENGE",
"LinkedIn security challenge triggered",
"Log into LinkedIn in your browser to verify your account, then refresh cookies"
),
]
def interpret_error(error: str | Exception) -> dict[str, str]:
"""
Interpret a technical error and return user-friendly information.
Args:
error: The error message or exception
Returns:
Dict with 'message', 'suggestion', and 'technical' keys
"""
error_str = str(error).lower()
original_error = str(error)
for pattern, message, suggestion in ERROR_PATTERNS:
if pattern.lower() in error_str:
return {
"message": message,
"suggestion": suggestion,
"technical": original_error,
}
# Default response for unrecognized errors
return {
"message": "An unexpected error occurred",
"suggestion": "Check the technical details or try again later",
"technical": original_error,
}
def format_error_response(error: str | Exception) -> dict[str, Any]:
"""
Format an error for API response with user-friendly messaging.
Args:
error: The error message or exception
Returns:
Dict suitable for returning as an error response
"""
interpreted = interpret_error(error)
return {
"error": interpreted["message"],
"suggestion": interpreted["suggestion"],
"details": interpreted["technical"],
}