When you start working with AI APIs, one of the most important skills to master is handling errors gracefully. Imagine you are building an application that relies on AI responses, and suddenly the API returns an error. Without proper error handling, your entire application crashes. This guide will walk you through everything you need to know about MCP (Model Context Protocol) error handling and retry mechanisms, using practical examples you can copy and run today.

If you are new to AI development, I recommend starting with a reliable provider. Sign up here for HolySheep AI, which offers competitive pricing at just $1 per dollar (saving you 85%+ compared to the industry average of ¥7.3), supports WeChat and Alipay payments, delivers responses in under 50ms latency, and provides free credits upon registration.

Understanding MCP Error Types

Before diving into code, let's understand what types of errors you might encounter when working with MCP. Think of errors as different types of traffic signals in your code—some are warnings (yellow), some are temporary blocks (red), and some are serious issues that require attention.

Common HTTP Status Codes in MCP

When you make a request to an MCP endpoint, the server responds with a status code. Here are the most common ones you will see:

Screenshot hint: When you see an error in your terminal or console, look at the status code first. It usually appears at the top of the error message, often highlighted in red.

Setting Up Your Environment

Let us start from scratch. First, you need to install Python if you have not already. Download it from python.org and make sure to check the box that says "Add Python to PATH" during installation.

Next, install the requests library, which we will use to communicate with the MCP server:

pip install requests

Now, let us set up your environment variables. Create a new file called config.py and add your HolySheep API key:

# config.py
import os

HolySheep AI Configuration

HOLYSHEEP_API_KEY = "YOUR_HOLYSHEEP_API_KEY" HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1"

Set environment variable for easy access

os.environ['HOLYSHEEP_API_KEY'] = HOLYSHEEP_API_KEY os.environ['HOLYSHEEP_BASE_URL'] = HOLYSHEEP_BASE_URL print("Configuration loaded successfully!") print(f"Using base URL: {HOLYSHEEP_BASE_URL}")

Replace YOUR_HOLYSHEEP_API_KEY with your actual key from the HolySheep dashboard. If you do not have an account yet, sign up here to get free credits to practice with.

Building Your First Error Handler

I remember the first time I encountered an API error in production—it was a 429 rate limit error that brought down my entire application at 3 AM. That experience taught me the importance of robust error handling. Let me share what I learned so you do not have to make the same mistakes.

Create a new file called error_handler.py and paste this complete error handling solution:

# error_handler.py
import requests
import time
import json
from typing import Optional, Dict, Any

class MCPToolError(Exception):
    """Custom exception for MCP tool errors"""
    def __init__(self, message: str, status_code: int = None, retry_after: int = None):
        self.message = message
        self.status_code = status_code
        self.retry_after = retry_after
        super().__init__(self.message)

def handle_mcp_error(response: requests.Response) -> MCPToolError:
    """
    Parse MCP error response and create appropriate exception.
    
    Args:
        response: The requests Response object
        
    Returns:
        MCPToolError with detailed information
    """
    try:
        error_data = response.json()
        error_message = error_data.get('error', {}).get('message', 'Unknown error')
        error_type = error_data.get('error', {}).get('type', 'unknown')
    except json.JSONDecodeError:
        error_message = response.text or 'Unknown error'
        error_type = 'parse_error'
    
    retry_after = None
    if 'retry_after' in response.headers:
        retry_after = int(response.headers['retry_after'])
    elif error_type == 'rate_limit_error':
        retry_after = 60  # Default 60 seconds for rate limits
    
    return MCPToolError(
        message=f"{error_type}: {error_message}",
        status_code=response.status_code,
        retry_after=retry_after
    )

