/**
* Operation Error Handler
*
* Provides detailed error messages with configuration guidance.
* Handles different types of errors and provides actionable solutions.
*/
export interface ErrorResponse {
error: {
code: string;
message: string;
details?: any;
suggestions: string[];
configurationGuide?: {
requiredEnvVars: string[];
exampleConfig: Record<string, string>;
setupInstructions: string[];
};
};
}
export interface ToolResult {
success: boolean;
data?: any;
error?: {
code: string;
message: string;
details?: any;
suggestions?: string[];
};
metadata?: {
provider?: string;
operation: string;
timestamp: string;
executionTime: number;
};
}
export class OperationErrorHandler {
/**
* Creates a standardized error response
*/
public static createErrorResponse(
code: string,
message: string,
details?: any,
suggestions: string[] = []
): ErrorResponse {
return {
error: {
code,
message,
details,
suggestions,
},
};
}
/**
* Creates a tool result with error information
*/
public static createToolError(
code: string,
message: string,
operation: string,
details?: any,
suggestions: string[] = []
): ToolResult {
return {
success: false,
error: {
code,
message,
details,
suggestions,
},
metadata: {
operation,
timestamp: new Date().toISOString(),
executionTime: 0,
},
};
}
/**
* Handles configuration errors with setup guidance
*/
public static handleConfigurationError(
provider?: 'github' | 'gitea' | 'both',
operation?: string
): ErrorResponse {
const response = this.createErrorResponse(
'CONFIGURATION_ERROR',
'Provider configuration is missing or invalid',
{ provider, operation }
);
if (provider === 'github' || provider === 'both') {
response.error.suggestions.push('Configure GitHub: Set GITHUB_TOKEN and GITHUB_USERNAME environment variables');
}
if (provider === 'gitea' || provider === 'both') {
response.error.suggestions.push('Configure Gitea: Set GITEA_URL, GITEA_TOKEN, and GITEA_USERNAME environment variables');
}
response.error.configurationGuide = {
requiredEnvVars: this.getRequiredEnvVars(provider),
exampleConfig: this.getExampleConfig(provider),
setupInstructions: this.getSetupInstructions(provider),
};
return response;
}
/**
* Handles validation errors with parameter guidance
*/
public static handleValidationError(
errors: string[],
suggestions: string[] = [],
toolName?: string,
action?: string
): ErrorResponse {
const response = this.createErrorResponse(
'VALIDATION_ERROR',
`Parameter validation failed: ${errors.join(', ')}`,
{ toolName, action, validationErrors: errors },
suggestions
);
// Add general parameter guidance
response.error.suggestions.push('Ensure all required parameters are provided');
response.error.suggestions.push('Check parameter formats and types');
if (toolName && action) {
response.error.suggestions.push(`See documentation for ${toolName} ${action} parameters`);
}
return response;
}
/**
* Handles Git command errors
*/
public static handleGitError(
gitError: string,
operation: string,
projectPath?: string
): ToolResult {
let code = 'GIT_ERROR';
let message = `Git operation failed: ${gitError}`;
const suggestions: string[] = [];
// Analyze common Git errors and provide specific guidance
if (gitError.includes('not a git repository')) {
code = 'NOT_GIT_REPOSITORY';
message = 'The specified directory is not a Git repository';
suggestions.push('Initialize a Git repository with: git init');
suggestions.push('Or navigate to an existing Git repository');
} else if (gitError.includes('nothing to commit')) {
code = 'NOTHING_TO_COMMIT';
message = 'No changes to commit';
suggestions.push('Make changes to files before committing');
suggestions.push('Use git status to see current repository state');
} else if (gitError.includes('merge conflict')) {
code = 'MERGE_CONFLICT';
message = 'Merge conflict detected';
suggestions.push('Resolve conflicts in the affected files');
suggestions.push('Use git status to see conflicted files');
suggestions.push('After resolving, use git add and git commit');
} else if (gitError.includes('remote origin already exists')) {
code = 'REMOTE_EXISTS';
message = 'Remote origin already exists';
suggestions.push('Use git remote set-url origin <url> to change the remote URL');
suggestions.push('Or use git remote remove origin to remove the existing remote');
} else if (gitError.includes('Permission denied')) {
code = 'PERMISSION_DENIED';
message = 'Permission denied for Git operation';
suggestions.push('Check your SSH keys or authentication credentials');
suggestions.push('Ensure you have write access to the repository');
} else if (gitError.includes('branch already exists')) {
code = 'BRANCH_EXISTS';
message = 'Branch already exists';
suggestions.push('Use a different branch name');
suggestions.push('Or delete the existing branch first');
suggestions.push('Check existing branches with: git branch -a');
} else if (gitError.includes('branch not found') || gitError.includes('not found in upstream')) {
code = 'BRANCH_NOT_FOUND';
message = 'Branch not found';
suggestions.push('Check if the branch name is correct');
suggestions.push('Use git branch -a to see all available branches');
suggestions.push('Ensure the branch exists on the remote repository');
} else if (gitError.includes('cannot lock ref')) {
code = 'REF_LOCK_ERROR';
message = 'Cannot lock Git reference';
suggestions.push('Another Git operation might be in progress');
suggestions.push('Wait for other operations to complete');
suggestions.push('If stuck, try: git gc --prune=now');
} else if (gitError.includes('detached HEAD')) {
code = 'DETACHED_HEAD';
message = 'Repository is in detached HEAD state';
suggestions.push('Create a new branch: git checkout -b <branch-name>');
suggestions.push('Or switch to an existing branch: git checkout <branch-name>');
suggestions.push('Or merge to main: git checkout main && git merge <commit-hash>');
} else if (gitError.includes('working tree is dirty')) {
code = 'DIRTY_WORKING_TREE';
message = 'Working directory has uncommitted changes';
suggestions.push('Commit your changes: git add . && git commit -m "message"');
suggestions.push('Or stash your changes: git stash');
suggestions.push('Or discard changes: git checkout -- .');
} else if (gitError.includes('upstream branch')) {
code = 'UPSTREAM_BRANCH_ERROR';
message = 'Upstream branch configuration issue';
suggestions.push('Set upstream branch: git branch --set-upstream-to=origin/<branch>');
suggestions.push('Or push with upstream: git push --set-upstream origin <branch>');
} else if (gitError.includes('fast-forward')) {
code = 'FAST_FORWARD_ERROR';
message = 'Cannot fast-forward merge';
suggestions.push('Use merge: git merge --no-ff <branch>');
suggestions.push('Or rebase: git rebase <branch>');
suggestions.push('Or force push: git push --force-with-lease');
} else if (gitError.includes('untracked files')) {
code = 'UNTRACKED_FILES';
message = 'Untracked files would be overwritten';
suggestions.push('Add untracked files: git add <files>');
suggestions.push('Or remove untracked files: git clean -fd');
suggestions.push('Or stash untracked files: git stash -u');
}
return this.createToolError(code, message, operation, { gitError, projectPath }, suggestions);
}
/**
* Handles provider API errors
*/
public static handleProviderError(
provider: 'github' | 'gitea',
apiError: any,
operation: string
): ToolResult {
let code = 'PROVIDER_ERROR';
let message = `${provider} API error`;
const suggestions: string[] = [];
// Handle common API errors
if (apiError.status === 401) {
code = 'AUTHENTICATION_ERROR';
message = `Authentication failed for ${provider}`;
suggestions.push(`Check your ${provider.toUpperCase()}_TOKEN environment variable`);
suggestions.push('Ensure the token has the required permissions');
suggestions.push('Verify the token is not expired');
} else if (apiError.status === 403) {
code = 'PERMISSION_ERROR';
message = `Permission denied for ${provider} operation`;
suggestions.push('Check if you have the required permissions for this operation');
suggestions.push('Verify the repository exists and you have access to it');
} else if (apiError.status === 404) {
code = 'NOT_FOUND';
message = `Resource not found on ${provider}`;
suggestions.push('Check if the repository, issue, or pull request exists');
suggestions.push('Verify the repository name and owner are correct');
} else if (apiError.status === 422) {
code = 'VALIDATION_ERROR';
message = `${provider} validation error`;
suggestions.push('Check the request parameters');
suggestions.push('Ensure all required fields are provided');
} else if (apiError.status >= 500) {
code = 'SERVER_ERROR';
message = `${provider} server error`;
suggestions.push('This is a temporary server issue, try again later');
suggestions.push('Check the provider status page for known issues');
}
return this.createToolError(
code,
message,
operation,
{ provider, apiError: apiError.message || apiError },
suggestions
);
}
/**
* Handles network and connectivity errors
*/
public static handleNetworkError(
networkError: any,
operation: string,
provider?: string
): ToolResult {
let code = 'NETWORK_ERROR';
let message = 'Network connectivity error';
const suggestions: string[] = [];
if (networkError.code === 'ENOTFOUND') {
code = 'DNS_ERROR';
message = 'DNS resolution failed';
suggestions.push('Check your internet connection');
suggestions.push('Verify the server URL is correct');
} else if (networkError.code === 'ECONNREFUSED') {
code = 'CONNECTION_REFUSED';
message = 'Connection refused by server';
suggestions.push('Check if the server is running');
suggestions.push('Verify the port and URL are correct');
} else if (networkError.code === 'ETIMEDOUT') {
code = 'TIMEOUT_ERROR';
message = 'Request timed out';
suggestions.push('Check your internet connection');
suggestions.push('Try again later - the server may be overloaded');
}
return this.createToolError(
code,
message,
operation,
{ networkError: networkError.message || networkError, provider },
suggestions
);
}
/**
* Handles file system errors
*/
public static handleFileSystemError(
fsError: any,
operation: string,
filePath?: string
): ToolResult {
let code = 'FILESYSTEM_ERROR';
let message = 'File system operation failed';
const suggestions: string[] = [];
if (fsError.code === 'ENOENT') {
code = 'FILE_NOT_FOUND';
message = 'File or directory not found';
suggestions.push('Check if the file path is correct');
suggestions.push('Ensure the file or directory exists');
} else if (fsError.code === 'EACCES') {
code = 'PERMISSION_DENIED';
message = 'Permission denied for file operation';
suggestions.push('Check file permissions');
suggestions.push('Run with appropriate user permissions');
} else if (fsError.code === 'EEXIST') {
code = 'FILE_EXISTS';
message = 'File or directory already exists';
suggestions.push('Use a different name or remove the existing file');
suggestions.push('Use update operation instead of create');
}
return this.createToolError(
code,
message,
operation,
{ fsError: fsError.message || fsError, filePath },
suggestions
);
}
/**
* Gets required environment variables for a provider
*/
private static getRequiredEnvVars(provider?: 'github' | 'gitea' | 'both'): string[] {
const envVars: string[] = [];
if (provider === 'github' || provider === 'both') {
envVars.push('GITHUB_TOKEN', 'GITHUB_USERNAME');
}
if (provider === 'gitea' || provider === 'both') {
envVars.push('GITEA_URL', 'GITEA_TOKEN', 'GITEA_USERNAME');
}
if (!provider) {
envVars.push('GITHUB_TOKEN', 'GITHUB_USERNAME', 'GITEA_URL', 'GITEA_TOKEN', 'GITEA_USERNAME');
}
return envVars;
}
/**
* Gets example configuration for a provider
*/
private static getExampleConfig(provider?: 'github' | 'gitea' | 'both'): Record<string, string> {
const config: Record<string, string> = {};
if (provider === 'github' || provider === 'both' || !provider) {
config.GITHUB_TOKEN = 'ghp_xxxxxxxxxxxxxxxxxxxx';
config.GITHUB_USERNAME = 'your-github-username';
}
if (provider === 'gitea' || provider === 'both' || !provider) {
config.GITEA_URL = 'https://gitea.example.com';
config.GITEA_TOKEN = 'your-gitea-token';
config.GITEA_USERNAME = 'your-gitea-username';
}
return config;
}
/**
* Gets setup instructions for a provider
*/
private static getSetupInstructions(provider?: 'github' | 'gitea' | 'both'): string[] {
const instructions: string[] = [];
if (provider === 'github' || provider === 'both' || !provider) {
instructions.push('1. For GitHub: Create a personal access token at https://github.com/settings/tokens');
instructions.push('2. Set GITHUB_TOKEN and GITHUB_USERNAME environment variables');
}
if (provider === 'gitea' || provider === 'both' || !provider) {
instructions.push('3. For Gitea: Create an access token in your Gitea instance settings');
instructions.push('4. Set GITEA_URL, GITEA_TOKEN, and GITEA_USERNAME environment variables');
}
instructions.push('5. Restart the MCP server after setting environment variables');
instructions.push('6. At least one provider must be configured for remote operations');
return instructions;
}
}