From 0055c9ecf2a58bd5658a8100575f3a4c8c1fee04 Mon Sep 17 00:00:00 2001 From: lmiranda Date: Tue, 27 Jan 2026 12:30:48 -0500 Subject: [PATCH] feat(gitea-mcp): add create_pull_request tool Add missing create_pull_request tool to Gitea MCP server. This completes the PR lifecycle - previously only had list/get/review/comment tools. - Add create_pull_request to GiteaClient - Add async wrapper to PullRequestTools with branch permissions - Register tool in server.py with proper schema - Parameters: title, body, head, base, labels (optional) - Branch-aware security: only allowed on development/feature branches Co-Authored-By: Claude Opus 4.5 --- CHANGELOG.md | 6 +++ mcp-servers/gitea/mcp_server/gitea_client.py | 39 +++++++++++++++++++ mcp-servers/gitea/mcp_server/server.py | 37 ++++++++++++++++++ .../gitea/mcp_server/tools/pull_requests.py | 39 +++++++++++++++++++ 4 files changed, 121 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2685e4..5cc1fd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added +#### Gitea MCP Server - create_pull_request Tool +- **`create_pull_request`**: Create new pull requests via MCP + - Parameters: title, body, head (source branch), base (target branch), labels + - Branch-aware security: only allowed on development/feature branches + - Completes the PR lifecycle (was previously missing - only had list/get/review/comment) + #### cmdb-assistant v1.1.0 - Data Quality Validation - **SessionStart Hook**: Tests NetBox API connectivity at session start - Warns if VMs exist without site assignment diff --git a/mcp-servers/gitea/mcp_server/gitea_client.py b/mcp-servers/gitea/mcp_server/gitea_client.py index abe62ba..638fda2 100644 --- a/mcp-servers/gitea/mcp_server/gitea_client.py +++ b/mcp-servers/gitea/mcp_server/gitea_client.py @@ -787,3 +787,42 @@ class GiteaClient: response = self.session.post(url, json=data) response.raise_for_status() return response.json() + + def create_pull_request( + self, + title: str, + body: str, + head: str, + base: str, + labels: Optional[List[str]] = None, + repo: Optional[str] = None + ) -> Dict: + """ + Create a new pull request. + + Args: + title: PR title + body: PR description/body + head: Source branch name (the branch with changes) + base: Target branch name (the branch to merge into) + labels: Optional list of label names + repo: Repository in 'owner/repo' format + + Returns: + Created pull request dictionary + """ + owner, target_repo = self._parse_repo(repo) + url = f"{self.base_url}/repos/{owner}/{target_repo}/pulls" + data = { + 'title': title, + 'body': body, + 'head': head, + 'base': base + } + if labels: + label_ids = self._resolve_label_ids(labels, owner, target_repo) + data['labels'] = label_ids + logger.info(f"Creating PR '{title}' in {owner}/{target_repo}: {head} -> {base}") + response = self.session.post(url, json=data) + response.raise_for_status() + return response.json() diff --git a/mcp-servers/gitea/mcp_server/server.py b/mcp-servers/gitea/mcp_server/server.py index 5b58190..b5e53a7 100644 --- a/mcp-servers/gitea/mcp_server/server.py +++ b/mcp-servers/gitea/mcp_server/server.py @@ -844,6 +844,41 @@ class GiteaMCPServer: }, "required": ["pr_number", "body"] } + ), + Tool( + name="create_pull_request", + description="Create a new pull request", + inputSchema={ + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "PR title" + }, + "body": { + "type": "string", + "description": "PR description/body" + }, + "head": { + "type": "string", + "description": "Source branch name (the branch with changes)" + }, + "base": { + "type": "string", + "description": "Target branch name (the branch to merge into)" + }, + "labels": { + "type": "array", + "items": {"type": "string"}, + "description": "Optional list of label names" + }, + "repo": { + "type": "string", + "description": "Repository name (owner/repo format)" + } + }, + "required": ["title", "body", "head", "base"] + } ) ] @@ -959,6 +994,8 @@ class GiteaMCPServer: result = await self.pr_tools.create_pr_review(**arguments) elif name == "add_pr_comment": result = await self.pr_tools.add_pr_comment(**arguments) + elif name == "create_pull_request": + result = await self.pr_tools.create_pull_request(**arguments) else: raise ValueError(f"Unknown tool: {name}") diff --git a/mcp-servers/gitea/mcp_server/tools/pull_requests.py b/mcp-servers/gitea/mcp_server/tools/pull_requests.py index b6bd831..405929b 100644 --- a/mcp-servers/gitea/mcp_server/tools/pull_requests.py +++ b/mcp-servers/gitea/mcp_server/tools/pull_requests.py @@ -272,3 +272,42 @@ class PullRequestTools: None, lambda: self.gitea.add_pr_comment(pr_number, body, repo) ) + + async def create_pull_request( + self, + title: str, + body: str, + head: str, + base: str, + labels: Optional[List[str]] = None, + repo: Optional[str] = None + ) -> Dict: + """ + Create a new pull request (async wrapper with branch check). + + Args: + title: PR title + body: PR description/body + head: Source branch name (the branch with changes) + base: Target branch name (the branch to merge into) + labels: Optional list of label names + repo: Override configured repo (for PMO multi-repo) + + Returns: + Created pull request dictionary + + Raises: + PermissionError: If operation not allowed on current branch + """ + if not self._check_branch_permissions('create_pull_request'): + branch = self._get_current_branch() + raise PermissionError( + f"Cannot create PR on branch '{branch}'. " + f"Switch to a development or feature branch to create PRs." + ) + + loop = asyncio.get_event_loop() + return await loop.run_in_executor( + None, + lambda: self.gitea.create_pull_request(title, body, head, base, labels, repo) + )