def call_mcp_tool(
    base_url: str,
    api_key: str,
    tool_name: str,
    parameters: Dict[str, Any],
    max_retries: int = 3,
    initial_delay: float = 1.0,
    backoff_factor: float = 2.0
) -> Dict[str, Any]:
    """
    Call an MCP tool with comprehensive error handling and retry logic.
    
    Args:
        base_url: The MCP server base URL
        api_key: Your API key
        tool_name: Name of the tool to call
        parameters: Parameters to pass to the tool
        max_retries: Maximum number of retry attempts
        initial_delay: Initial delay between retries in seconds
        backoff_factor: Multiplier for delay after each retry
        
    Returns:
        The tool response as a dictionary
        
    Raises:
        MCPToolError: If the request fails after all retries
    """
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json"
    }
    
    endpoint = f"{base_url}/tools/{tool_name}"
    delay = initial_delay
    
    for attempt in range(max_retries):
        try:
            print(f"Attempt {attempt + 1}/{max_retries}: Calling {tool_name}...")
            
            response = requests.post(
                endpoint,
                headers=headers,
                json=parameters,
                timeout=30
            )
            
            # Handle successful response
            if response.status_code == 200:
                print(f"Success! Received response in {response.elapsed.total_seconds():.3f}s")
                return response.json()
            
            # Handle rate limiting with retry
            if response.status_code == 429:
                error = handle_mcp_error(response)
                if attempt < max_retries - 1:
                    wait_time = error.retry_after or delay
                    print(f"Rate limited. Waiting {wait_time} seconds before retry...")
                    time.sleep(wait_time)
                    delay *= backoff_factor
                    continue
                else:
                    raise error
            
            # Handle server errors with retry
            if response.status_code >= 500:
                if attempt < max_retries - 1:
                    print(f"Server error ({response.status_code}). Retrying in {delay} seconds...")
                    time.sleep(delay)
                    delay *= backoff_factor
                    continue
                else:
                    error = handle_mcp_error(response)
                    raise error
            
            # Handle client errors (4xx except 429) - no retry
            error = handle_mcp_error(response)
            raise error
            
        except requests.exceptions.Timeout:
            if attempt < max_retries - 1:
                print(f"Request timed out. Retrying in {delay} seconds...")
                time.sleep(delay)
                delay *= backoff_factor
                continue
            else:
                raise MCPToolError("Request timed out after all retries", status_code=408)
                
        except requests.exceptions.ConnectionError as e:
            if attempt < max_retries - 1:
                print(f"Connection error: {e}. Retrying in {delay} seconds...")
                time.sleep(delay)
                delay *= backoff_factor
                continue
            else:
                raise MCPToolError(f"Failed to connect: {str(e)}", status_code=503)
                
        except requests.exceptions.RequestException as e:
            raise MCPToolError(f"Request failed: {str(e)}", status_code=0)
    
    raise MCPToolError("Max retries exceeded", status_code=0)

Example usage

if __name__ == "__main__": import os api_key = os.environ.get('HOLYSHEEP_API_KEY') base_url = os.environ.get('HOLYSHEEP_BASE_URL') if not api_key or api_key == "YOUR_HOLYSHEEP_API_KEY": print("Please set your HOLYSHEEP_API_KEY in config.py") else: try: result = call_mcp_tool( base_url=base_url, api_key=api_key, tool_name="text-generation", parameters={ "prompt": "Explain quantum computing in simple terms", "max_tokens": 500, "temperature": 0.7 } ) print(f"Result: {json.dumps(result, indent=2)[:500]}") except MCPToolError as e: print(f"MCP Error: {e.message}") if e.status_code: print(f"Status Code: {e.status_code}") if e.retry_after: print(f"Retry After: {e.retry_after} seconds")

Screenshot hint: When you run this script, you should see output like "Attempt 1/3: Calling text-generation..." followed by either "Success!" or error messages with specific status codes.

Implementing Exponential Backoff

Exponential backoff is a smart retry strategy where you wait progressively longer between each retry. Instead of waiting the same amount of time, you double (or multiply) the wait time after each failed attempt. This prevents overwhelming the server while giving it time to recover.

Here is an advanced implementation with jitter (random variation) to prevent thundering herd problems:

