/**
* Git Configuration Tool
*
* Git configuration management tool providing comprehensive Git config operations.
* Supports get, set, unset, list, edit, and show operations for Git configuration.
*
* Operations: get, set, unset, list, edit, show
*/
import { GitCommandExecutor, GitCommandResult } from '../utils/git-command-executor.js';
import { ParameterValidator, ToolParams } from '../utils/parameter-validator.js';
import { OperationErrorHandler, ToolResult } from '../utils/operation-error-handler.js';
import { configManager } from '../config.js';
export interface GitConfigParams extends ToolParams {
action: 'get' | 'set' | 'unset' | 'list' | 'edit' | 'show';
// Configuration parameters
key?: string; // Configuration key (required for get, set, unset, show)
value?: string; // Configuration value (required for set)
// Scope parameters
global?: boolean; // Use global configuration
local?: boolean; // Use local repository configuration
system?: boolean; // Use system configuration
// Display options
showOrigin?: boolean; // Show configuration file origin
showScope?: boolean; // Show configuration scope
}
export class GitConfigTool {
private gitExecutor: GitCommandExecutor;
constructor() {
this.gitExecutor = new GitCommandExecutor();
}
/**
* Execute git-config operation
*/
async execute(params: GitConfigParams): Promise<ToolResult> {
const startTime = Date.now();
try {
// Validate basic parameters
const validation = ParameterValidator.validateToolParams('git-config', params);
if (!validation.isValid) {
return OperationErrorHandler.createToolError(
'VALIDATION_ERROR',
`Parameter validation failed: ${validation.errors.join(', ')}`,
params.action,
{ validationErrors: validation.errors },
validation.suggestions
);
}
// Validate operation-specific parameters
const operationValidation = this.validateOperationParams(params);
if (!operationValidation.isValid) {
return OperationErrorHandler.createToolError(
'VALIDATION_ERROR',
`Operation validation failed: ${operationValidation.errors.join(', ')}`,
params.action,
{ validationErrors: operationValidation.errors },
operationValidation.suggestions
);
}
// Route to appropriate handler
switch (params.action) {
case 'get':
return await this.handleGet(params, startTime);
case 'set':
return await this.handleSet(params, startTime);
case 'unset':
return await this.handleUnset(params, startTime);
case 'list':
return await this.handleList(params, startTime);
case 'edit':
return await this.handleEdit(params, startTime);
case 'show':
return await this.handleShow(params, startTime);
default:
return OperationErrorHandler.createToolError(
'UNSUPPORTED_OPERATION',
`Operation '${params.action}' is not supported`,
params.action,
{},
['Use one of: get, set, unset, list, edit, show']
);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return OperationErrorHandler.createToolError(
'EXECUTION_ERROR',
`Failed to execute ${params.action}: ${errorMessage}`,
params.action,
{ error: errorMessage },
['Check the error details and try again']
);
}
}
/**
* Validate operation-specific parameters
*/
private validateOperationParams(params: GitConfigParams): { isValid: boolean; errors: string[]; suggestions: string[] } {
const errors: string[] = [];
const suggestions: string[] = [];
// Validate key requirement for specific operations
if (['get', 'set', 'unset'].includes(params.action) && !params.key) {
errors.push(`Key parameter is required for ${params.action} operation`);
suggestions.push('Provide a configuration key (e.g., user.name, user.email)');
}
// Validate value requirement for set operation
if (params.action === 'set' && !params.value) {
errors.push('Value parameter is required for set operation');
suggestions.push('Provide a configuration value to set');
}
// Validate scope conflicts
const scopeCount = [params.global, params.local, params.system].filter(Boolean).length;
if (scopeCount > 1) {
errors.push('Only one scope can be specified (global, local, or system)');
suggestions.push('Choose either --global, --local, or --system, not multiple');
}
// Validate key format
if (params.key && !this.isValidConfigKey(params.key)) {
errors.push('Invalid configuration key format');
suggestions.push('Use format like: user.name, core.editor, remote.origin.url');
}
return {
isValid: errors.length === 0,
errors,
suggestions
};
}
/**
* Validate configuration key format
*/
private isValidConfigKey(key: string): boolean {
// Git config keys should contain at least one dot and valid characters
return /^[a-zA-Z][a-zA-Z0-9-]*(\.[a-zA-Z][a-zA-Z0-9-]*)+$/.test(key);
}
/**
* Handle get configuration operation
*/
private async handleGet(params: GitConfigParams, startTime: number): Promise<ToolResult> {
try {
const result = await this.gitExecutor.getConfig(
params.projectPath,
params.key!,
{
global: params.global,
local: params.local,
system: params.system
}
);
if (!result.success) {
// Check if it's a "not found" error
if (result.stderr.includes('not found') || result.exitCode === 1) {
return {
success: true,
data: {
key: params.key,
value: null,
found: false,
message: `Configuration key '${params.key}' not found`,
scope: this.getScopeString(params)
},
metadata: {
operation: 'get',
timestamp: new Date().toISOString(),
executionTime: Date.now() - startTime
}
};
}
return OperationErrorHandler.handleGitError(result.stderr, 'get config', params.projectPath);
}
return {
success: true,
data: {
key: params.key,
value: result.value,
found: true,
scope: this.getScopeString(params),
raw: result.stdout
},
metadata: {
operation: 'get',
timestamp: new Date().toISOString(),
executionTime: Date.now() - startTime
}
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return OperationErrorHandler.createToolError(
'GET_CONFIG_ERROR',
`Failed to get configuration: ${errorMessage}`,
'get',
{ error: errorMessage, key: params.key }
);
}
}
/**
* Handle set configuration operation
*/
private async handleSet(params: GitConfigParams, startTime: number): Promise<ToolResult> {
try {
const result = await this.gitExecutor.setConfig(
params.projectPath,
params.key!,
params.value!,
{
global: params.global,
local: params.local,
system: params.system
}
);
if (!result.success) {
return OperationErrorHandler.handleGitError(result.stderr, 'set config', params.projectPath);
}
return {
success: true,
data: {
message: 'Configuration set successfully',
key: params.key,
value: params.value,
scope: this.getScopeString(params),
output: result.stdout
},
metadata: {
operation: 'set',
timestamp: new Date().toISOString(),
executionTime: Date.now() - startTime
}
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return OperationErrorHandler.createToolError(
'SET_CONFIG_ERROR',
`Failed to set configuration: ${errorMessage}`,
'set',
{ error: errorMessage, key: params.key, value: params.value }
);
}
}
/**
* Handle unset configuration operation
*/
private async handleUnset(params: GitConfigParams, startTime: number): Promise<ToolResult> {
try {
const result = await this.gitExecutor.unsetConfig(
params.projectPath,
params.key!,
{
global: params.global,
local: params.local,
system: params.system
}
);
if (!result.success) {
// Check if it's a "not found" error
if (result.stderr.includes('not found') || result.exitCode === 5) {
return {
success: true,
data: {
key: params.key,
found: false,
message: `Configuration key '${params.key}' was not set`,
scope: this.getScopeString(params)
},
metadata: {
operation: 'unset',
timestamp: new Date().toISOString(),
executionTime: Date.now() - startTime
}
};
}
return OperationErrorHandler.handleGitError(result.stderr, 'unset config', params.projectPath);
}
return {
success: true,
data: {
message: 'Configuration unset successfully',
key: params.key,
scope: this.getScopeString(params),
output: result.stdout
},
metadata: {
operation: 'unset',
timestamp: new Date().toISOString(),
executionTime: Date.now() - startTime
}
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return OperationErrorHandler.createToolError(
'UNSET_CONFIG_ERROR',
`Failed to unset configuration: ${errorMessage}`,
'unset',
{ error: errorMessage, key: params.key }
);
}
}
/**
* Handle list configuration operation
*/
private async handleList(params: GitConfigParams, startTime: number): Promise<ToolResult> {
try {
const result = await this.gitExecutor.listConfig(
params.projectPath,
{
global: params.global,
local: params.local,
system: params.system,
showOrigin: params.showOrigin
}
);
if (!result.success) {
return OperationErrorHandler.handleGitError(result.stderr, 'list config', params.projectPath);
}
const configs = result.configs || [];
const groupedConfigs = this.groupConfigsBySection(configs);
return {
success: true,
data: {
message: `Found ${configs.length} configuration entries`,
scope: this.getScopeString(params),
totalCount: configs.length,
configs: configs,
groupedConfigs: groupedConfigs,
showOrigin: params.showOrigin,
raw: result.stdout
},
metadata: {
operation: 'list',
timestamp: new Date().toISOString(),
executionTime: Date.now() - startTime
}
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return OperationErrorHandler.createToolError(
'LIST_CONFIG_ERROR',
`Failed to list configuration: ${errorMessage}`,
'list',
{ error: errorMessage }
);
}
}
/**
* Handle edit configuration operation
*/
private async handleEdit(params: GitConfigParams, startTime: number): Promise<ToolResult> {
try {
const result = await this.gitExecutor.editConfig(
params.projectPath,
{
global: params.global,
local: params.local,
system: params.system
}
);
if (!result.success) {
return OperationErrorHandler.handleGitError(result.stderr, 'edit config', params.projectPath);
}
return {
success: true,
data: {
message: 'Configuration editor opened successfully',
scope: this.getScopeString(params),
note: 'Configuration file was opened in the default editor',
output: result.stdout
},
metadata: {
operation: 'edit',
timestamp: new Date().toISOString(),
executionTime: Date.now() - startTime
}
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return OperationErrorHandler.createToolError(
'EDIT_CONFIG_ERROR',
`Failed to edit configuration: ${errorMessage}`,
'edit',
{ error: errorMessage }
);
}
}
/**
* Handle show configuration operation
*/
private async handleShow(params: GitConfigParams, startTime: number): Promise<ToolResult> {
try {
const result = await this.gitExecutor.showConfig(
params.projectPath,
params.key,
{
global: params.global,
local: params.local,
system: params.system,
showOrigin: params.showOrigin,
showScope: params.showScope
}
);
if (!result.success) {
// Check if it's a "not found" error for specific key
if (params.key && (result.stderr.includes('not found') || result.exitCode === 1)) {
return {
success: true,
data: {
key: params.key,
found: false,
message: `Configuration key '${params.key}' not found`,
scope: this.getScopeString(params)
},
metadata: {
operation: 'show',
timestamp: new Date().toISOString(),
executionTime: Date.now() - startTime
}
};
}
return OperationErrorHandler.handleGitError(result.stderr, 'show config', params.projectPath);
}
const configs = result.configs || [];
const groupedConfigs = params.key ? undefined : this.groupConfigsBySection(configs);
return {
success: true,
data: {
message: params.key
? `Configuration for '${params.key}'`
: `Configuration details (${configs.length} entries)`,
key: params.key,
scope: this.getScopeString(params),
totalCount: configs.length,
configs: configs,
groupedConfigs: groupedConfigs,
showOrigin: params.showOrigin,
showScope: params.showScope,
raw: result.stdout
},
metadata: {
operation: 'show',
timestamp: new Date().toISOString(),
executionTime: Date.now() - startTime
}
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return OperationErrorHandler.createToolError(
'SHOW_CONFIG_ERROR',
`Failed to show configuration: ${errorMessage}`,
'show',
{ error: errorMessage, key: params.key }
);
}
}
/**
* Get scope string for display
*/
private getScopeString(params: GitConfigParams): string {
if (params.global) return 'global';
if (params.local) return 'local';
if (params.system) return 'system';
return 'default';
}
/**
* Group configurations by section
*/
private groupConfigsBySection(configs: Array<{ key: string; value: string; origin?: string; scope?: string }>): Record<string, Array<{ key: string; value: string; origin?: string; scope?: string }>> {
const grouped: Record<string, Array<{ key: string; value: string; origin?: string; scope?: string }>> = {};
for (const config of configs) {
const dotIndex = config.key.indexOf('.');
const section = dotIndex > 0 ? config.key.substring(0, dotIndex) : 'other';
if (!grouped[section]) {
grouped[section] = [];
}
grouped[section].push(config);
}
return grouped;
}
/**
* Get tool schema for MCP registration
*/
static getToolSchema() {
return {
name: 'git-config',
description: 'Git configuration management tool for get, set, unset, list, edit, and show operations. Supports global, local, and system configuration scopes.',
inputSchema: {
type: 'object',
properties: {
action: {
type: 'string',
enum: ['get', 'set', 'unset', 'list', 'edit', 'show'],
description: 'The Git configuration operation to perform'
},
projectPath: {
type: 'string',
description: 'Absolute path to the project directory'
},
key: {
type: 'string',
description: 'Configuration key (required for get, set, unset, show operations). Examples: user.name, user.email, core.editor'
},
value: {
type: 'string',
description: 'Configuration value (required for set operation)'
},
global: {
type: 'boolean',
description: 'Use global configuration (~/.gitconfig)'
},
local: {
type: 'boolean',
description: 'Use local repository configuration (.git/config)'
},
system: {
type: 'boolean',
description: 'Use system configuration (/etc/gitconfig)'
},
showOrigin: {
type: 'boolean',
description: 'Show configuration file origin (for list and show operations)'
},
showScope: {
type: 'boolean',
description: 'Show configuration scope (for show operation)'
}
},
required: ['action', 'projectPath']
}
};
}
}