Skip to main content
Glama

MaverickMCP

by wshobson
MIT License
165
  • Apple
agent_errors.py10.5 kB
""" Agent-friendly error handler with helpful fix suggestions. This module provides decorators and utilities to catch common errors and provide actionable solutions for agents. """ import asyncio import functools import traceback from collections.abc import Callable from typing import Any, TypeVar from maverick_mcp.utils.logging import get_logger logger = get_logger(__name__) T = TypeVar("T") # Common error patterns and their fixes ERROR_FIXES = { # DataFrame column errors "KeyError.*close": { "error": "DataFrame column 'close' not found", "fix": "Use 'Close' with capital C - DataFrame columns are case-sensitive", "example": "df['Close'] not df['close']", }, "KeyError.*open": { "error": "DataFrame column 'open' not found", "fix": "Use 'Open' with capital O", "example": "df['Open'] not df['open']", }, "KeyError.*high": { "error": "DataFrame column 'high' not found", "fix": "Use 'High' with capital H", "example": "df['High'] not df['high']", }, "KeyError.*low": { "error": "DataFrame column 'low' not found", "fix": "Use 'Low' with capital L", "example": "df['Low'] not df['low']", }, "KeyError.*volume": { "error": "DataFrame column 'volume' not found", "fix": "Use 'Volume' with capital V", "example": "df['Volume'] not df['volume']", }, # Authentication errors "401.*Unauthorized": { "error": "Authentication required", "fix": "Set AUTH_ENABLED=false for development or use generate_dev_token tool", "example": "AUTH_ENABLED=false python -m maverick_mcp.api.server", }, # Connection errors "Redis.*Connection.*refused": { "error": "Redis connection failed", "fix": "Start Redis: brew services start redis", "example": "Or set REDIS_HOST=none to skip caching", }, "psycopg2.*could not connect": { "error": "PostgreSQL connection failed", "fix": "Use SQLite for development: DATABASE_URL=sqlite:///dev.db", "example": "Or start PostgreSQL: brew services start postgresql", }, # Import errors "ModuleNotFoundError.*maverick": { "error": "Maverick MCP modules not found", "fix": "Install dependencies: uv sync", "example": "Make sure you're in the project root directory", }, "ImportError.*ta_lib": { "error": "TA-Lib not installed", "fix": "Install TA-Lib: brew install ta-lib && uv pip install ta-lib", "example": "TA-Lib requires system libraries", }, # Type errors "TypeError.*NoneType.*has no attribute": { "error": "Trying to access attribute on None", "fix": "Check if the object exists before accessing attributes", "example": "if obj is not None: obj.attribute", }, # Value errors "ValueError.*not enough values to unpack": { "error": "Unpacking mismatch", "fix": "Check the return value - it might be None or have fewer values", "example": "result = func(); if result: a, b = result", }, # Async errors "RuntimeError.*no running event loop": { "error": "Async function called without event loop", "fix": "Use asyncio.run() or await in async context", "example": "asyncio.run(async_function())", }, # File errors "FileNotFoundError": { "error": "File not found", "fix": "Check the file path - use absolute paths for reliability", "example": "Path(__file__).parent / 'data.csv'", }, # Port errors "Address already in use.*8000": { "error": "Port 8000 already in use", "fix": "Stop the existing server: make stop", "example": "Or use a different port: --port 8001", }, } def find_error_fix(error_str: str) -> dict[str, str] | None: """Find a fix suggestion for the given error string.""" import re error_str_lower = str(error_str).lower() for pattern, fix_info in ERROR_FIXES.items(): if re.search(pattern.lower(), error_str_lower): return fix_info return None def agent_friendly_errors[T]( func: Callable[..., T] | None = None, *, provide_fix: bool = True, log_errors: bool = True, reraise: bool = True, ) -> Callable[..., T] | Callable[[Callable[..., T]], Callable[..., T]]: """ Decorator that catches errors and provides helpful fix suggestions. Args: provide_fix: Whether to include fix suggestions log_errors: Whether to log errors reraise: Whether to re-raise the error after logging Usage: @agent_friendly_errors def my_function(): ... @agent_friendly_errors(reraise=False) def my_function(): ... """ def decorator(f: Callable[..., T]) -> Callable[..., T]: @functools.wraps(f) def wrapper(*args: Any, **kwargs: Any) -> T: try: return f(*args, **kwargs) except Exception as e: error_msg = str(e) error_type = type(e).__name__ # Build error info error_info = { "function": f.__name__, "error_type": error_type, "error_message": error_msg, } # Find fix suggestion if provide_fix: fix_info = find_error_fix(error_msg) if fix_info: error_info["fix_suggestion"] = fix_info # Log the error if log_errors: logger.error( f"Error in {f.__name__}: {error_type}: {error_msg}", extra=error_info, exc_info=True, ) if fix_info: logger.info( f"💡 Fix suggestion: {fix_info['fix']}", extra={"example": fix_info.get("example", "")}, ) # Create enhanced error message if fix_info and provide_fix: enhanced_msg = ( f"{error_msg}\n\n" f"💡 Fix: {fix_info['fix']}\n" f"Example: {fix_info.get('example', '')}" ) # Replace the error message e.args = (enhanced_msg,) + e.args[1:] if reraise: raise # Return error info if not re-raising return error_info # type: ignore[return-value] # Add async support if asyncio.iscoroutinefunction(f): @functools.wraps(f) async def async_wrapper(*args: Any, **kwargs: Any) -> T: try: return await f(*args, **kwargs) except Exception as e: # Same error handling logic error_msg = str(e) error_type = type(e).__name__ error_info = { "function": f.__name__, "error_type": error_type, "error_message": error_msg, } if provide_fix: fix_info = find_error_fix(error_msg) if fix_info: error_info["fix_suggestion"] = fix_info if log_errors: logger.error( f"Error in {f.__name__}: {error_type}: {error_msg}", extra=error_info, exc_info=True, ) if fix_info: logger.info( f"💡 Fix suggestion: {fix_info['fix']}", extra={"example": fix_info.get("example", "")}, ) if fix_info and provide_fix: enhanced_msg = ( f"{error_msg}\n\n" f"💡 Fix: {fix_info['fix']}\n" f"Example: {fix_info.get('example', '')}" ) e.args = (enhanced_msg,) + e.args[1:] if reraise: raise return error_info # type: ignore[return-value] return async_wrapper # type: ignore[return-value] return wrapper # Handle being called with or without parentheses if func is None: return decorator else: return decorator(func) # Context manager for agent-friendly error handling class AgentErrorContext: """Context manager that provides helpful error messages.""" def __init__(self, operation: str = "operation"): self.operation = operation def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): if exc_type is not None: error_msg = str(exc_val) fix_info = find_error_fix(error_msg) if fix_info: logger.error( f"Error during {self.operation}: {exc_type.__name__}: {error_msg}" ) logger.info( f"💡 Fix: {fix_info['fix']}", extra={"example": fix_info.get("example", "")}, ) # Don't suppress the exception return False return False # Utility function to get common error context def get_error_context(error: Exception) -> dict[str, Any]: """Extract useful context from an error.""" context = { "error_type": type(error).__name__, "error_message": str(error), "traceback": traceback.format_exc().split("\n"), } # Add specific context based on error type if isinstance(error, KeyError): context["key"] = error.args[0] if error.args else "unknown" elif isinstance(error, ValueError): context["value_error_details"] = error.args elif isinstance(error, ConnectionError): context["connection_type"] = "network" elif hasattr(error, "response"): # HTTP errors context["status_code"] = getattr(error.response, "status_code", None) context["response_text"] = getattr(error.response, "text", None) return context

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/wshobson/maverick-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server