# retry_strategy.py
import random
import time
from functools import wraps
from typing import Callable, Any

class RetryStrategy:
    """
    Configurable retry strategy with exponential backoff and jitter.
    
    Attributes:
        max_retries: Maximum number of retry attempts
        base_delay: Initial delay in seconds
        max_delay: Maximum delay cap in seconds
        exponential_base: Multiplier for exponential growth
        jitter: Whether to add random jitter (0.0 to 1.0)
    """
    
    def __init__(
        self,
        max_retries: int = 5,
        base_delay: float = 1.0,
        max_delay: float = 60.0,
        exponential_base: float = 2.0,
        jitter: float = 0.1
    ):
        self.max_retries = max_retries
        self.base_delay = base_delay
        self.max_delay = max_delay
        self.exponential_base = exponential_base
        self.jitter = jitter
    
    def calculate_delay(self, attempt: int) -> float:
        """
        Calculate delay for a given attempt number.
        
        Args:
            attempt: The current retry attempt (0-indexed)
            
        Returns:
            Delay in seconds
        """
        # Exponential backoff: base_delay * (exponential_base ^ attempt)
        delay = self.base_delay * (self.exponential_base ** attempt)
        
        # Cap at maximum delay
        delay = min(delay, self.max_delay)
        
        # Add jitter if enabled
        if self.jitter > 0:
            jitter_range = delay * self.jitter
            delay += random.uniform(-jitter_range, jitter_range)
        
        return max(0.1, delay)  # Minimum 100ms delay
    
    def execute_with_retry(self, func: Callable, *args, **kwargs) -> Any:
        """
        Execute a function with retry logic.
        
        Args:
            func: The function to execute
            *args: Positional arguments for the function
            **kwargs: Keyword arguments for the function
            
        Returns:
            The function result
            
        Raises:
            The last exception if all retries fail
        """
        last_exception = None
        
        for attempt in range(self.max_retries + 1):
            try:
                result = func(*args, **kwargs)
                
                if attempt > 0:
                    print(f"✓ Success after {attempt} retries")
                
                return result
                
            except Exception as e:
                last_exception = e
                
                if attempt < self.max_retries:
                    delay = self.calculate_delay(attempt)
                    print(f"✗ Attempt {attempt + 1} failed: {str(e)}")
                    print(f"  Waiting {delay:.2f}s before retry...")
                    time.sleep(delay)
                else:
                    print(f"✗ All {self.max_retries + 1} attempts failed")
        
        raise last_exception

def with_retry(strategy: RetryStrategy = None):
    """
    Decorator to add retry logic to any function.
    
    Usage:
        @with_retry(RetryStrategy(max_retries=3))
        def my_function():
            ...
    """
    if strategy is None:
        strategy = RetryStrategy()
    
    def decorator(func: Callable) -> Callable:
        @wraps(func)
        def wrapper(*args, **kwargs):
            return strategy.execute_with_retry(func, *args, **kwargs)
        return wrapper
    return decorator

Advanced rate-limit-aware retry strategy

class RateLimitAwareRetry(RetryStrategy): """ Extended retry strategy that respects server-provided rate limits. """ def execute_with_retry(self, func: Callable, *args, **kwargs) -> Any: last_exception = None for attempt in range(self.max_retries + 1): try: result = func(*args, **kwargs) if attempt > 0: print(f"✓ Success after {attempt} retries") return result except Exception as e: last_exception = e error_str = str(e).lower() # Check for rate limit indicators retry_after = None # Common rate limit indicators if '429' in error_str or 'rate limit' in error_str: # Try to extract retry-after value import re match = re.search(r'retry[-_\s]?after[:\s]+(\d+)', error_str) if match: retry_after = int(match.group(1)) else: retry_after = 60 # Default 60 seconds if attempt < self.max_retries: if retry_after: delay = retry_after print(f"✗ Rate limited. Respecting server request to wait {delay}s") else: delay = self.calculate_delay(attempt) print(f"✗ Attempt {attempt + 1} failed: {str(e)}") print(f" Waiting {delay:.2f}s before retry...") time.sleep(delay) else: print(f"✗ All {self.max_retries + 1} attempts failed") raise last_exception

