Skip to main content
Glama

Commit Helper MCP

by jolfr
task-04-error-handling.md26.1 kB
# Task 4: Error Handling Standardization ## Overview **Priority**: Low **Goal**: Standardize error handling across the application **Target**: Consistent error experience with unified exception hierarchy **Status**: ✅ COMPLETED ## Completed Tasks - [x] Create errors directory structure - [x] Implement custom exception hierarchy in exceptions.py - [x] Create error handling decorators in handlers.py - [x] Implement standardized response formats in responses.py - [x] Update MCP tools to use new error handling - [x] Update services to use custom exceptions - [x] Add comprehensive error logging - [x] Add error recovery mechanisms - [x] Write tests for error handling ## Current Problem Error handling is currently inconsistent across the codebase: - Different error response formats across MCP tools - Mixed exception types and handling patterns - No centralized error logging or monitoring - Inconsistent error messages and user feedback - No standardized error recovery mechanisms This makes the code: - Difficult to debug and troubleshoot - Inconsistent user experience - Hard to implement comprehensive error monitoring - Challenging to provide helpful error recovery suggestions ## Target Structure ``` src/commit_helper_mcp/errors/ ├── __init__.py ├── exceptions.py # Custom exception hierarchy ├── handlers.py # Error handling decorators/utilities └── responses.py # Standardized error response formats ``` ## Implementation Steps ### Step 1: Create Error Handling Package **File**: `src/commit_helper_mcp/errors/__init__.py` ```python """ Error Handling for Commit Helper MCP This package provides standardized error handling with custom exceptions, consistent response formats, and comprehensive error recovery mechanisms. """ from .exceptions import ( CommitHelperMCPError, GitOperationError, ValidationError, ConfigurationError, RepositoryError, PluginError, ServiceError ) from .handlers import ( handle_errors, handle_git_errors, handle_validation_errors, handle_configuration_errors, log_error_context ) from .responses import ( ErrorResponse, create_error_response, create_success_response, format_validation_error, format_git_error ) __all__ = [ # Exceptions 'CommitHelperMCPError', 'GitOperationError', 'ValidationError', 'ConfigurationError', 'RepositoryError', 'PluginError', 'ServiceError', # Handlers 'handle_errors', 'handle_git_errors', 'handle_validation_errors', 'handle_configuration_errors', 'log_error_context', # Responses 'ErrorResponse', 'create_error_response', 'create_success_response', 'format_validation_error', 'format_git_error' ] ``` ### Step 2: Create Custom Exception Hierarchy **File**: `src/commit_helper_mcp/errors/exceptions.py` **Responsibilities**: - Define custom exception hierarchy - Provide context-rich error information - Enable specific error handling patterns - Support error recovery suggestions ```python """ Custom Exception Hierarchy Defines a comprehensive exception hierarchy for the Commitizen MCP Connector with context-rich error information and recovery suggestions. """ from typing import Dict, Any, Optional, List from dataclasses import dataclass, field @dataclass class ErrorContext: """Rich error context information.""" operation: str component: str details: Dict[str, Any] = field(default_factory=dict) suggestions: List[str] = field(default_factory=list) recovery_actions: List[str] = field(default_factory=list) user_message: Optional[str] = None class CommitHelperMCPError(Exception): """Base exception for all Commit Helper MCP errors.""" def __init__( self, message: str, context: Optional[ErrorContext] = None, cause: Optional[Exception] = None ): super().__init__(message) self.context = context or ErrorContext(operation="unknown", component="unknown") self.cause = cause def to_dict(self) -> Dict[str, Any]: """Convert exception to dictionary for serialization.""" return { "error_type": self.__class__.__name__, "message": str(self), "context": { "operation": self.context.operation, "component": self.context.component, "details": self.context.details, "suggestions": self.context.suggestions, "recovery_actions": self.context.recovery_actions, "user_message": self.context.user_message }, "cause": str(self.cause) if self.cause else None } def get_user_message(self) -> str: """Get user-friendly error message.""" return self.context.user_message or str(self) def get_suggestions(self) -> List[str]: """Get error recovery suggestions.""" return self.context.suggestions def get_recovery_actions(self) -> List[str]: """Get specific recovery actions.""" return self.context.recovery_actions class GitOperationError(CommitHelperMCPError): """Errors related to git operations.""" def __init__( self, message: str, git_command: Optional[str] = None, repo_path: Optional[str] = None, cause: Optional[Exception] = None ): context = ErrorContext( operation="git_operation", component="git_service", details={ "git_command": git_command, "repo_path": repo_path }, suggestions=[ "Check if the repository exists and is accessible", "Verify git is installed and configured", "Ensure you have proper permissions for the repository" ] ) super().__init__(message, context, cause) class ValidationError(CommitHelperMCPError): """Errors related to validation (commit messages, inputs, etc.).""" def __init__( self, message: str, validation_type: Optional[str] = None, invalid_value: Optional[str] = None, expected_format: Optional[str] = None, cause: Optional[Exception] = None ): context = ErrorContext( operation="validation", component="validation_service", details={ "validation_type": validation_type, "invalid_value": invalid_value, "expected_format": expected_format }, suggestions=[ "Check the format requirements", "Review the validation rules", "Use the get_commit_types tool to see valid options" ] ) super().__init__(message, context, cause) class ConfigurationError(CommitHelperMCPError): """Errors related to configuration loading and validation.""" def __init__( self, message: str, config_file: Optional[str] = None, config_key: Optional[str] = None, cause: Optional[Exception] = None ): context = ErrorContext( operation="configuration", component="config_service", details={ "config_file": config_file, "config_key": config_key }, suggestions=[ "Check if configuration files exist and are readable", "Verify configuration syntax is correct", "Review environment variable settings" ] ) super().__init__(message, context, cause) class RepositoryError(CommitHelperMCPError): """Errors related to repository access and validation.""" def __init__( self, message: str, repo_path: Optional[str] = None, cause: Optional[Exception] = None ): context = ErrorContext( operation="repository_access", component="repository_manager", details={ "repo_path": repo_path }, suggestions=[ "Verify the repository path exists", "Check if it's a valid git repository", "Ensure proper file permissions" ] ) super().__init__(message, context, cause) class PluginError(CommitHelperMCPError): """Errors related to Commitizen plugin operations.""" def __init__( self, message: str, plugin_name: Optional[str] = None, cause: Optional[Exception] = None ): context = ErrorContext( operation="plugin_operation", component="plugin_service", details={ "plugin_name": plugin_name }, suggestions=[ "Check if the plugin is installed", "Verify plugin configuration is correct", "Try refreshing the configuration" ] ) super().__init__(message, context, cause) class ServiceError(CommitHelperMCPError): """Errors related to service initialization and operation.""" def __init__( self, message: str, service_name: Optional[str] = None, cause: Optional[Exception] = None ): context = ErrorContext( operation="service_operation", component=service_name or "unknown_service", details={ "service_name": service_name }, suggestions=[ "Check service dependencies are available", "Verify service configuration", "Try restarting the service" ] ) super().__init__(message, context, cause) # Convenience functions for creating specific errors def create_git_error( message: str, git_command: str = None, repo_path: str = None, cause: Exception = None ) -> GitOperationError: """Create a GitOperationError with context.""" return GitOperationError(message, git_command, repo_path, cause) def create_validation_error( message: str, validation_type: str = None, invalid_value: str = None, expected_format: str = None, cause: Exception = None ) -> ValidationError: """Create a ValidationError with context.""" return ValidationError(message, validation_type, invalid_value, expected_format, cause) def create_config_error( message: str, config_file: str = None, config_key: str = None, cause: Exception = None ) -> ConfigurationError: """Create a ConfigurationError with context.""" return ConfigurationError(message, config_file, config_key, cause) ``` ### Step 3: Create Error Handlers **File**: `src/commit_helper_mcp/errors/handlers.py` **Responsibilities**: - Provide error handling decorators - Centralize error logging - Implement error recovery mechanisms - Convert exceptions to appropriate responses ```python """ Error Handlers Provides decorators and utilities for consistent error handling across the application with logging, monitoring, and recovery mechanisms. """ import logging import functools from typing import Dict, Any, Callable, Optional, Type from .exceptions import ( CommitHelperMCPError, GitOperationError, ValidationError, ConfigurationError, RepositoryError, PluginError, ServiceError ) from .responses import create_error_response logger = logging.getLogger(__name__) def handle_errors( default_response: Optional[Dict[str, Any]] = None, log_errors: bool = True, reraise: bool = False ): """ Decorator for comprehensive error handling. Args: default_response: Default response to return on error log_errors: Whether to log errors reraise: Whether to reraise exceptions after handling """ def decorator(func: Callable) -> Callable: @functools.wraps(func) def wrapper(*args, **kwargs) -> Dict[str, Any]: try: return func(*args, **kwargs) except CommitHelperMCPError as e: if log_errors: log_error_context(e, func.__name__) response = create_error_response(e) if reraise: raise return response except Exception as e: # Convert unexpected exceptions to CommitHelperMCPError mcp_error = ServiceError( f"Unexpected error in {func.__name__}: {str(e)}", service_name=func.__module__, cause=e ) if log_errors: log_error_context(mcp_error, func.__name__) response = create_error_response(mcp_error) if reraise: raise mcp_error from e return response or default_response or { "error": "An unexpected error occurred", "success": False } return wrapper return decorator def handle_git_errors(func: Callable) -> Callable: """Decorator specifically for git operation error handling.""" @functools.wraps(func) def wrapper(*args, **kwargs) -> Dict[str, Any]: try: return func(*args, **kwargs) except GitOperationError: # Already a git error, re-raise raise except Exception as e: # Convert to GitOperationError git_error = GitOperationError( f"Git operation failed in {func.__name__}: {str(e)}", cause=e ) raise git_error from e return wrapper def handle_validation_errors(func: Callable) -> Callable: """Decorator specifically for validation error handling.""" @functools.wraps(func) def wrapper(*args, **kwargs) -> Dict[str, Any]: try: return func(*args, **kwargs) except ValidationError: # Already a validation error, re-raise raise except Exception as e: # Convert to ValidationError validation_error = ValidationError( f"Validation failed in {func.__name__}: {str(e)}", cause=e ) raise validation_error from e return wrapper def handle_configuration_errors(func: Callable) -> Callable: """Decorator specifically for configuration error handling.""" @functools.wraps(func) def wrapper(*args, **kwargs) -> Dict[str, Any]: try: return func(*args, **kwargs) except ConfigurationError: # Already a configuration error, re-raise raise except Exception as e: # Convert to ConfigurationError config_error = ConfigurationError( f"Configuration error in {func.__name__}: {str(e)}", cause=e ) raise config_error from e return wrapper def log_error_context(error: CommitHelperMCPError, function_name: str): """Log error with full context information.""" logger.error( f"Error in {function_name}: {error}", extra={ "error_type": error.__class__.__name__, "operation": error.context.operation, "component": error.context.component, "details": error.context.details, "function": function_name, "cause": str(error.cause) if error.cause else None } ) def create_error_recovery_response( error: CommitHelperMCPError, additional_context: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: """Create error response with recovery information.""" response = create_error_response(error) if additional_context: response.update(additional_context) # Add recovery information if error.get_suggestions(): response["suggestions"] = error.get_suggestions() if error.get_recovery_actions(): response["recovery_actions"] = error.get_recovery_actions() return response class ErrorHandler: """Context manager for error handling.""" def __init__( self, operation: str, component: str, log_errors: bool = True, reraise: bool = True ): self.operation = operation self.component = component self.log_errors = log_errors self.reraise = reraise def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): if exc_type is None: return False if issubclass(exc_type, CommitHelperMCPError): if self.log_errors: log_error_context(exc_val, self.operation) return not self.reraise # Convert unexpected exceptions mcp_error = ServiceError( f"Unexpected error in {self.operation}: {str(exc_val)}", service_name=self.component, cause=exc_val ) if self.log_errors: log_error_context(mcp_error, self.operation) if self.reraise: raise mcp_error from exc_val return True ``` ### Step 4: Create Response Formatters **File**: `src/commit_helper_mcp/errors/responses.py` **Responsibilities**: - Standardize error response formats - Provide consistent success responses - Format errors for different contexts - Support internationalization (future) ```python """ Error Response Formatting Provides standardized response formatting for errors and success cases with consistent structure and helpful information. """ from typing import Dict, Any, Optional, List from dataclasses import dataclass, field from .exceptions import CommitHelperMCPError @dataclass class ErrorResponse: """Standardized error response structure.""" success: bool = field(default=False) error: str = field(default="") error_type: str = field(default="") details: Dict[str, Any] = field(default_factory=dict) suggestions: List[str] = field(default_factory=list) recovery_actions: List[str] = field(default_factory=list) context: Dict[str, Any] = field(default_factory=dict) def to_dict(self) -> Dict[str, Any]: """Convert to dictionary for JSON serialization.""" result = { "success": self.success, "error": self.error } if self.error_type: result["error_type"] = self.error_type if self.details: result["details"] = self.details if self.suggestions: result["suggestions"] = self.suggestions if self.recovery_actions: result["recovery_actions"] = self.recovery_actions if self.context: result["context"] = self.context return result def create_error_response( error: CommitHelperMCPError, include_context: bool = True, include_suggestions: bool = True ) -> Dict[str, Any]: """Create standardized error response from exception.""" response = ErrorResponse( success=False, error=error.get_user_message(), error_type=error.__class__.__name__ ) if include_context and error.context: response.context = { "operation": error.context.operation, "component": error.context.component } if error.context.details: response.details = error.context.details if include_suggestions: response.suggestions = error.get_suggestions() response.recovery_actions = error.get_recovery_actions() return response.to_dict() def create_success_response( data: Dict[str, Any], message: Optional[str] = None ) -> Dict[str, Any]: """Create standardized success response.""" response = { "success": True, **data } if message: response["message"] = message return response def format_validation_error( message: str, invalid_value: Optional[str] = None, expected_format: Optional[str] = None, validation_rules: Optional[List[str]] = None ) -> Dict[str, Any]: """Format validation error with helpful information.""" from .exceptions import ValidationError error = ValidationError( message=message, validation_type="format_validation", invalid_value=invalid_value, expected_format=expected_format ) if validation_rules: error.context.suggestions.extend(validation_rules) return create_error_response(error) def format_git_error( message: str, git_command: Optional[str] = None, repo_path: Optional[str] = None, exit_code: Optional[int] = None ) -> Dict[str, Any]: """Format git operation error with command context.""" from .exceptions import GitOperationError error = GitOperationError( message=message, git_command=git_command, repo_path=repo_path ) if exit_code is not None: error.context.details["exit_code"] = exit_code return create_error_response(error) def format_service_error( message: str, service_name: str, operation: Optional[str] = None ) -> Dict[str, Any]: """Format service error with service context.""" from .exceptions import ServiceError error = ServiceError( message=message, service_name=service_name ) if operation: error.context.operation = operation return create_error_response(error) def add_troubleshooting_info( response: Dict[str, Any], troubleshooting_steps: List[str] ) -> Dict[str, Any]: """Add troubleshooting information to error response.""" response = response.copy() if "suggestions" not in response: response["suggestions"] = [] response["suggestions"].extend(troubleshooting_steps) return response def add_documentation_links( response: Dict[str, Any], doc_links: Dict[str, str] ) -> Dict[str, Any]: """Add documentation links to error response.""" response = response.copy() if "context" not in response: response["context"] = {} response["context"]["documentation"] = doc_links return response ``` ## Integration with Existing Code ### Update MCP Tools to Use Error Handling **Example Integration**: ```python from ..errors import handle_errors, create_validation_error, create_git_error @mcp.tool() @handle_errors(log_errors=True) def generate_commit_message( type: str, subject: str, # ... other parameters ) -> Dict[str, Any]: try: # Existing implementation pass except ValidationError: raise create_validation_error( "Invalid commit message format", validation_type="commit_message", invalid_value=f"{type}: {subject}" ) ``` ### Update Services to Use Custom Exceptions **Example Service Update**: ```python from ..errors import GitOperationError, ValidationError, handle_git_errors class GitPythonCore: @handle_git_errors def execute_commit(self, message: str, **kwargs) -> Dict[str, Any]: try: # Existing implementation pass except Exception as e: raise GitOperationError( "Failed to execute git commit", git_command="commit", repo_path=str(self.repo_path), cause=e ) ``` ## Implementation Guidelines ### Error Handling Principles 1. **Consistent Structure**: All errors follow the same response format 2. **Rich Context**: Provide detailed context for debugging 3. **User-Friendly**: Include helpful suggestions and recovery actions 4. **Logging**: Comprehensive error logging with context 5. **Recovery**: Enable graceful error recovery where possible ### Migration Strategy 1. **Gradual Adoption**: Update tools and services incrementally 2. **Backward Compatibility**: Maintain existing error response formats initially 3. **Testing**: Comprehensive error scenario testing ## Validation Criteria ### Success Metrics - [x] All errors follow consistent response format - [x] Error messages are user-friendly and actionable - [x] Comprehensive error logging is in place - [x] Error recovery mechanisms work correctly - [x] Exception hierarchy covers all error scenarios - [x] Performance impact is minimal ## Testing Plan ### Unit Tests - Test custom exception creation and serialization - Verify error handler decorators work correctly - Test response formatting functions - Validate error context information ### Integration Tests - Test error handling across MCP tools - Verify error propagation through service layers - Test error recovery mechanisms - Validate logging output ### Error Scenario Tests - Test all major error conditions - Verify appropriate exceptions are raised - Test error response consistency - Validate user-friendly error messages ## Dependencies ### Prerequisites - Task 3 (Configuration Management) completed - Understanding of exception handling patterns - Familiarity with decorator patterns ### Affected Files - All MCP tool files (error handling integration) - All service files (exception usage) - Test files (error scenario testing) ## Estimated Effort **Time Estimate**: 15 minutes **Complexity**: Medium **Risk Level**: Low (additive changes, improves reliability) ## Next Steps After completing this task: 1. Proceed to Task 5: Test Reorganization 2. Update documentation with error handling guidelines 3. Consider adding error monitoring and alerting 4. Update development guidelines for error handling patterns

Latest Blog Posts

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/jolfr/commit-helper-mcp'

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