Skip to main content
Glama
parameter-validator.ts28.9 kB
/** * Parameter Validation * * Provides robust input validation for all Git MCP operations. * Validates required parameters, formats, and operation support. */ import path from 'path'; import { configManager } from '../config.js'; export interface ValidationResult { isValid: boolean; errors: string[]; warnings: string[]; suggestions: string[]; } export interface ToolParams { action: string; projectPath: string; provider?: 'github' | 'gitea' | 'both'; [key: string]: any; } export class ParameterValidator { private static readonly VALID_PROVIDERS = ['github', 'gitea', 'both']; private static readonly TOOL_OPERATIONS: Record<string, string[]> = { 'git-workflow': ['init', 'commit', 'sync', 'status', 'backup', 'push', 'pull', 'create', 'list', 'get', 'update', 'delete', 'fork', 'search'], 'git-files': ['read', 'search', 'backup', 'list'], 'git-branches': ['create', 'list', 'get', 'delete', 'merge', 'compare'], 'git-issues': ['create', 'list', 'get', 'update', 'close', 'comment', 'search'], 'git-pulls': ['create', 'list', 'get', 'update', 'merge', 'close', 'review', 'search'], 'git-tags': ['create', 'list', 'get', 'delete', 'search'], 'git-release': ['create', 'list', 'get', 'update', 'delete', 'publish', 'download'], 'git-remote': ['add', 'remove', 'rename', 'show', 'set-url', 'prune', 'list'], 'git-reset': ['soft', 'mixed', 'hard', 'reset-to-commit', 'reset-branch'], 'git-stash': ['stash', 'pop', 'apply', 'list', 'show', 'drop', 'clear'], 'git-config': ['get', 'set', 'unset', 'list', 'edit', 'show'], 'git-monitor': ['log', 'status', 'commits', 'contributors'], 'git-backup': ['backup', 'restore', 'list', 'verify'], 'git-archive': ['create', 'extract', 'list', 'verify'], 'git-packages': ['list', 'get', 'create', 'update', 'delete', 'publish', 'download'], 'git-analytics': ['stats', 'commits', 'contributors'], 'git-sync': ['sync', 'status'], 'git-update': ['update', 'history', 'changelog', 'track', 'sync-providers', 'status', 'rollback', 'compare'], 'git-history': ['log', 'track', 'sync', 'export', 'auto'] }; private static readonly REMOTE_OPERATIONS: Record<string, string[]> = { 'git-workflow': ['create', 'list', 'get', 'update', 'delete', 'fork', 'search'], // git-files operations are local by default, remote only when provider is explicitly specified (read-only only) 'git-files': [], 'git-branches': [], // All branch operations are local for now 'git-issues': ['create', 'list', 'get', 'update', 'close', 'comment', 'search'], 'git-pulls': ['create', 'list', 'get', 'update', 'merge', 'close', 'review', 'search'], 'git-tags': [], // All tag operations are local 'git-release': ['create', 'list', 'get', 'update', 'delete', 'publish', 'download'], 'git-remote': [], // Remote operations are local Git commands 'git-reset': [], // Reset operations are local 'git-stash': [], // Stash operations are local 'git-config': [], // Config operations are local 'git-monitor': [], // Monitor operations are local 'git-backup': [], // Backup operations are local 'git-archive': [], // Archive operations are local 'git-packages': ['create', 'update', 'delete', 'publish', 'download'], // list and get are local 'git-analytics': [], // All analytics operations are local by default 'git-sync': [], // All sync operations are local by default 'git-update': ['sync-providers'], // Only sync-providers is remote 'git-history': ['sync'] // sync operation can be remote when using API method }; /** * Validates basic tool parameters */ public static validateToolParams(toolName: string, params: ToolParams): ValidationResult { const result: ValidationResult = { isValid: true, errors: [], warnings: [], suggestions: [] }; // Validate required parameters if (!params.projectPath) { result.errors.push('projectPath is required for all operations'); result.suggestions.push('Provide the absolute path to your project directory'); result.isValid = false; } if (!params.action) { result.errors.push('action is required to specify the operation to perform'); result.suggestions.push(`Valid actions for ${toolName}: ${this.TOOL_OPERATIONS[toolName]?.join(', ') || 'unknown tool'}`); result.isValid = false; } // Validate tool exists if (!this.TOOL_OPERATIONS[toolName]) { result.errors.push(`Unknown tool: ${toolName}`); result.suggestions.push(`Available tools: ${Object.keys(this.TOOL_OPERATIONS).join(', ')}`); result.isValid = false; return result; } // Validate action is supported by tool if (params.action && !this.TOOL_OPERATIONS[toolName].includes(params.action)) { result.errors.push(`Action '${params.action}' is not supported by ${toolName}`); result.suggestions.push(`Valid actions: ${this.TOOL_OPERATIONS[toolName].join(', ')}`); result.isValid = false; } // Validate provider parameter if (params.provider && !this.VALID_PROVIDERS.includes(params.provider)) { result.errors.push(`Invalid provider: ${params.provider}`); result.suggestions.push(`Valid providers: ${this.VALID_PROVIDERS.join(', ')}`); result.isValid = false; } // Check if provider is required for remote operations const toolRemoteOps = this.REMOTE_OPERATIONS[toolName] || []; if (params.action && toolRemoteOps.includes(params.action) && !params.provider) { // In universal mode, provider is auto-injected, so don't require it if (!configManager.isUniversalMode()) { result.errors.push(`Provider is required for remote operation: ${params.action}`); result.suggestions.push(`Specify provider as one of: ${this.VALID_PROVIDERS.join(', ')}`); result.isValid = false; } } // Validate projectPath format if (params.projectPath && typeof params.projectPath !== 'string') { result.errors.push('projectPath must be a string'); result.isValid = false; } // Check for common parameter format issues if (params.projectPath && params.projectPath.includes('\\') && !params.projectPath.includes(':\\')) { result.warnings.push('projectPath contains backslashes - ensure it is a valid absolute path'); } // Validate URLs format if (params.provider === 'gitea' && params.projectPath) { // This is a basic check - more specific validation would be in the operation-specific validator if (params.projectPath.startsWith('http://') || params.projectPath.startsWith('https://')) { result.warnings.push('projectPath appears to be a URL - ensure you are using the local repository path'); } } // Validate branch names format if (params.action && ['create', 'delete', 'merge'].includes(params.action) && params.branch) { if (typeof params.branch !== 'string') { result.errors.push('branch parameter must be a string'); result.isValid = false; } else { // Basic branch name validation if (params.branch.includes('..') || params.branch.includes('~') || params.branch.includes('^')) { result.warnings.push('branch name contains special characters - ensure it is a valid branch name'); } if (params.branch.startsWith('-')) { result.errors.push('branch name cannot start with a hyphen'); result.isValid = false; } if (params.branch.includes(' ')) { result.warnings.push('branch name contains spaces - consider using hyphens or underscores'); } } } // Validate commit hash format if (params.commit && typeof params.commit === 'string') { if (params.commit.length < 7 || params.commit.length > 40) { result.warnings.push('commit hash appears to be invalid format (should be 7-40 characters)'); } if (!/^[a-f0-9]+$/i.test(params.commit)) { result.errors.push('commit hash contains invalid characters (only hex digits allowed)'); result.isValid = false; } } // Validate token format (basic check) if (params.token && typeof params.token === 'string') { if (params.token.length < 20) { result.warnings.push('token appears to be too short - ensure it is a valid API token'); } if (params.token.includes(' ')) { result.errors.push('token contains spaces - ensure it is a valid token without spaces'); result.isValid = false; } } return result; } /** * Validates specific operation parameters */ public static validateOperationParams(toolName: string, action: string, params: ToolParams): ValidationResult { const result: ValidationResult = { isValid: true, errors: [], warnings: [], suggestions: [] }; // Tool-specific validations switch (toolName) { case 'git-workflow': return this.validateWorkflowParams(action, params); case 'git-files': return this.validateFilesParams(action, params); case 'git-branches': return this.validateBranchesParams(action, params); case 'git-tags': return this.validateTagsParams(action, params); case 'git-issues': return this.validateIssuesParams(action, params); case 'git-pulls': return this.validatePullsParams(action, params); case 'git-sync': return this.validateSyncParams(action, params); case 'git-files': return this.validateFilesParams(action, params); case 'git-update': return this.validateUpdateParams(action, params); default: // Generic validation for other tools return result; } } private static validateWorkflowParams(action: string, params: ToolParams): ValidationResult { const result: ValidationResult = { isValid: true, errors: [], warnings: [], suggestions: [] }; switch (action) { case 'commit': if (!params.message) { result.errors.push('message is required for commit operation'); result.suggestions.push('Provide a commit message describing your changes'); result.isValid = false; } break; case 'create': if (!params.name) { result.errors.push('name is required for repository creation'); result.suggestions.push('Provide a repository name'); result.isValid = false; } break; case 'get': case 'update': case 'delete': case 'fork': if (!params.repo) { result.errors.push('repo is required for repository operations'); result.suggestions.push('Provide repository name'); result.isValid = false; } break; case 'search': if (!params.query) { result.errors.push('query is required for search operation'); result.suggestions.push('Provide a search query string'); result.isValid = false; } break; case 'push': // Validate push parameters if (params.force) { result.warnings.push('Force push will override remote changes. Use with caution.'); } break; case 'pull': // Validate pull parameters if (params.strategy && !['merge', 'rebase', 'fast-forward'].includes(params.strategy)) { result.errors.push('Invalid pull strategy. Must be one of: merge, rebase, fast-forward'); result.suggestions.push('Use strategy: "merge", "rebase", or "fast-forward"'); result.isValid = false; } break; case 'backup': if (params.backupPath && typeof params.backupPath !== 'string') { result.errors.push('backupPath must be a string'); result.isValid = false; } break; case 'sync': if (params.remote && typeof params.remote !== 'string') { result.errors.push('remote must be a string'); result.isValid = false; } if (params.branch && typeof params.branch !== 'string') { result.errors.push('branch must be a string'); result.isValid = false; } break; } return result; } private static validateBranchesParams(action: string, params: ToolParams): ValidationResult { const result: ValidationResult = { isValid: true, errors: [], warnings: [], suggestions: [] }; switch (action) { case 'create': if (!params.branchName) { result.errors.push('branchName is required for branch creation'); result.suggestions.push('Provide a name for the new branch'); result.isValid = false; } break; case 'get': case 'delete': case 'merge': if (!params.branchName) { result.errors.push('branchName is required for branch operation'); result.suggestions.push('Provide the name of the branch to operate on'); result.isValid = false; } break; case 'compare': if (!params.baseBranch || !params.compareBranch) { result.errors.push('baseBranch and compareBranch are required for comparison'); result.suggestions.push('Provide both branch names to compare'); result.isValid = false; } break; case 'list': // list operation doesn't require any additional parameters break; } return result; } private static validateTagsParams(action: string, params: ToolParams): ValidationResult { const result: ValidationResult = { isValid: true, errors: [], warnings: [], suggestions: [] }; switch (action) { case 'create': if (!params.tagName) { result.errors.push('tagName is required for tag creation'); result.suggestions.push('Provide a name for the new tag'); result.isValid = false; } break; case 'get': case 'delete': if (!params.tagName) { result.errors.push('tagName is required for tag operation'); result.suggestions.push('Provide the name of the tag to operate on'); result.isValid = false; } break; case 'search': if (!params.query) { result.errors.push('query is required for tag search'); result.suggestions.push('Provide a search query to find tags'); result.isValid = false; } break; case 'list': // list operation doesn't require any additional parameters break; } return result; } private static validateIssuesParams(action: string, params: ToolParams): ValidationResult { const result: ValidationResult = { isValid: true, errors: [], warnings: [], suggestions: [] }; switch (action) { case 'create': if (!params.title) { result.errors.push('title is required for issue creation'); result.suggestions.push('Provide a descriptive title for the issue'); result.isValid = false; } break; case 'get': case 'update': case 'close': if (params.issue_number === undefined) { result.errors.push('issue_number is required for issue operations'); result.suggestions.push('Provide the issue number'); result.isValid = false; } break; case 'comment': if (params.issue_number === undefined) { result.errors.push('issue_number is required for commenting on issues'); result.suggestions.push('Provide the issue number'); result.isValid = false; } if (!params.comment_body) { result.errors.push('comment_body is required for commenting on issues'); result.suggestions.push('Provide the comment text'); result.isValid = false; } break; case 'search': if (!params.query) { result.errors.push('query is required for issue search'); result.suggestions.push('Provide a search query to find issues'); result.isValid = false; } break; } return result; } private static validatePullsParams(action: string, params: ToolParams): ValidationResult { const result: ValidationResult = { isValid: true, errors: [], warnings: [], suggestions: [] }; switch (action) { case 'create': if (!params.title) { result.errors.push('title is required for pull request creation'); result.suggestions.push('Provide a descriptive title for the pull request'); result.isValid = false; } if (!params.head || !params.base) { result.errors.push('head and base branches are required for pull request creation'); result.suggestions.push('Specify the source (head) and target (base) branches'); result.isValid = false; } break; case 'get': case 'update': case 'merge': case 'close': if (params.pull_number === undefined) { result.errors.push('pull_number is required for pull request operations'); result.suggestions.push('Provide the pull request number'); result.isValid = false; } break; case 'review': if (params.pull_number === undefined) { result.errors.push('pull_number is required for reviewing pull requests'); result.suggestions.push('Provide the pull request number'); result.isValid = false; } if (!params.event) { result.errors.push('event is required for pull request reviews'); result.suggestions.push('Specify review event: APPROVE, REQUEST_CHANGES, or COMMENT'); result.isValid = false; } if (params.event === 'REQUEST_CHANGES' && !params.review_body) { result.warnings.push('review_body is recommended for REQUEST_CHANGES reviews'); } break; case 'search': if (!params.query) { result.errors.push('query is required for pull request search'); result.suggestions.push('Provide a search query to find pull requests'); result.isValid = false; } break; } return result; } /** * Provides usage examples for correct parameter format */ public static getUsageExamples(toolName: string, action?: string): string[] { const examples: string[] = []; if (!action) { examples.push(`${toolName} usage examples:`); const operations = this.TOOL_OPERATIONS[toolName] || []; operations.slice(0, 3).forEach(op => { examples.push(` ${toolName} with action="${op}"`); }); return examples; } switch (toolName) { case 'git-workflow': switch (action) { case 'commit': examples.push('git-workflow commit: { "action": "commit", "projectPath": "/path/to/project", "message": "Fix bug in authentication" }'); break; case 'create': examples.push('git-workflow create: { "action": "create", "projectPath": "/path/to/project", "provider": "github", "name": "my-repo", "description": "My new repository" }'); break; case 'get': examples.push('git-workflow get: { "action": "get", "projectPath": "/path/to/project", "provider": "github", "repo": "repository-name" }'); break; case 'list': examples.push('git-workflow list: { "action": "list", "projectPath": "/path/to/project", "provider": "github" }'); break; case 'update': examples.push('git-workflow update: { "action": "update", "projectPath": "/path/to/project", "provider": "github", "repo": "repository-name", "description": "Updated description" }'); break; case 'delete': examples.push('git-workflow delete: { "action": "delete", "projectPath": "/path/to/project", "provider": "github", "repo": "repository-name" }'); break; case 'fork': examples.push('git-workflow fork: { "action": "fork", "projectPath": "/path/to/project", "provider": "github", "repo": "repository-name" }'); break; case 'search': examples.push('git-workflow search: { "action": "search", "projectPath": "/path/to/project", "provider": "github", "query": "javascript framework" }'); break; } break; case 'git-files': switch (action) { case 'read': examples.push('git-files read: { "action": "read", "projectPath": "/path/to/project", "filePath": "src/index.js" }'); examples.push('git-files read (remote): { "action": "read", "projectPath": "/path/to/project", "provider": "github", "filePath": "README.md", "repo": "repository" }'); break; case 'create': examples.push('git-files create: { "action": "create", "projectPath": "/path/to/project", "filePath": "README.md", "content": "# My Project" }'); examples.push('git-files create (remote): { "action": "create", "projectPath": "/path/to/project", "provider": "github", "filePath": "docs/api.md", "content": "# API Documentation", "message": "Add API docs" }'); break; case 'update': examples.push('git-files update: { "action": "update", "projectPath": "/path/to/project", "filePath": "package.json", "content": "{\\"name\\": \\"my-package\\"}" }'); examples.push('git-files update (remote): { "action": "update", "projectPath": "/path/to/project", "provider": "github", "filePath": "README.md", "content": "Updated content", "message": "Update README", "sha": "abc123" }'); break; case 'delete': examples.push('git-files delete: { "action": "delete", "projectPath": "/path/to/project", "filePath": "temp.txt" }'); examples.push('git-files delete (remote): { "action": "delete", "projectPath": "/path/to/project", "provider": "github", "filePath": "old-file.txt", "message": "Remove old file", "sha": "def456" }'); break; case 'search': examples.push('git-files search: { "action": "search", "projectPath": "/path/to/project", "query": "function authenticate" }'); examples.push('git-files search (remote): { "action": "search", "projectPath": "/path/to/project", "provider": "github", "query": "TODO" }'); break; case 'backup': examples.push('git-files backup: { "action": "backup", "projectPath": "/path/to/project" }'); examples.push('git-files backup (custom): { "action": "backup", "projectPath": "/path/to/project", "backupPath": "/backup/location", "includePattern": "*.js" }'); break; } break; } if (examples.length === 0) { examples.push(`${toolName} ${action}: { "action": "${action}", "projectPath": "/path/to/project" }`); } return examples; } /** * Validate git-sync specific parameters */ private static validateSyncParams(action: string, params: ToolParams): ValidationResult { const result: ValidationResult = { isValid: true, errors: [], warnings: [], suggestions: [] }; switch (action) { case 'sync': // Validate sync parameters if (params.strategy && !['merge', 'rebase', 'fast-forward'].includes(params.strategy)) { result.errors.push('Invalid sync strategy. Must be one of: merge, rebase, fast-forward'); result.suggestions.push('Use strategy: "merge", "rebase", or "fast-forward"'); result.isValid = false; } if (params.force) { result.warnings.push('Force sync will override uncommitted changes. Use with caution.'); } break; case 'status': // Status operation has no specific validation requirements break; } return result; } /** * Validate git-files specific parameters */ private static validateFilesParams(action: string, params: ToolParams): ValidationResult { const result: ValidationResult = { isValid: true, errors: [], warnings: [], suggestions: [] }; switch (action) { case 'read': if (!params.filePath) { result.errors.push('filePath is required for read operation'); result.suggestions.push('Provide a filePath parameter with the relative path to the file'); result.isValid = false; } if (params.encoding && !['utf8', 'base64', 'binary'].includes(params.encoding)) { result.errors.push('Invalid encoding. Must be one of: utf8, base64, binary'); result.suggestions.push('Use encoding: "utf8", "base64", or "binary"'); result.isValid = false; } break; case 'search': if (!params.query) { result.errors.push('query is required for search operation'); result.suggestions.push('Provide a query parameter with the text to search for'); result.isValid = false; } break; case 'list': // List operation has no specific validation requirements break; case 'backup': // Backup operation has no specific validation requirements break; } return result; } private static validateUpdateParams(action: string, params: ToolParams): ValidationResult { const result: ValidationResult = { isValid: true, errors: [], warnings: [], suggestions: [] }; switch (action) { case 'update': if (params.updateType && !['all', 'dependencies', 'code', 'docs', 'config'].includes(params.updateType)) { result.errors.push('Invalid update type. Must be one of: all, dependencies, code, docs, config'); result.suggestions.push('Use updateType: "all", "dependencies", "code", "docs", or "config"'); result.isValid = false; } break; case 'history': if (params.format && !['json', 'markdown', 'text'].includes(params.format)) { result.errors.push('Invalid format. Must be one of: json, markdown, text'); result.suggestions.push('Use format: "json", "markdown", or "text"'); result.isValid = false; } break; case 'changelog': if (params.version && !/^[\d]+\.[\d]+\.[\d]+/.test(params.version)) { result.warnings.push('Version format should follow semantic versioning (e.g., 1.0.0)'); } break; case 'track': if (!params.trackFile && !params.trackPattern) { result.warnings.push('No specific file or pattern to track - will track all changes'); } break; case 'sync-providers': if (params.providers && Array.isArray(params.providers)) { const validProviders = ['github', 'gitea']; const invalidProviders = params.providers.filter(p => !validProviders.includes(p)); if (invalidProviders.length > 0) { result.errors.push(`Invalid providers: ${invalidProviders.join(', ')}`); result.suggestions.push('Use providers: "github", "gitea", or both'); result.isValid = false; } } break; case 'rollback': if (!params.rollbackTo) { result.errors.push('rollbackTo parameter is required for rollback operation'); result.suggestions.push('Provide a commit hash, tag name, or version to rollback to'); result.isValid = false; } if (params.rollbackType && !['commit', 'tag', 'version'].includes(params.rollbackType)) { result.errors.push('Invalid rollback type. Must be one of: commit, tag, version'); result.suggestions.push('Use rollbackType: "commit", "tag", or "version"'); result.isValid = false; } break; case 'compare': if (!params.compareWith) { result.errors.push('compareWith parameter is required for compare operation'); result.suggestions.push('Provide a commit hash, tag name, branch, or provider to compare with'); result.isValid = false; } if (params.compareType && !['commit', 'tag', 'branch', 'provider'].includes(params.compareType)) { result.errors.push('Invalid compare type. Must be one of: commit, tag, branch, provider'); result.suggestions.push('Use compareType: "commit", "tag", "branch", or "provider"'); result.isValid = false; } break; case 'status': // Status operation has no specific validation requirements break; } return result; } }

Implementation Reference

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