Example usage

if __name__ == "__main__": import os import requests # Create a retry strategy strategy = RateLimitAwareRetry( max_retries=5, base_delay=2.0, max_delay=120.0, jitter=0.2 ) # Test the retry logic with a mock API call attempt_count = 0 def mock_api_call(): global attempt_count attempt_count += 1 print(f" API call attempt {attempt_count}") # Simulate random failures if attempt_count < 3: raise Exception("Simulated error: 429 Rate limit exceeded") return {"status": "success", "attempts": attempt_count} print("Testing retry strategy with simulated failures:") result = strategy.execute_with_retry(mock_api_call) print(f"Result: {result}") # Reset counter attempt_count = 0 print("\n" + "="*50) print("Testing with @with_retry decorator:") @with_retry(RetryStrategy(max_retries=3, base_delay=1.0)) def decorated_function(): global attempt_count attempt_count += 1 print(f" Decorated function attempt {attempt_count}") if attempt_count < 2: raise Exception("Simulated transient error") return {"message": "Success with decorator!", "attempts": attempt_count} result = decorated_function() print(f"Result: {result}")

Handling Specific MCP Error Scenarios

Let us now look at real-world scenarios you will encounter and how to handle them properly.

Scenario 1: Authentication Failures

Authentication errors happen when your API key is missing, invalid, or expired. These should never be retried blindly—instead, log the issue and alert the developer immediately.

# auth_handler.py
import requests
from typing import Dict, Any, Optional

def handle_auth_error(
    api_key: str,
    base_url: str = "https://api.holysheep.ai/v1"
) -> Dict[str, Any]:
    """
    Handle authentication-related errors.
    
    Args:
        api_key: Your API key
        base_url: The base URL of the API
        
    Returns:
        Dictionary with authentication status and details
    """
    result = {
        "authenticated": False,
        "error": None,
        "error_type": None,
        "suggestions": []
    }
    
    # Check if key is empty or placeholder
    if not api_key or api_key == "YOUR_HOLYSHEEP_API_KEY":
        result["error"] = "API key is missing or is a placeholder value"
        result["error_type"] = "missing_key"
        result["suggestions"] = [
            "Generate an API key from your HolySheep dashboard",
            "Copy the key exactly as shown (no extra spaces)",
            "Store the key in environment variables, not in code"
        ]
        return result
    
    # Try to validate the key with a simple API call
    try:
        headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        }
        
        # Use the models endpoint to validate authentication
        response = requests.get(
            f"{base_url}/models",
            headers=headers,
            timeout=10
        )
        
        if response.status_code == 200:
            result["authenticated"] = True
            result["available_models"] = response.json().get("data", [])
            return result
        
        elif response.status_code == 401:
            result["error"] = "Invalid API key"
            result["error_type"] = "invalid_key"
            result["suggestions"] = [
                "Verify your API key is correct",
                "Check if your account is active",
                "Generate a new API key if needed"
            ]
            return result
        
        elif response.status_code == 403:
            result["error"] = "API key does not have required permissions"
            result["error_type"] = "insufficient_permissions"
            result["suggestions"] = [
                "Check if your plan includes API access",
                "Upgrade your subscription if needed"
            ]
            return result
        
        else:
            result["error"] = f"Unexpected response: {response.status_code}"
            result["error_type"] = "unexpected_response"
            return result
            
    except requests.exceptions.Timeout:
        result["error"] = "Authentication check timed out"
        result["error_type"] = "timeout"
        return result
        
    except requests.exceptions.ConnectionError:
        result["error"] = "Cannot connect to authentication service"
        result["error_type"] = "connection_error"
        result["suggestions"] = [
            "Check your internet connection",
            "Verify the API endpoint URL is correct"
        ]
        return result

