Skip to main content
Glama
git-packages.ts24.5 kB
/** * 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'] } }; } }

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/Andre-Buzeli/git-mcp'

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