From 4aacfe348a4448618a9f95d93629c084e409539a Mon Sep 17 00:00:00 2001 From: tylermitchell Date: Wed, 11 Feb 2026 17:53:09 +0000 Subject: [PATCH] feat(pull): add merge_pull_request tool (#123) Add MCP tool to merge pull requests with support for: - Multiple merge styles (merge, rebase, rebase-merge, squash, fast-forward-only) - Custom merge commit messages - Optional branch deletion after merge - Detailed error handling for merge conflicts and edge cases Updated all README files (English, Simplified Chinese, Traditional Chinese) with the new tool entry. --------- Co-authored-by: Tyler Potts Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/123 Reviewed-by: Lunny Xiao Reviewed-by: silverwind Co-authored-by: tylermitchell Co-committed-by: tylermitchell --- README.md | 1 + README.zh-cn.md | 1 + README.zh-tw.md | 1 + operation/pull/pull.go | 81 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+) diff --git a/README.md b/README.md index dcea00d..fa3ccdd 100644 --- a/README.md +++ b/README.md @@ -221,6 +221,7 @@ The Gitea MCP Server supports the following tools: | submit_pull_request_review | Pull Request | Submit a pending review | | delete_pull_request_review | Pull Request | Delete a review | | dismiss_pull_request_review | Pull Request | Dismiss a review with optional message | +| merge_pull_request | Pull Request | Merge a pull request | | search_users | User | Search for users | | search_org_teams | Organization | Search for teams in an organization | | list_org_labels | Organization | List labels defined at organization level | diff --git a/README.zh-cn.md b/README.zh-cn.md index 27af10b..86485ae 100644 --- a/README.zh-cn.md +++ b/README.zh-cn.md @@ -220,6 +220,7 @@ Gitea MCP 服务器支持以下工具: | submit_pull_request_review | 拉取请求 | 提交待处理的审查 | | delete_pull_request_review | 拉取请求 | 删除审查 | | dismiss_pull_request_review | 拉取请求 | 驳回审查(可附消息) | +| merge_pull_request | 拉取请求 | 合并拉取请求 | | search_users | 用户 | 搜索用户 | | search_org_teams | 组织 | 搜索组织团队 | | list_org_labels | 组织 | 列出组织标签 | diff --git a/README.zh-tw.md b/README.zh-tw.md index abed1a6..2dac558 100644 --- a/README.zh-tw.md +++ b/README.zh-tw.md @@ -220,6 +220,7 @@ Gitea MCP 伺服器支援以下工具: | submit_pull_request_review | 拉取請求 | 提交待處理的審查 | | delete_pull_request_review | 拉取請求 | 刪除審查 | | dismiss_pull_request_review | 拉取請求 | 駁回審查(可附訊息) | +| merge_pull_request | 拉取請求 | 合併拉取請求 | | search_users | 用戶 | 搜尋用戶 | | search_org_teams | 組織 | 搜尋組織團隊 | | list_org_labels | 組織 | 列出組織標籤 | diff --git a/operation/pull/pull.go b/operation/pull/pull.go index f5ff583..cbf839b 100644 --- a/operation/pull/pull.go +++ b/operation/pull/pull.go @@ -30,6 +30,7 @@ const ( SubmitPullRequestReviewToolName = "submit_pull_request_review" DeletePullRequestReviewToolName = "delete_pull_request_review" DismissPullRequestReviewToolName = "dismiss_pull_request_review" + MergePullRequestToolName = "merge_pull_request" ) var ( @@ -170,6 +171,17 @@ var ( mcp.WithNumber("review_id", mcp.Required(), mcp.Description("review ID")), mcp.WithString("message", mcp.Description("dismissal reason")), ) + + MergePullRequestTool = mcp.NewTool( + MergePullRequestToolName, + mcp.WithDescription("merge a pull request"), + mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), + mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), + mcp.WithNumber("index", mcp.Required(), mcp.Description("pull request index")), + mcp.WithString("merge_style", mcp.Description("merge style: merge, rebase, rebase-merge, squash, fast-forward-only"), mcp.Enum("merge", "rebase", "rebase-merge", "squash", "fast-forward-only"), mcp.DefaultString("merge")), + mcp.WithString("message", mcp.Description("custom merge commit message (optional)")), + mcp.WithBoolean("delete_branch", mcp.Description("delete the branch after merge"), mcp.DefaultBool(false)), + ) ) func init() { @@ -225,6 +237,10 @@ func init() { Tool: DismissPullRequestReviewTool, Handler: DismissPullRequestReviewFn, }) + Tool.RegisterWrite(server.ServerTool{ + Tool: MergePullRequestTool, + Handler: MergePullRequestFn, + }) } func GetPullRequestByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { @@ -795,3 +811,68 @@ func DismissPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (* return to.TextResult(successMsg) } + +func MergePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { + log.Debugf("Called MergePullRequestFn") + owner, ok := req.GetArguments()["owner"].(string) + if !ok { + return to.ErrorResult(fmt.Errorf("owner is required")) + } + repo, ok := req.GetArguments()["repo"].(string) + if !ok { + return to.ErrorResult(fmt.Errorf("repo is required")) + } + index, ok := req.GetArguments()["index"].(float64) + if !ok { + return to.ErrorResult(fmt.Errorf("index is required")) + } + + mergeStyle := "merge" + if style, exists := req.GetArguments()["merge_style"].(string); exists && style != "" { + mergeStyle = style + } + + message := "" + if msg, exists := req.GetArguments()["message"].(string); exists { + message = msg + } + + deleteBranch := false + if del, exists := req.GetArguments()["delete_branch"].(bool); exists { + deleteBranch = del + } + + client, err := gitea.ClientFromContext(ctx) + if err != nil { + return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) + } + + opt := gitea_sdk.MergePullRequestOption{ + Style: gitea_sdk.MergeStyle(mergeStyle), + Message: message, + DeleteBranchAfterMerge: deleteBranch, + } + + merged, resp, err := client.MergePullRequest(owner, repo, int64(index), opt) + if err != nil { + return to.ErrorResult(fmt.Errorf("merge %v/%v/pr/%v err: %v", owner, repo, int64(index), err)) + } + + if !merged && resp != nil && resp.StatusCode >= 400 { + return to.ErrorResult(fmt.Errorf("merge %v/%v/pr/%v failed: HTTP %d %s", owner, repo, int64(index), resp.StatusCode, resp.Status)) + } + + if !merged { + return to.ErrorResult(fmt.Errorf("merge %v/%v/pr/%v returned merged=false", owner, repo, int64(index))) + } + + successMsg := map[string]interface{}{ + "merged": merged, + "pr_index": int64(index), + "repository": fmt.Sprintf("%s/%s", owner, repo), + "merge_style": mergeStyle, + "branch_deleted": deleteBranch, + } + + return to.TextResult(successMsg) +}