Practical usage example

def authenticated_mcp_request( endpoint: str, api_key: str, method: str = "POST", data: Optional[Dict] = None ) -> Dict[str, Any]: """ Make an authenticated MCP request with proper error handling. """ # First validate authentication auth_result = handle_auth_error(api_key) if not auth_result["authenticated"]: return { "success": False, "error": auth_result["error"], "error_type": auth_result["error_type"], "suggestions": auth_result["suggestions"] } # Proceed with the actual request headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } try: if method.upper() == "POST": response = requests.post(endpoint, headers=headers, json=data, timeout=30) else: response = requests.get(endpoint, headers=headers, timeout=30) return { "success": True, "status_code": response.status_code, "data": response.json() if response.headers.get("content-type", "").startswith("application/json") else response.text } except Exception as e: return { "success": False, "error": str(e), "error_type": "request_failed" }

Run examples

if __name__ == "__main__": import os # Test with invalid key print("Testing with placeholder key:") result = handle_auth_error("YOUR_HOLYSHEEP_API_KEY") print(f"Authenticated: {result['authenticated']}") print(f"Error: {result['error']}") print(f"Suggestions: {result['suggestions']}") print("\n" + "="*50) print("Testing with real key:") real_key = os.environ.get('HOLYSHEEP_API_KEY') if real_key and real_key != "YOUR_HOLYSHEEP_API_KEY": result = handle_auth_error(real_key) print(f"Authenticated: {result['authenticated']}") if result['authenticated']: print(f"Available models: {len(result.get('available_models', []))}") else: print("No real API key found in environment. Skipping real test.")

Scenario 2: Rate Limiting with Smart Wait

Rate limits are common when working with AI APIs. HolySheep AI offers competitive pricing that helps you stay well within limits while saving significantly compared to other providers. Here is how to handle rate limits gracefully:

# rate_limit_handler.py
import time
import requests
from datetime import datetime, timedelta
from collections import deque
from threading import Lock

