git-pulls
Manage pull requests for Git repositories by creating, listing, updating, merging, closing, reviewing, and searching across GitHub and Gitea platforms.
Instructions
Comprehensive pull request management tool for Git repositories. Supports create, list, get, update, merge, close, review, and search operations for pull requests.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| action | Yes | The pull request operation to perform | |
| base | No | Target branch (required for create) | |
| base_filter | No | Filter by base branch (for list action) | |
| body | No | Pull request body/description | |
| commit_message | No | Merge commit message (for merge action) | |
| commit_title | No | Merge commit title (for merge action) | |
| direction | No | Sort direction (for list action, default: desc) | |
| event | No | Review event (required for review action) | |
| head | No | Source branch (required for create) | |
| head_filter | No | Filter by head branch (for list action) | |
| merge_method | No | Merge method (for merge action, default: merge) | |
| owner | No | Repository owner (auto-detected if not provided) | |
| projectPath | Yes | Absolute path to the project directory | |
| provider | Yes | Provider for pull request operations (required) | |
| pull_number | No | Pull request number (required for get/update/merge/close/review actions) | |
| query | No | Search query (required for search action) | |
| repo | No | Repository name (auto-detected if not provided) | |
| review_body | No | Review comment body (for review action) | |
| search_order | No | Order for search results (default: desc) | |
| search_sort | No | Sort for search results (default: created) | |
| sort | No | Sort criteria (for list action, default: created) | |
| state | No | Pull request state (for update action) | |
| state_filter | No | Filter pull requests by state (for list action, default: open) | |
| title | No | Pull request title (required for create, optional for update) |
Implementation Reference
- src/tools/git-pulls.ts:53-349 (handler)GitPullsTool class: core handler implementation for 'git-pulls' tool. Contains execute() method (lines 65-112) that validates params, routes to remote providers, and executes PR operations (create/list/get/update/merge/close/review/search). Delegates to ProviderOperationHandler for actual API calls.export class GitPullsTool { private providerHandler?: ProviderOperationHandler; constructor(providerConfig?: ProviderConfig) { if (providerConfig) { this.providerHandler = new ProviderOperationHandler(providerConfig); } } /** * Execute git-pulls operation */ async execute(params: GitPullsParams): Promise<ToolResult> { const startTime = Date.now(); try { // Validate basic parameters const validation = ParameterValidator.validateToolParams('git-pulls', 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-pulls', 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'] ); } } /** * Check if operation is remote */ private isRemoteOperation(action: string): boolean { // All pull request operations are remote const remoteOperations = ['create', 'list', 'get', 'update', 'merge', 'close', 'review', 'search']; return remoteOperations.includes(action); } /** * Execute remote pull request operations */ private async executeRemoteOperation(params: GitPullsParams, startTime: number): Promise<ToolResult> { // Check provider configuration if (!this.providerHandler) { return OperationErrorHandler.createToolError( 'PROVIDER_NOT_CONFIGURED', 'Provider handler is not configured for pull request operations', params.action, {}, ['Configure GitHub or Gitea provider to use pull request 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 pull request operations', params.action, {}, ['Specify provider as: github, gitea, or both'] ); } } return await this.executePullRequestOperation(params, startTime); } /** * Execute local pull request operations (if any) */ private async executeLocalOperation(params: GitPullsParams, startTime: number): Promise<ToolResult> { // Pull requests are remote-only operations return OperationErrorHandler.createToolError( 'LOCAL_OPERATION_NOT_SUPPORTED', 'Pull request operations are not supported locally', params.action, {}, ['Use remote operations with GitHub or Gitea provider'] ); } /** * Execute pull request operation through provider */ private async executePullRequestOperation(params: GitPullsParams, startTime: number): Promise<ToolResult> { const operation: ProviderOperation = { provider: params.provider!, operation: this.mapActionToProviderOperation(params.action), parameters: this.extractPullRequestParameters(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 || 'PR_OPERATION_ERROR', message: result.errors[0]?.error?.message || 'Pull request operation failed', details: result.errors, suggestions: this.getOperationSuggestions(params.action) }, 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( 'PR_OPERATION_ERROR', `Pull request operation failed: ${errorMessage}`, params.action, { error: errorMessage }, ['Check provider configuration and network connectivity'] ); } } /** * Extract parameters for pull request operations */ private extractPullRequestParameters(params: GitPullsParams): Record<string, any> { const prParams: Record<string, any> = { projectPath: params.projectPath }; // Auto-detect repo if not provided if (params.repo) prParams.repo = params.repo; // Pull request identification if (params.pull_number !== undefined) prParams.pull_number = params.pull_number; // Operation-specific parameters switch (params.action) { case 'create': if (params.title) prParams.title = params.title; if (params.body) prParams.body = params.body; if (params.head) prParams.head = params.head; if (params.base) prParams.base = params.base; break; case 'list': if (params.state_filter) prParams.state = params.state_filter; if (params.sort) prParams.sort = params.sort; if (params.direction) prParams.direction = params.direction; if (params.head_filter) prParams.head = params.head_filter; if (params.base_filter) prParams.base = params.base_filter; break; case 'get': // Get operations need owner, repo, and pull_number (already handled above) break; case 'update': if (params.title) prParams.title = params.title; if (params.body) prParams.body = params.body; if (params.state) prParams.state = params.state; break; case 'merge': if (params.commit_title) prParams.commit_title = params.commit_title; if (params.commit_message) prParams.commit_message = params.commit_message; if (params.merge_method) prParams.merge_method = params.merge_method; break; case 'close': prParams.state = 'closed'; break; case 'review': if (params.event) prParams.event = params.event; if (params.review_body) prParams.body = params.review_body; break; case 'search': if (params.query) prParams.query = params.query; if (params.search_sort) prParams.sort = params.search_sort; if (params.search_order) prParams.order = params.search_order; break; } return prParams; } /** * Map git-pulls actions to provider operations */ private mapActionToProviderOperation(action: string): string { const actionMap: Record<string, string> = { 'create': 'pr-create', 'list': 'pr-list', 'get': 'pr-get', 'update': 'pr-update', 'merge': 'pr-merge', 'close': 'pr-close', 'review': 'pr-review', 'search': 'pr-search' }; return actionMap[action] || action; } /** * Get operation-specific suggestions */ private getOperationSuggestions(action: string): string[] { const suggestions: Record<string, string[]> = { 'create': [ 'Ensure title, head, and base are provided', 'Check that head branch exists and has commits', 'Verify you have permission to create PRs in the repository', 'Ensure base branch exists and is accessible' ], 'list': [ 'Check repository access permissions', 'Verify owner and repo parameters are correct', 'Try different state filters: open, closed, all' ], 'get': [ 'Verify the pull request number exists', 'Check repository access permissions', 'Ensure owner and repo parameters are correct' ], 'update': [ 'Verify the pull request number exists', 'Check that you have permission to edit the PR', 'Ensure at least one field is being updated' ], 'merge': [ 'Verify the pull request number exists and is open', 'Check that you have permission to merge the PR', 'Ensure the PR is mergeable (no conflicts)', 'Check that required status checks are passing' ], 'close': [ 'Verify the pull request number exists and is open', 'Check that you have permission to close the PR', 'Ensure the PR is not already closed' ], 'review': [ 'Verify the pull request number exists', 'Ensure event is one of: APPROVE, REQUEST_CHANGES, COMMENT', 'Check that you have permission to review the PR', 'Provide review body for REQUEST_CHANGES or COMMENT events' ], 'search': [ 'Provide a search query', 'Check search syntax for the provider', 'Try different sort and order parameters' ] }; return suggestions[action] || ['Check provider configuration and try again']; }
- src/tools/git-pulls.ts:353-466 (schema)Tool schema definition returned by GitPullsTool.getToolSchema(). Defines inputSchema for MCP registration including all parameters like action, provider, repo, pull_number, title, etc., with types, enums, and descriptions.static getToolSchema() { return { name: 'git-pulls', description: 'Comprehensive pull request management tool for Git repositories. Supports create, list, get, update, merge, close, review, and search operations for pull requests. In universal mode (GIT_MCP_MODE=universal), automatically executes on both GitHub and Gitea providers.', inputSchema: { type: 'object', properties: { action: { type: 'string', enum: ['create', 'list', 'get', 'update', 'merge', 'close', 'review', 'search'], description: 'The pull request operation to perform' }, projectPath: { type: 'string', description: 'Absolute path to the project directory' }, provider: { type: 'string', enum: ['github', 'gitea', 'both'], description: 'Provider for pull request operations (required)' }, repo: { type: 'string', description: 'Repository name (auto-detected if not provided)' }, pull_number: { type: 'number', description: 'Pull request number (required for get/update/merge/close/review actions)' }, title: { type: 'string', description: 'Pull request title (required for create, optional for update)' }, body: { type: 'string', description: 'Pull request body/description' }, head: { type: 'string', description: 'Source branch (required for create)' }, base: { type: 'string', description: 'Target branch (required for create)' }, state: { type: 'string', enum: ['open', 'closed'], description: 'Pull request state (for update action)' }, state_filter: { type: 'string', enum: ['open', 'closed', 'all'], description: 'Filter pull requests by state (for list action, default: open)' }, sort: { type: 'string', enum: ['created', 'updated', 'popularity', 'long-running'], description: 'Sort criteria (for list action, default: created)' }, direction: { type: 'string', enum: ['asc', 'desc'], description: 'Sort direction (for list action, default: desc)' }, head_filter: { type: 'string', description: 'Filter by head branch (for list action)' }, base_filter: { type: 'string', description: 'Filter by base branch (for list action)' }, commit_title: { type: 'string', description: 'Merge commit title (for merge action)' }, commit_message: { type: 'string', description: 'Merge commit message (for merge action)' }, merge_method: { type: 'string', enum: ['merge', 'squash', 'rebase'], description: 'Merge method (for merge action, default: merge)' }, event: { type: 'string', enum: ['APPROVE', 'REQUEST_CHANGES', 'COMMENT'], description: 'Review event (required for review action)' }, review_body: { type: 'string', description: 'Review comment body (for review action)' }, query: { type: 'string', description: 'Search query (required for search action)' }, search_sort: { type: 'string', enum: ['created', 'updated', 'popularity'], description: 'Sort for search results (default: created)' }, search_order: { type: 'string', enum: ['asc', 'desc'], description: 'Order for search results (default: desc)' } }, required: ['action', 'projectPath'] } }; }
- src/server.ts:478-479 (registration)Server execution registration: switch case in GitMCPServer.executeTool() that routes 'git-pulls' calls to this.gitPullsTool.execute().case 'git-pulls': return await this.gitPullsTool.execute(args);
- src/server.ts:91-91 (registration)Tool instantiation in GitMCPServer constructor: creates GitPullsTool instance with provider config.this.gitPullsTool = new GitPullsTool(providerConfig);
- ParameterValidator helper: validateOperationParams for 'git-pulls' with validatePullsParams(action, params) defining operation-specific validation rules (e.g., required title/head/base for create, pull_number for get/merge, etc.). Also defines supported operations in TOOL_OPERATIONS (line 33).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; }