/**
* Git Packages Tool
*
* Package management tool providing comprehensive Git package operations.
* Supports both local package operations and remote provider operations.
*
* Operations: list, get, create, update, delete, publish, download
*/
import { GitCommandExecutor } from '../utils/git-command-executor.js';
import { ParameterValidator, ToolParams } from '../utils/parameter-validator.js';
import { OperationErrorHandler, ToolResult } from '../utils/operation-error-handler.js';
import { ProviderOperationHandler } from '../providers/provider-operation-handler.js';
import { ProviderConfig, ProviderOperation } from '../providers/types.js';
import { configManager } from '../config.js';
export interface GitPackagesParams extends ToolParams {
action: 'list' | 'get' | 'create' | 'update' | 'delete' | 'publish' | 'download';
// Package identification parameters
packageName?: string; // For get, create, update, delete, publish, download
packageType?: string; // For create, update (npm, container, maven, etc.)
version?: string; // For get, create, update, delete, publish, download
// Package metadata parameters
description?: string; // For create, update
visibility?: 'public' | 'private' | 'internal'; // For create, update
// Create/Update parameters
packageData?: any; // For create, update (package content/metadata)
tags?: string[]; // For create, update (package tags)
// Download parameters
downloadPath?: string; // For download (where to save package)
format?: string; // For download (package format)
// Publish parameters
registry?: string; // For publish (target registry)
// Options
force?: boolean; // For delete, update
includeVersions?: boolean; // For list, get (include version history)
// Remote operation parameters
repo?: string; // For remote operations
// List/search parameters
limit?: number; // For list (max packages to return)
query?: string; // For list (search query)
}
export class GitPackagesTool {
private gitExecutor: GitCommandExecutor;
private providerHandler?: ProviderOperationHandler;
constructor(providerConfig?: ProviderConfig) {
this.gitExecutor = new GitCommandExecutor();
if (providerConfig) {
this.providerHandler = new ProviderOperationHandler(providerConfig);
}
}
/**
* Execute git-packages operation
*/
async execute(params: GitPackagesParams): Promise<ToolResult> {
const startTime = Date.now();
try {
// Validate basic parameters
const validation = ParameterValidator.validateToolParams('git-packages', 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 = ParameterValidator.validateOperationParams('git-packages', params.action, 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
const isRemoteOperation = this.isRemoteOperation(params.action);
if (isRemoteOperation) {
return await this.executeRemoteOperation(params, startTime);
} else {
return await this.executeLocalOperation(params, startTime);
}
} 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']
);
}
}
/**
* Execute local Git package operations
*/
private async executeLocalOperation(params: GitPackagesParams, startTime: number): Promise<ToolResult> {
switch (params.action) {
case 'list':
return await this.handleListLocalPackages(params, startTime);
case 'get':
return await this.handleGetLocalPackage(params, startTime);
default:
return OperationErrorHandler.createToolError(
'UNSUPPORTED_LOCAL_OPERATION',
`Local operation '${params.action}' is not supported. Use provider for remote package operations.`,
params.action,
{},
['Specify a provider (github, gitea, or both) for remote package operations']
);
}
}
/**
* Execute remote provider operations
*/
private async executeRemoteOperation(params: GitPackagesParams, startTime: number): Promise<ToolResult> {
if (!this.providerHandler) {
return OperationErrorHandler.createToolError(
'PROVIDER_NOT_CONFIGURED',
'Provider handler is not configured for remote operations',
params.action,
{},
['Configure GitHub or Gitea provider to use remote operations']
);
}
if (!params.provider) {
if (configManager.isUniversalMode()) {
params.provider = 'both';
console.error(`[Universal Mode] Auto-applying both providers for ${params.action}`);
} else {
return OperationErrorHandler.createToolError(
'PROVIDER_REQUIRED',
'Provider parameter is required for remote package operations',
params.action,
{},
['Specify provider as: github, gitea, or both']
);
}
}
const operation: ProviderOperation = {
provider: params.provider,
operation: this.mapActionToProviderOperation(params.action),
parameters: this.extractRemoteParameters(params),
requiresAuth: true,
isRemoteOperation: true
};
try {
const result = await this.providerHandler.executeOperation(operation);
return {
success: result.success,
data: result.partialFailure ? result : result.results[0]?.data,
error: result.success ? undefined : {
code: result.errors[0]?.error?.code || 'REMOTE_OPERATION_ERROR',
message: result.errors[0]?.error?.message || 'Remote operation failed',
details: result.errors,
suggestions: ['Check provider configuration and credentials']
},
metadata: {
provider: params.provider,
operation: params.action,
timestamp: new Date().toISOString(),
executionTime: Date.now() - startTime
}
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return OperationErrorHandler.createToolError(
'REMOTE_OPERATION_ERROR',
`Remote operation failed: ${errorMessage}`,
params.action,
{ error: errorMessage },
['Check provider configuration and network connectivity']
);
}
}
/**
* Handle list local packages operation (checks for package files)
*/
private async handleListLocalPackages(params: GitPackagesParams, startTime: number): Promise<ToolResult> {
try {
const packages = [];
// Check for common package files
const packageFiles = [
{ file: 'package.json', type: 'npm' },
{ file: 'pom.xml', type: 'maven' },
{ file: 'build.gradle', type: 'gradle' },
{ file: 'Cargo.toml', type: 'cargo' },
{ file: 'composer.json', type: 'composer' },
{ file: 'setup.py', type: 'python' },
{ file: 'pyproject.toml', type: 'python' },
{ file: 'Dockerfile', type: 'container' },
{ file: 'Chart.yaml', type: 'helm' },
{ file: 'pubspec.yaml', type: 'dart' },
{ file: 'go.mod', type: 'go' }
];
for (const { file, type } of packageFiles) {
const filePath = `${params.projectPath}/${file}`;
try {
// Check if file exists using git ls-files or simple file check
const result = await this.gitExecutor.executeGitCommand(
'ls-files',
[file],
params.projectPath
);
if (result.success && result.stdout.trim()) {
// File exists in git, try to read package info
const packageInfo = await this.extractPackageInfo(params.projectPath, file, type);
if (packageInfo) {
packages.push({
type,
file,
...packageInfo,
isLocal: true
});
}
}
} catch (error) {
// Continue checking other files
continue;
}
}
return {
success: true,
data: {
packages,
total: packages.length,
note: 'These are local package definitions. Use a provider for remote package registry operations.'
},
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_PACKAGES_ERROR',
`Failed to list local packages: ${errorMessage}`,
'list',
{ error: errorMessage, projectPath: params.projectPath }
);
}
}
/**
* Handle get local package operation (gets package file info)
*/
private async handleGetLocalPackage(params: GitPackagesParams, startTime: number): Promise<ToolResult> {
try {
if (!params.packageName && !params.packageType) {
return OperationErrorHandler.createToolError(
'MISSING_PARAMETER',
'Either packageName or packageType is required for get package operation',
'get',
{},
[
'Provide packageName with the actual file path (e.g., "package.json", "pom.xml")',
'Or provide packageType to auto-detect common package files (e.g., "npm", "maven")',
'Note: packageName should be the file name, not the package name from the file'
]
);
}
// Determine package file based on name or type
let packageFile = params.packageName;
let packageType = params.packageType;
if (!packageFile && packageType) {
const typeToFile: Record<string, string> = {
'npm': 'package.json',
'maven': 'pom.xml',
'gradle': 'build.gradle',
'cargo': 'Cargo.toml',
'composer': 'composer.json',
'python': 'setup.py',
'container': 'Dockerfile',
'helm': 'Chart.yaml',
'dart': 'pubspec.yaml',
'go': 'go.mod'
};
packageFile = typeToFile[packageType];
}
if (!packageFile) {
return OperationErrorHandler.createToolError(
'INVALID_PACKAGE_TYPE',
`Unknown package type: ${packageType}`,
'get',
{ packageType },
['Use supported package types: npm, maven, gradle, cargo, composer, python, container, helm, dart, go']
);
}
// Check if package file exists
const result = await this.gitExecutor.executeGitCommand(
'ls-files',
[packageFile],
params.projectPath
);
if (!result.success || !result.stdout.trim()) {
return OperationErrorHandler.createToolError(
'PACKAGE_NOT_FOUND',
`Package file '${packageFile}' not found in repository`,
'get',
{ packageFile },
[
'Check if the package file exists in the repository',
'Verify the file is tracked by git (not in .gitignore)',
'Use correct file name (e.g., "package.json" not "@andrebuzeli/git-mcp")',
'Try using packageType parameter instead to auto-detect common files'
]
);
}
// Extract package information
const packageInfo = await this.extractPackageInfo(params.projectPath, packageFile, packageType);
if (!packageInfo) {
return OperationErrorHandler.createToolError(
'PACKAGE_READ_ERROR',
`Failed to read package information from '${packageFile}'`,
'get',
{ packageFile },
['Check if the package file is valid and readable']
);
}
return {
success: true,
data: {
file: packageFile,
type: packageType,
...packageInfo,
isLocal: true,
note: 'This is a local package definition. Use a provider for remote package registry operations.'
},
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_PACKAGE_ERROR',
`Failed to get local package information: ${errorMessage}`,
'get',
{ error: errorMessage, projectPath: params.projectPath }
);
}
}
/**
* Extract package information from package files
*/
private async extractPackageInfo(projectPath: string, file: string, type?: string): Promise<any> {
try {
const result = await this.gitExecutor.executeGitCommand(
'show',
[`HEAD:${file}`],
projectPath
);
if (!result.success) {
return null;
}
const content = result.stdout;
switch (file) {
case 'package.json':
try {
const pkg = JSON.parse(content);
return {
name: pkg.name,
version: pkg.version,
description: pkg.description,
author: pkg.author,
license: pkg.license,
dependencies: pkg.dependencies ? Object.keys(pkg.dependencies).length : 0,
devDependencies: pkg.devDependencies ? Object.keys(pkg.devDependencies).length : 0,
scripts: pkg.scripts ? Object.keys(pkg.scripts) : [],
repository: pkg.repository,
homepage: pkg.homepage
};
} catch {
return { name: 'Invalid package.json', error: 'Failed to parse JSON' };
}
case 'pom.xml':
// Basic XML parsing for Maven
const groupIdMatch = content.match(/<groupId>(.*?)<\/groupId>/);
const artifactIdMatch = content.match(/<artifactId>(.*?)<\/artifactId>/);
const versionMatch = content.match(/<version>(.*?)<\/version>/);
const nameMatch = content.match(/<name>(.*?)<\/name>/);
const descriptionMatch = content.match(/<description>(.*?)<\/description>/);
return {
groupId: groupIdMatch?.[1],
artifactId: artifactIdMatch?.[1],
name: nameMatch?.[1] || artifactIdMatch?.[1],
version: versionMatch?.[1],
description: descriptionMatch?.[1]
};
case 'Cargo.toml':
// Basic TOML parsing for Rust
const nameTomlMatch = content.match(/name\s*=\s*"([^"]+)"/);
const versionTomlMatch = content.match(/version\s*=\s*"([^"]+)"/);
const descriptionTomlMatch = content.match(/description\s*=\s*"([^"]+)"/);
const authorTomlMatch = content.match(/authors\s*=\s*\[(.*?)\]/s);
return {
name: nameTomlMatch?.[1],
version: versionTomlMatch?.[1],
description: descriptionTomlMatch?.[1],
authors: authorTomlMatch?.[1]?.split(',').map((a: string) => a.trim().replace(/"/g, ''))
};
case 'composer.json':
try {
const composer = JSON.parse(content);
return {
name: composer.name,
version: composer.version,
description: composer.description,
type: composer.type,
license: composer.license,
authors: composer.authors,
require: composer.require ? Object.keys(composer.require).length : 0,
requireDev: composer['require-dev'] ? Object.keys(composer['require-dev']).length : 0
};
} catch {
return { name: 'Invalid composer.json', error: 'Failed to parse JSON' };
}
case 'Chart.yaml':
// Basic YAML parsing for Helm
const chartNameMatch = content.match(/name:\s*(.+)/);
const chartVersionMatch = content.match(/version:\s*(.+)/);
const chartDescMatch = content.match(/description:\s*(.+)/);
const appVersionMatch = content.match(/appVersion:\s*(.+)/);
return {
name: chartNameMatch?.[1]?.trim(),
version: chartVersionMatch?.[1]?.trim(),
description: chartDescMatch?.[1]?.trim(),
appVersion: appVersionMatch?.[1]?.trim()
};
case 'go.mod':
const moduleMatch = content.match(/module\s+(.+)/);
const goVersionMatch = content.match(/go\s+(.+)/);
return {
module: moduleMatch?.[1]?.trim(),
goVersion: goVersionMatch?.[1]?.trim(),
name: moduleMatch?.[1]?.split('/').pop()
};
default:
return {
name: file,
type: type || 'unknown',
size: content.length
};
}
} catch (error) {
return null;
}
}
/**
* Check if operation is a remote operation
*/
private isRemoteOperation(action: string): boolean {
// Most package operations require remote provider APIs
const remoteOperations = ['create', 'update', 'delete', 'publish', 'download'];
return remoteOperations.includes(action);
}
/**
* Extract parameters for remote operations
*/
private extractRemoteParameters(params: GitPackagesParams): Record<string, any> {
const remoteParams: Record<string, any> = {
projectPath: params.projectPath
};
// Common parameters
if (params.repo) remoteParams.repo = params.repo;
if (params.packageName) remoteParams.package_name = params.packageName;
if (params.packageType) remoteParams.package_type = params.packageType;
if (params.version) remoteParams.version = params.version;
// Operation-specific parameters
switch (params.action) {
case 'list':
if (params.limit) remoteParams.per_page = params.limit;
if (params.packageType) remoteParams.package_type = params.packageType;
if (params.query) remoteParams.q = params.query;
if (params.includeVersions !== undefined) remoteParams.include_versions = params.includeVersions;
break;
case 'get':
// Get operations need packageName (already handled above)
if (params.includeVersions !== undefined) remoteParams.include_versions = params.includeVersions;
break;
case 'create':
if (params.description) remoteParams.description = params.description;
if (params.visibility) remoteParams.visibility = params.visibility;
if (params.packageData) remoteParams.package_data = params.packageData;
if (params.tags) remoteParams.tags = params.tags;
break;
case 'update':
if (params.description) remoteParams.description = params.description;
if (params.visibility) remoteParams.visibility = params.visibility;
if (params.packageData) remoteParams.package_data = params.packageData;
if (params.tags) remoteParams.tags = params.tags;
break;
case 'delete':
// Delete operations need packageName and version (already handled above)
break;
case 'publish':
if (params.registry) remoteParams.registry = params.registry;
if (params.packageData) remoteParams.package_data = params.packageData;
break;
case 'download':
if (params.downloadPath) remoteParams.download_path = params.downloadPath;
if (params.format) remoteParams.format = params.format;
break;
}
return remoteParams;
}
/**
* Map git-packages actions to provider operations
*/
private mapActionToProviderOperation(action: string): string {
const actionMap: Record<string, string> = {
'list': 'package-list',
'get': 'package-get',
'create': 'package-create',
'update': 'package-update',
'delete': 'package-delete',
'publish': 'package-publish',
'download': 'package-download'
};
return actionMap[action] || action;
}
/**
* Get tool schema for MCP registration
*/
static getToolSchema() {
return {
name: 'git-packages',
description: 'Git package management tool for package operations. Supports list, get, create, update, delete, publish, and download operations. Local operations work with package files, remote operations require a provider. In universal mode (GIT_MCP_MODE=universal), automatically executes on both GitHub and Gitea providers.',
inputSchema: {
type: 'object',
properties: {
action: {
type: 'string',
enum: ['list', 'get', 'create', 'update', 'delete', 'publish', 'download'],
description: 'The package operation to perform'
},
projectPath: {
type: 'string',
description: 'Absolute path to the project directory'
},
provider: {
type: 'string',
enum: ['github', 'gitea', 'both'],
description: 'Provider for remote operations (required for create, update, delete, publish, download)'
},
packageName: {
type: 'string',
description: 'Name of the package file (e.g., "package.json", "pom.xml") - NOT the package name from inside the file (required for most operations)'
},
packageType: {
type: 'string',
description: 'Type of package (npm, maven, container, etc.)'
},
version: {
type: 'string',
description: 'Package version (for get, create, update, delete, publish, download)'
},
description: {
type: 'string',
description: 'Package description (for create/update)'
},
visibility: {
type: 'string',
enum: ['public', 'private', 'internal'],
description: 'Package visibility (for create/update)'
},
packageData: {
type: 'object',
description: 'Package content/metadata (for create/update/publish)'
},
tags: {
type: 'array',
items: { type: 'string' },
description: 'Package tags (for create/update)'
},
downloadPath: {
type: 'string',
description: 'Path to save downloaded package (for download)'
},
format: {
type: 'string',
description: 'Package format for download (for download)'
},
registry: {
type: 'string',
description: 'Target registry for publishing (for publish)'
},
force: {
type: 'boolean',
description: 'Force operation (for delete, update)'
},
includeVersions: {
type: 'boolean',
description: 'Include version history (for list, get)'
},
repo: {
type: 'string',
description: 'Repository name (for remote operations)'
},
limit: {
type: 'number',
description: 'Maximum number of packages to return (for list)'
},
query: {
type: 'string',
description: 'Search query for packages (for list)'
}
},
required: ['action', 'projectPath']
}
};
}
}