class RateLimitHandler:
    """
    Smart rate limit handler that tracks request patterns
    and automatically throttles requests.
    
    Features:
    - Tracks requests per time window
    - Calculates optimal delay between requests
    - Respects server-provided rate limit headers
    - Thread-safe for concurrent applications
    """
    
    def __init__(
        self,
        requests_per_minute: int = 60,
        burst_size: int = 10,
        auto_adjust: bool = True
    ):
        self.requests_per_minute = requests_per_minute
        self.burst_size = burst_size
        self.auto_adjust = auto_adjust
        
        # Track request timestamps
        self.request_history = deque(maxlen=1000)
        self.lock = Lock()
        
        # Server-reported limits
        self.server_limit = None
        self.server_remaining = None
        self.server_reset_time = None
        
        # Calculate optimal delay
        self.min_delay = 60.0 / requests_per_minute
    
    def record_request(self, response: requests.Response = None):
        """
        Record a request and update rate limit tracking.
        
        Args:
            response: Optional response object to extract rate limit headers
        """
        with self.lock:
            now = datetime.now()
            self.request_history.append(now)
            
            # Extract server rate limit info from headers
            if response is not None:
                if 'x-ratelimit-limit' in response.headers:
                    self.server_limit = int(response.headers['x-ratelimit-limit'])
                
                if 'x-ratelimit-remaining' in response.headers:
                    self.server_remaining = int(response.headers['x-ratelimit-remaining'])
                
                if 'x-ratelimit-reset' in response.headers:
                    reset_timestamp = int(response.headers['x-ratelimit-reset'])
                    self.server_reset_time = datetime.fromtimestamp(reset_timestamp)
                
                # Auto-adjust based on server feedback
                if self.auto_adjust and self.server_remaining is not None:
                    if self.server_remaining < 10:
                        # Getting close to limit, slow down
                        self.requests_per_minute = min(
                            self.requests_per_minute,
                            self.server_remaining * 2
                        )
                        self.min_delay = 60.0 / max(self.requests_per_minute, 1)
    
    def should_wait(self) -> tuple[bool, float]:
        """
        Check if we should wait before making the next request.
        
        Returns:
            Tuple of (should_wait, wait_duration)
        """
        with self.lock:
            now = datetime.now()
            cutoff = now - timedelta(minutes=1)
            
            # Remove old requests from history
            while self.request_history and self.request_history[0] < cutoff:
                self.request_history.popleft()
            
            # Count requests in the last minute
            recent_requests = len(self.request_history)
            
            # Check if we are at the rate limit
            if recent_requests >= self.requests_per_minute:
                # Calculate wait time until oldest request expires
                oldest = self.request_history[0]
                wait_time = (oldest + timedelta(minutes=1) - now).total_seconds()
                return True, max(0.1, wait_time)
            
            # Check server-reported remaining quota
            if self.server_remaining is not None and self.server_remaining <= 0:
                if self.server_reset_time:
                    wait_time = (self.server_reset_time - now).total_seconds()
                    return True, max(0.1, wait_time)
            
            # Check burst limit
            recent_burst = sum(
                1 for ts in self.request_history
                if ts > now - timedelta(seconds=10)
            )
            
            if recent_burst >= self.burst_size:
                wait_time = 10.0 / self.burst_size
                return True, wait_time
            
            return False, 0.0
    
    def wait_if_needed(self):
        """Wait if necessary to respect rate limits."""
        should_wait, wait_time = self.should_wait()
        
        if should_wait:
            print(f"Rate limit handler: waiting {wait_time:.2f}s")
            time.sleep(wait_time)
    
    def make_request(
        self,
        url: str,
        headers: dict,
        method: str = "POST",
        data: dict = None,
        max_retries: int = 3
    ):
        """
        Make a request with automatic rate limit handling.
        """
        self.wait_if_needed()
        
        for attempt in range(max_retries):
            try:
                if method.upper() == "POST":
                    response = requests.post(
                        url, headers=headers, json=data, timeout=30
                    )
                else:
                    response = requests.get(url, headers=headers, timeout=30)
                
                # Record this request
                self.record_request(response)
                
                # Handle rate limit response
                if response.status_code == 429:
                    retry_after = int(response.headers.get('retry-after', 60))
                    
                    if attempt < max_retries - 1:
                        print(f"Rate limited. Waiting {retry_after}s before retry...")
                        time.sleep(retry_after)
                        continue
                    else:
                        raise Exception(f"Rate limited after {max_retries} retries. Retry after: {retry_after}s")
                
                return response
                
            except Exception as e:
                if attempt < max_retries - 1:
                    wait_time = 2 ** attempt
                    print(f"Request failed: {e}. Retrying in {wait_time}s...")
                    time.sleep(wait_time)
                else:
                    raise
        
        raise Exception("Max retries exceeded")

Usage example

if __name__ == "__main__": handler = RateLimitHandler(requests_per_minute=30) # Simulate making requests for i in range(5): print(f"Making request {i + 1}...") # In real usage, you would call: # response = handler.make_request( # url="https://api.holysheep.ai/v1/chat/completions", # headers={"Authorization": f"Bearer {api_key}"}, # data={"model": "gpt-4", "messages": [...]} # ) # Simulate the response print(f" Request {i + 1} completed at {datetime.now().strftime('%H:%M:%S')}") # Simulate recording a response class MockResponse: headers = { 'x-ratelimit-limit': '60', 'x-ratelimit-remaining': '55', 'x-ratelimit-reset': str(int(time.time()) + 60) } handler.record_request(MockResponse())

Creating a Production-Ready MCP Client

Now let us combine everything into a robust, production-ready MCP client that handles all edge cases:

# production_mcp_client.py
import time
import requests
import logging
from typing import Dict, Any, Optional, List, Callable
from dataclasses import dataclass
from enum import Enum

Configure logging

logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger('MCPClient') class ErrorSeverity(Enum): """Severity levels for error classification.""" LOW = "low" # Non-critical, can be retried immediately MEDIUM = "medium" # Temporary issues, retry with backoff HIGH = "high" # Configuration or auth issues, manual intervention needed CRITICAL = "critical" # Data loss risk, stop immediately @dataclass class MCPError: """Structured error representation.""" message: str status_code: Optional[int] error_type: str severity: ErrorSeverity retryable: bool retry_after: Optional[int] = None suggestions: Optional[List[str]] = None class ProductionMCPClient: """ Production-ready MCP client with comprehensive error handling, retry logic, rate limiting, and monitoring. Features: - Automatic retry with exponential backoff - Rate limiting with smart throttling - Detailed error classification - Request/response logging - Circuit breaker pattern for cascading failures """ def __init__( self, api_key: str, base_url: str = "https://api.holysheep.ai/v1", max_retries: int = 5, timeout: int = 30, rate_limit_rpm: int = 60 ): self.api_key = api_key self.base_url = base_url self.max_retries = max_retries self.timeout = timeout self.rate_limiter = RateLimitHandler(requests_per_minute=rate_limit_rpm) # Circuit breaker state self.failure_count = 0 self.failure_threshold = 5 self.circuit_open = False self.circuit_reset_time = None # Monitoring self.total_requests = 0 self.successful_requests = 0 self.failed_requests = 0 # Error callback for custom handling self.error_callback: Optional[Callable[[MCPError], None]] = None def _classify_error(self, status_code: int, response_text: str) -> MCPError: """Classify an error and determine severity and retryability.""" error_info = { 400: { "type": "bad_request", "severity": ErrorSeverity.HIGH, "retryable": False, "suggestions": [ "Check request parameters", "Validate JSON format", "Review API documentation" ] }, 401: { "type": "authentication_error", "severity": ErrorSeverity.CRITICAL, "retryable": False, "suggestions": [ "Verify API key is correct", "Check if subscription is active", "Generate new API key if compromised" ] }, 403: { "type": "forbidden", "severity": ErrorSeverity.CRITICAL, "retryable": False, "suggestions": [ "Check account permissions", "Verify subscription plan includes this endpoint" ] }, 404: { "type": "not_found", "severity": ErrorSeverity.HIGH, "retryable": False, "suggestions": [ "Verify endpoint URL is correct", "Check if resource still exists" ] }, 429: { "type": "rate_limit", "severity": ErrorSeverity.MEDIUM, "retryable": True, "suggestions": [ "Implement request queuing", "Consider upgrading your plan" ] }, 500: { "type": "server_error", "severity": ErrorSeverity.MEDIUM, "retryable": True, "suggestions": [ "Server issue, retry later", "Contact support if persistent" ] }, 502: { "type": "bad_gateway", "severity": ErrorSeverity.MEDIUM, "retryable": True, "suggestions": [ "Temporary server issue", "Retry with exponential backoff" ] }, 503: { "type": "service_unavailable", "severity": ErrorSeverity.MEDIUM, "retryable": True, "suggestions": [ "Service temporarily down", "Check status page for ongoing incidents" ] } } info = error_info.get(status_code, { "type": "unknown_error", "severity": ErrorSeverity.MEDIUM, "retryable": True, "suggestions": ["Contact support with error details"] }) return MCPError( message=f"HTTP {status_code}: {response_text[:200]}", status_code=status_code, error_type=info["type"], severity=info["severity"], retryable=info["retryable"], suggestions=info["suggestions"] ) def _update_circuit_breaker(self, success: bool): """Update circuit breaker state.""" if success: self.failure_count = 0 self.circuit_open = False else: self.failure_count += 1 if self.failure_count >= self.failure_threshold: self.circuit_open = True self.circuit_reset_time = time.time() + 60 # Reset after 60 seconds logger.warning("Circuit breaker OPEN - too many failures") def _check_circuit_breaker