Files
gitea-mcp/operation/pull/pull.go
silverwind bba612d238 Consolidate tools from 110 to 45 using method dispatch (#143)
Consolidate 110 individual MCP tools down to 45 using a method dispatch pattern, aligning tool names with the GitHub MCP server conventions.

**Motivation:** LLMs work better with fewer, well-organized tools. The method dispatch pattern (used by GitHub's MCP server) groups related operations under read/write tools with a `method` parameter.

**Changes:**
- Group related tools into `_read`/`_write` pairs with method dispatch (e.g. `issue_read`, `issue_write`, `pull_request_read`, `pull_request_write`)
- Rename tools to match GitHub MCP naming (`get_file_contents`, `create_or_update_file`, `list_issues`, `list_pull_requests`, etc.)
- Rename `pageSize` to `perPage` for GitHub MCP compat
- Move issue label ops (`add_labels`, `remove_label`, etc.) into `issue_write`
- Merge `create_file`/`update_file` into `create_or_update_file` with optional `sha`
- Make `delete_file` require `sha`
- Add `get_labels` method to `issue_read`
- Add shared helpers: `GetInt64Slice`, `GetStringSlice`, `GetPagination` in params package
- Unexport all dispatch handler functions
- Fix: pass assignees/milestone in `CreateIssue`, bounds check in `GetFileContent`

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/143
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-committed-by: silverwind <me@silverwind.io>
2026-03-06 19:12:15 +00:00

813 lines
26 KiB
Go

package pull
import (
"context"
"fmt"
"gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
var Tool = tool.New()
const (
ListRepoPullRequestsToolName = "list_pull_requests"
PullRequestReadToolName = "pull_request_read"
PullRequestWriteToolName = "pull_request_write"
PullRequestReviewWriteToolName = "pull_request_review_write"
)
var (
ListRepoPullRequestsTool = mcp.NewTool(
ListRepoPullRequestsToolName,
mcp.WithDescription("List repository pull requests"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("state", mcp.Description("state"), mcp.Enum("open", "closed", "all"), mcp.DefaultString("all")),
mcp.WithString("sort", mcp.Description("sort"), mcp.Enum("oldest", "recentupdate", "leastupdate", "mostcomment", "leastcomment", "priority"), mcp.DefaultString("recentupdate")),
mcp.WithNumber("milestone", mcp.Description("milestone")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("perPage", mcp.Description("results per page"), mcp.DefaultNumber(30)),
)
PullRequestReadTool = mcp.NewTool(
PullRequestReadToolName,
mcp.WithDescription("Get pull request information. Use method 'get' for PR details, 'get_diff' for diff, 'get_reviews'/'get_review'/'get_review_comments' for review data."),
mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("get", "get_diff", "get_reviews", "get_review", "get_review_comments")),
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.WithNumber("review_id", mcp.Description("review ID (required for 'get_review', 'get_review_comments')")),
mcp.WithBoolean("binary", mcp.Description("whether to include binary file changes (for 'get_diff')")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("perPage", mcp.Description("results per page"), mcp.DefaultNumber(30)),
)
PullRequestWriteTool = mcp.NewTool(
PullRequestWriteToolName,
mcp.WithDescription("Create, update, or merge pull requests, manage reviewers."),
mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("create", "update", "merge", "add_reviewers", "remove_reviewers")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithNumber("index", mcp.Description("pull request index (required for all methods except 'create')")),
mcp.WithString("title", mcp.Description("PR title (required for 'create', optional for 'update', 'merge')")),
mcp.WithString("body", mcp.Description("PR body (required for 'create', optional for 'update')")),
mcp.WithString("head", mcp.Description("PR head branch (required for 'create')")),
mcp.WithString("base", mcp.Description("PR base branch (required for 'create', optional for 'update')")),
mcp.WithString("assignee", mcp.Description("username to assign (for 'update')")),
mcp.WithArray("assignees", mcp.Description("usernames to assign (for 'update')"), mcp.Items(map[string]any{"type": "string"})),
mcp.WithNumber("milestone", mcp.Description("milestone number (for 'update')")),
mcp.WithString("state", mcp.Description("PR state (for 'update')"), mcp.Enum("open", "closed")),
mcp.WithBoolean("allow_maintainer_edit", mcp.Description("allow maintainer to edit (for 'update')")),
mcp.WithString("merge_style", mcp.Description("merge style (for 'merge')"), mcp.Enum("merge", "rebase", "rebase-merge", "squash", "fast-forward-only"), mcp.DefaultString("merge")),
mcp.WithString("message", mcp.Description("merge commit message (for 'merge') or dismissal reason")),
mcp.WithBoolean("delete_branch", mcp.Description("delete branch after merge (for 'merge')")),
mcp.WithArray("reviewers", mcp.Description("reviewer usernames (for 'add_reviewers', 'remove_reviewers')"), mcp.Items(map[string]any{"type": "string"})),
mcp.WithArray("team_reviewers", mcp.Description("team reviewer names (for 'add_reviewers', 'remove_reviewers')"), mcp.Items(map[string]any{"type": "string"})),
)
PullRequestReviewWriteTool = mcp.NewTool(
PullRequestReviewWriteToolName,
mcp.WithDescription("Manage pull request reviews: create, submit, delete, or dismiss."),
mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("create", "submit", "delete", "dismiss")),
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.WithNumber("review_id", mcp.Description("review ID (required for 'submit', 'delete', 'dismiss')")),
mcp.WithString("state", mcp.Description("review state"), mcp.Enum("APPROVED", "REQUEST_CHANGES", "COMMENT", "PENDING")),
mcp.WithString("body", mcp.Description("review body/comment")),
mcp.WithString("commit_id", mcp.Description("commit SHA to review (for 'create')")),
mcp.WithString("message", mcp.Description("dismissal reason (for 'dismiss')")),
mcp.WithArray("comments", mcp.Description("inline review comments (for 'create')"), mcp.Items(map[string]any{
"type": "object",
"properties": map[string]any{
"path": map[string]any{"type": "string", "description": "file path to comment on"},
"body": map[string]any{"type": "string", "description": "comment body"},
"old_line_num": map[string]any{"type": "number", "description": "line number in the old file (for deletions/changes)"},
"new_line_num": map[string]any{"type": "number", "description": "line number in the new file (for additions/changes)"},
},
})),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{
Tool: ListRepoPullRequestsTool,
Handler: listRepoPullRequestsFn,
})
Tool.RegisterRead(server.ServerTool{
Tool: PullRequestReadTool,
Handler: pullRequestReadFn,
})
Tool.RegisterWrite(server.ServerTool{
Tool: PullRequestWriteTool,
Handler: pullRequestWriteFn,
})
Tool.RegisterWrite(server.ServerTool{
Tool: PullRequestReviewWriteTool,
Handler: pullRequestReviewWriteFn,
})
}
func pullRequestReadFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
method, err := params.GetString(req.GetArguments(), "method")
if err != nil {
return to.ErrorResult(err)
}
switch method {
case "get":
return getPullRequestByIndexFn(ctx, req)
case "get_diff":
return getPullRequestDiffFn(ctx, req)
case "get_reviews":
return listPullRequestReviewsFn(ctx, req)
case "get_review":
return getPullRequestReviewFn(ctx, req)
case "get_review_comments":
return listPullRequestReviewCommentsFn(ctx, req)
default:
return to.ErrorResult(fmt.Errorf("unknown method: %s", method))
}
}
func pullRequestWriteFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
method, err := params.GetString(req.GetArguments(), "method")
if err != nil {
return to.ErrorResult(err)
}
switch method {
case "create":
return createPullRequestFn(ctx, req)
case "update":
return editPullRequestFn(ctx, req)
case "merge":
return mergePullRequestFn(ctx, req)
case "add_reviewers":
return createPullRequestReviewerFn(ctx, req)
case "remove_reviewers":
return deletePullRequestReviewerFn(ctx, req)
default:
return to.ErrorResult(fmt.Errorf("unknown method: %s", method))
}
}
func pullRequestReviewWriteFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
method, err := params.GetString(req.GetArguments(), "method")
if err != nil {
return to.ErrorResult(err)
}
switch method {
case "create":
return createPullRequestReviewFn(ctx, req)
case "submit":
return submitPullRequestReviewFn(ctx, req)
case "delete":
return deletePullRequestReviewFn(ctx, req)
case "dismiss":
return dismissPullRequestReviewFn(ctx, req)
default:
return to.ErrorResult(fmt.Errorf("unknown method: %s", method))
}
}
func getPullRequestByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called getPullRequestByIndexFn")
args := req.GetArguments()
owner, err := params.GetString(args, "owner")
if err != nil {
return to.ErrorResult(err)
}
repo, err := params.GetString(args, "repo")
if err != nil {
return to.ErrorResult(err)
}
index, err := params.GetIndex(args, "index")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
pr, _, err := client.GetPullRequest(owner, repo, index)
if err != nil {
return to.ErrorResult(fmt.Errorf("get %v/%v/pr/%v err: %v", owner, repo, index, err))
}
return to.TextResult(slimPullRequest(pr))
}
func getPullRequestDiffFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called getPullRequestDiffFn")
args := req.GetArguments()
owner, err := params.GetString(args, "owner")
if err != nil {
return to.ErrorResult(err)
}
repo, err := params.GetString(args, "repo")
if err != nil {
return to.ErrorResult(err)
}
index, err := params.GetIndex(args, "index")
if err != nil {
return to.ErrorResult(err)
}
binary, _ := args["binary"].(bool)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
diffBytes, _, err := client.GetPullRequestDiff(owner, repo, index, gitea_sdk.PullRequestDiffOptions{
Binary: binary,
})
if err != nil {
return to.ErrorResult(fmt.Errorf("get %v/%v/pr/%v diff err: %v", owner, repo, index, err))
}
return to.TextResult(string(diffBytes))
}
func listRepoPullRequestsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListRepoPullRequests")
args := req.GetArguments()
owner, err := params.GetString(args, "owner")
if err != nil {
return to.ErrorResult(err)
}
repo, err := params.GetString(args, "repo")
if err != nil {
return to.ErrorResult(err)
}
state, _ := args["state"].(string)
sort := params.GetOptionalString(args, "sort", "recentupdate")
milestone := params.GetOptionalInt(args, "milestone", 0)
page, pageSize := params.GetPagination(args, 30)
opt := gitea_sdk.ListPullRequestsOptions{
State: gitea_sdk.StateType(state),
Sort: sort,
Milestone: milestone,
ListOptions: gitea_sdk.ListOptions{
Page: page,
PageSize: pageSize,
},
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
pullRequests, _, err := client.ListRepoPullRequests(owner, repo, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("list %v/%v/pull_requests err: %v", owner, repo, err))
}
return to.TextResult(slimPullRequests(pullRequests))
}
func createPullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called createPullRequestFn")
args := req.GetArguments()
owner, err := params.GetString(args, "owner")
if err != nil {
return to.ErrorResult(err)
}
repo, err := params.GetString(args, "repo")
if err != nil {
return to.ErrorResult(err)
}
title, err := params.GetString(args, "title")
if err != nil {
return to.ErrorResult(err)
}
body, err := params.GetString(args, "body")
if err != nil {
return to.ErrorResult(err)
}
head, err := params.GetString(args, "head")
if err != nil {
return to.ErrorResult(err)
}
base, err := params.GetString(args, "base")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
pr, _, err := client.CreatePullRequest(owner, repo, gitea_sdk.CreatePullRequestOption{
Title: title,
Body: body,
Head: head,
Base: base,
})
if err != nil {
return to.ErrorResult(fmt.Errorf("create %v/%v/pull_request err: %v", owner, repo, err))
}
return to.TextResult(slimPullRequest(pr))
}
func createPullRequestReviewerFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called createPullRequestReviewerFn")
args := req.GetArguments()
owner, err := params.GetString(args, "owner")
if err != nil {
return to.ErrorResult(err)
}
repo, err := params.GetString(args, "repo")
if err != nil {
return to.ErrorResult(err)
}
index, err := params.GetIndex(args, "index")
if err != nil {
return to.ErrorResult(err)
}
reviewers := params.GetStringSlice(args, "reviewers")
teamReviewers := params.GetStringSlice(args, "team_reviewers")
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.CreateReviewRequests(owner, repo, index, gitea_sdk.PullReviewRequestOptions{
Reviewers: reviewers,
TeamReviewers: teamReviewers,
})
if err != nil {
return to.ErrorResult(fmt.Errorf("create review requests for %v/%v/pr/%v err: %v", owner, repo, index, err))
}
successMsg := map[string]any{
"message": "Successfully created review requests",
"reviewers": reviewers,
"team_reviewers": teamReviewers,
"pr_index": index,
"repository": fmt.Sprintf("%s/%s", owner, repo),
}
return to.TextResult(successMsg)
}
func deletePullRequestReviewerFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called deletePullRequestReviewerFn")
args := req.GetArguments()
owner, err := params.GetString(args, "owner")
if err != nil {
return to.ErrorResult(err)
}
repo, err := params.GetString(args, "repo")
if err != nil {
return to.ErrorResult(err)
}
index, err := params.GetIndex(args, "index")
if err != nil {
return to.ErrorResult(err)
}
reviewers := params.GetStringSlice(args, "reviewers")
teamReviewers := params.GetStringSlice(args, "team_reviewers")
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.DeleteReviewRequests(owner, repo, index, gitea_sdk.PullReviewRequestOptions{
Reviewers: reviewers,
TeamReviewers: teamReviewers,
})
if err != nil {
return to.ErrorResult(fmt.Errorf("delete review requests for %v/%v/pr/%v err: %v", owner, repo, index, err))
}
successMsg := map[string]any{
"message": "Successfully deleted review requests",
"reviewers": reviewers,
"team_reviewers": teamReviewers,
"pr_index": index,
"repository": fmt.Sprintf("%s/%s", owner, repo),
}
return to.TextResult(successMsg)
}
func listPullRequestReviewsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called listPullRequestReviewsFn")
args := req.GetArguments()
owner, err := params.GetString(args, "owner")
if err != nil {
return to.ErrorResult(err)
}
repo, err := params.GetString(args, "repo")
if err != nil {
return to.ErrorResult(err)
}
index, err := params.GetIndex(args, "index")
if err != nil {
return to.ErrorResult(err)
}
page, pageSize := params.GetPagination(args, 30)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
reviews, _, err := client.ListPullReviews(owner, repo, index, gitea_sdk.ListPullReviewsOptions{
ListOptions: gitea_sdk.ListOptions{
Page: page,
PageSize: pageSize,
},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list reviews for %v/%v/pr/%v err: %v", owner, repo, index, err))
}
return to.TextResult(slimReviews(reviews))
}
func getPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called getPullRequestReviewFn")
args := req.GetArguments()
owner, err := params.GetString(args, "owner")
if err != nil {
return to.ErrorResult(err)
}
repo, err := params.GetString(args, "repo")
if err != nil {
return to.ErrorResult(err)
}
index, err := params.GetIndex(args, "index")
if err != nil {
return to.ErrorResult(err)
}
reviewID, err := params.GetIndex(args, "review_id")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
review, _, err := client.GetPullReview(owner, repo, index, reviewID)
if err != nil {
return to.ErrorResult(fmt.Errorf("get review %v for %v/%v/pr/%v err: %v", reviewID, owner, repo, index, err))
}
return to.TextResult(slimReview(review))
}
func listPullRequestReviewCommentsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called listPullRequestReviewCommentsFn")
args := req.GetArguments()
owner, err := params.GetString(args, "owner")
if err != nil {
return to.ErrorResult(err)
}
repo, err := params.GetString(args, "repo")
if err != nil {
return to.ErrorResult(err)
}
index, err := params.GetIndex(args, "index")
if err != nil {
return to.ErrorResult(err)
}
reviewID, err := params.GetIndex(args, "review_id")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
comments, _, err := client.ListPullReviewComments(owner, repo, index, reviewID)
if err != nil {
return to.ErrorResult(fmt.Errorf("list review comments for review %v on %v/%v/pr/%v err: %v", reviewID, owner, repo, index, err))
}
return to.TextResult(slimReviewComments(comments))
}
func createPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called createPullRequestReviewFn")
args := req.GetArguments()
owner, err := params.GetString(args, "owner")
if err != nil {
return to.ErrorResult(err)
}
repo, err := params.GetString(args, "repo")
if err != nil {
return to.ErrorResult(err)
}
index, err := params.GetIndex(args, "index")
if err != nil {
return to.ErrorResult(err)
}
opt := gitea_sdk.CreatePullReviewOptions{}
if state, ok := args["state"].(string); ok {
opt.State = gitea_sdk.ReviewStateType(state)
}
if body, ok := args["body"].(string); ok {
opt.Body = body
}
if commitID, ok := args["commit_id"].(string); ok {
opt.CommitID = commitID
}
// Parse inline comments
if commentsArg, exists := args["comments"]; exists {
if commentsSlice, ok := commentsArg.([]any); ok {
for _, comment := range commentsSlice {
if commentMap, ok := comment.(map[string]any); ok {
reviewComment := gitea_sdk.CreatePullReviewComment{}
if path, ok := commentMap["path"].(string); ok {
reviewComment.Path = path
}
if body, ok := commentMap["body"].(string); ok {
reviewComment.Body = body
}
if oldLineNum, ok := params.ToInt64(commentMap["old_line_num"]); ok {
reviewComment.OldLineNum = oldLineNum
}
if newLineNum, ok := params.ToInt64(commentMap["new_line_num"]); ok {
reviewComment.NewLineNum = newLineNum
}
opt.Comments = append(opt.Comments, reviewComment)
}
}
}
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
review, _, err := client.CreatePullReview(owner, repo, index, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("create review for %v/%v/pr/%v err: %v", owner, repo, index, err))
}
return to.TextResult(slimReview(review))
}
func submitPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called submitPullRequestReviewFn")
args := req.GetArguments()
owner, err := params.GetString(args, "owner")
if err != nil {
return to.ErrorResult(err)
}
repo, err := params.GetString(args, "repo")
if err != nil {
return to.ErrorResult(err)
}
index, err := params.GetIndex(args, "index")
if err != nil {
return to.ErrorResult(err)
}
reviewID, err := params.GetIndex(args, "review_id")
if err != nil {
return to.ErrorResult(err)
}
state, err := params.GetString(args, "state")
if err != nil {
return to.ErrorResult(err)
}
opt := gitea_sdk.SubmitPullReviewOptions{
State: gitea_sdk.ReviewStateType(state),
}
if body, ok := args["body"].(string); ok {
opt.Body = body
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
review, _, err := client.SubmitPullReview(owner, repo, index, reviewID, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("submit review %v for %v/%v/pr/%v err: %v", reviewID, owner, repo, index, err))
}
return to.TextResult(slimReview(review))
}
func deletePullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called deletePullRequestReviewFn")
args := req.GetArguments()
owner, err := params.GetString(args, "owner")
if err != nil {
return to.ErrorResult(err)
}
repo, err := params.GetString(args, "repo")
if err != nil {
return to.ErrorResult(err)
}
index, err := params.GetIndex(args, "index")
if err != nil {
return to.ErrorResult(err)
}
reviewID, err := params.GetIndex(args, "review_id")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.DeletePullReview(owner, repo, index, reviewID)
if err != nil {
return to.ErrorResult(fmt.Errorf("delete review %v for %v/%v/pr/%v err: %v", reviewID, owner, repo, index, err))
}
successMsg := map[string]any{
"message": "Successfully deleted review",
"review_id": reviewID,
"pr_index": index,
"repository": fmt.Sprintf("%s/%s", owner, repo),
}
return to.TextResult(successMsg)
}
func dismissPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called dismissPullRequestReviewFn")
args := req.GetArguments()
owner, err := params.GetString(args, "owner")
if err != nil {
return to.ErrorResult(err)
}
repo, err := params.GetString(args, "repo")
if err != nil {
return to.ErrorResult(err)
}
index, err := params.GetIndex(args, "index")
if err != nil {
return to.ErrorResult(err)
}
reviewID, err := params.GetIndex(args, "review_id")
if err != nil {
return to.ErrorResult(err)
}
opt := gitea_sdk.DismissPullReviewOptions{}
if message, ok := args["message"].(string); ok {
opt.Message = message
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.DismissPullReview(owner, repo, index, reviewID, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("dismiss review %v for %v/%v/pr/%v err: %v", reviewID, owner, repo, index, err))
}
successMsg := map[string]any{
"message": "Successfully dismissed review",
"review_id": reviewID,
"pr_index": index,
"repository": fmt.Sprintf("%s/%s", owner, repo),
}
return to.TextResult(successMsg)
}
func mergePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called mergePullRequestFn")
args := req.GetArguments()
owner, err := params.GetString(args, "owner")
if err != nil {
return to.ErrorResult(err)
}
repo, err := params.GetString(args, "repo")
if err != nil {
return to.ErrorResult(err)
}
index, err := params.GetIndex(args, "index")
if err != nil {
return to.ErrorResult(err)
}
mergeStyle := params.GetOptionalString(args, "merge_style", "merge")
title, _ := args["title"].(string)
message, _ := args["message"].(string)
deleteBranch, _ := args["delete_branch"].(bool)
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),
Title: title,
Message: message,
DeleteBranchAfterMerge: deleteBranch,
}
merged, resp, err := client.MergePullRequest(owner, repo, index, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("merge %v/%v/pr/%v err: %v", owner, repo, 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, index, resp.StatusCode, resp.Status))
}
if !merged {
return to.ErrorResult(fmt.Errorf("merge %v/%v/pr/%v returned merged=false", owner, repo, index))
}
successMsg := map[string]any{
"merged": merged,
"pr_index": index,
"repository": fmt.Sprintf("%s/%s", owner, repo),
"merge_style": mergeStyle,
"branch_deleted": deleteBranch,
}
return to.TextResult(successMsg)
}
func editPullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called editPullRequestFn")
args := req.GetArguments()
owner, err := params.GetString(args, "owner")
if err != nil {
return to.ErrorResult(err)
}
repo, err := params.GetString(args, "repo")
if err != nil {
return to.ErrorResult(err)
}
index, err := params.GetIndex(args, "index")
if err != nil {
return to.ErrorResult(err)
}
opt := gitea_sdk.EditPullRequestOption{}
if title, ok := args["title"].(string); ok {
opt.Title = title
}
if body, ok := args["body"].(string); ok {
opt.Body = new(body)
}
if base, ok := args["base"].(string); ok {
opt.Base = base
}
if assignee, ok := args["assignee"].(string); ok {
opt.Assignee = assignee
}
if assignees := params.GetStringSlice(args, "assignees"); assignees != nil {
opt.Assignees = assignees
}
if val, exists := args["milestone"]; exists {
if milestone, ok := params.ToInt64(val); ok {
opt.Milestone = milestone
}
}
if state, ok := args["state"].(string); ok {
opt.State = new(gitea_sdk.StateType(state))
}
if allowMaintainerEdit, ok := args["allow_maintainer_edit"].(bool); ok {
opt.AllowMaintainerEdit = new(allowMaintainerEdit)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
pr, _, err := client.EditPullRequest(owner, repo, index, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("edit %v/%v/pr/%v err: %v", owner, repo, index, err))
}
return to.TextResult(slimPullRequest(pr))
}