mirror of
				https://gitea.com/gitea/gitea-mcp.git
				synced 2025-11-03 20:01:50 +00:00 
			
		
		
		
	- Add support for creating pull request reviewers through a new tool and handler - Document the new tool for adding reviewers to a pull request in English, Simplified Chinese, and Traditional Chinese READMEs Signed-off-by: appleboy <appleboy.tw@gmail.com> Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/103 Co-authored-by: appleboy <appleboy.tw@gmail.com> Co-committed-by: appleboy <appleboy.tw@gmail.com>
		
			
				
	
	
		
			263 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			263 lines
		
	
	
		
			8.8 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/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 (
 | 
						|
	GetPullRequestByIndexToolName     = "get_pull_request_by_index"
 | 
						|
	ListRepoPullRequestsToolName      = "list_repo_pull_requests"
 | 
						|
	CreatePullRequestToolName         = "create_pull_request"
 | 
						|
	CreatePullRequestReviewerToolName = "create_pull_request_reviewer"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	GetPullRequestByIndexTool = mcp.NewTool(
 | 
						|
		GetPullRequestByIndexToolName,
 | 
						|
		mcp.WithDescription("get pull request by index"),
 | 
						|
		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("repository pull request index")),
 | 
						|
	)
 | 
						|
 | 
						|
	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("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
 | 
						|
	)
 | 
						|
 | 
						|
	CreatePullRequestTool = mcp.NewTool(
 | 
						|
		CreatePullRequestToolName,
 | 
						|
		mcp.WithDescription("create pull request"),
 | 
						|
		mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
						|
		mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
						|
		mcp.WithString("title", mcp.Required(), mcp.Description("pull request title")),
 | 
						|
		mcp.WithString("body", mcp.Required(), mcp.Description("pull request body")),
 | 
						|
		mcp.WithString("head", mcp.Required(), mcp.Description("pull request head")),
 | 
						|
		mcp.WithString("base", mcp.Required(), mcp.Description("pull request base")),
 | 
						|
	)
 | 
						|
 | 
						|
	CreatePullRequestReviewerTool = mcp.NewTool(
 | 
						|
		CreatePullRequestReviewerToolName,
 | 
						|
		mcp.WithDescription("create pull request reviewer"),
 | 
						|
		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.WithArray("reviewers", mcp.Description("list of reviewer usernames"), mcp.Items(map[string]interface{}{"type": "string"})),
 | 
						|
		mcp.WithArray("team_reviewers", mcp.Description("list of team reviewer names"), mcp.Items(map[string]interface{}{"type": "string"})),
 | 
						|
	)
 | 
						|
)
 | 
						|
 | 
						|
func init() {
 | 
						|
	Tool.RegisterRead(server.ServerTool{
 | 
						|
		Tool:    GetPullRequestByIndexTool,
 | 
						|
		Handler: GetPullRequestByIndexFn,
 | 
						|
	})
 | 
						|
	Tool.RegisterRead(server.ServerTool{
 | 
						|
		Tool:    ListRepoPullRequestsTool,
 | 
						|
		Handler: ListRepoPullRequestsFn,
 | 
						|
	})
 | 
						|
	Tool.RegisterWrite(server.ServerTool{
 | 
						|
		Tool:    CreatePullRequestTool,
 | 
						|
		Handler: CreatePullRequestFn,
 | 
						|
	})
 | 
						|
	Tool.RegisterWrite(server.ServerTool{
 | 
						|
		Tool:    CreatePullRequestReviewerTool,
 | 
						|
		Handler: CreatePullRequestReviewerFn,
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func GetPullRequestByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
						|
	log.Debugf("Called GetPullRequestByIndexFn")
 | 
						|
	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"))
 | 
						|
	}
 | 
						|
	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, int64(index))
 | 
						|
	if err != nil {
 | 
						|
		return to.ErrorResult(fmt.Errorf("get %v/%v/pr/%v err: %v", owner, repo, int64(index), err))
 | 
						|
	}
 | 
						|
 | 
						|
	return to.TextResult(pr)
 | 
						|
}
 | 
						|
 | 
						|
func ListRepoPullRequestsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
						|
	log.Debugf("Called ListRepoPullRequests")
 | 
						|
	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"))
 | 
						|
	}
 | 
						|
	state, _ := req.GetArguments()["state"].(string)
 | 
						|
	sort, ok := req.GetArguments()["sort"].(string)
 | 
						|
	if !ok {
 | 
						|
		sort = "recentupdate"
 | 
						|
	}
 | 
						|
	milestone, _ := req.GetArguments()["milestone"].(float64)
 | 
						|
	page, ok := req.GetArguments()["page"].(float64)
 | 
						|
	if !ok {
 | 
						|
		page = 1
 | 
						|
	}
 | 
						|
	pageSize, ok := req.GetArguments()["pageSize"].(float64)
 | 
						|
	if !ok {
 | 
						|
		pageSize = 100
 | 
						|
	}
 | 
						|
	opt := gitea_sdk.ListPullRequestsOptions{
 | 
						|
		State:     gitea_sdk.StateType(state),
 | 
						|
		Sort:      sort,
 | 
						|
		Milestone: int64(milestone),
 | 
						|
		ListOptions: gitea_sdk.ListOptions{
 | 
						|
			Page:     int(page),
 | 
						|
			PageSize: int(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(pullRequests)
 | 
						|
}
 | 
						|
 | 
						|
func CreatePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
						|
	log.Debugf("Called CreatePullRequestFn")
 | 
						|
	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"))
 | 
						|
	}
 | 
						|
	title, ok := req.GetArguments()["title"].(string)
 | 
						|
	if !ok {
 | 
						|
		return to.ErrorResult(fmt.Errorf("title is required"))
 | 
						|
	}
 | 
						|
	body, ok := req.GetArguments()["body"].(string)
 | 
						|
	if !ok {
 | 
						|
		return to.ErrorResult(fmt.Errorf("body is required"))
 | 
						|
	}
 | 
						|
	head, ok := req.GetArguments()["head"].(string)
 | 
						|
	if !ok {
 | 
						|
		return to.ErrorResult(fmt.Errorf("head is required"))
 | 
						|
	}
 | 
						|
	base, ok := req.GetArguments()["base"].(string)
 | 
						|
	if !ok {
 | 
						|
		return to.ErrorResult(fmt.Errorf("base is required"))
 | 
						|
	}
 | 
						|
	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(pr)
 | 
						|
}
 | 
						|
 | 
						|
func CreatePullRequestReviewerFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
						|
	log.Debugf("Called CreatePullRequestReviewerFn")
 | 
						|
	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"))
 | 
						|
	}
 | 
						|
 | 
						|
	var reviewers []string
 | 
						|
	if reviewersArg, exists := req.GetArguments()["reviewers"]; exists {
 | 
						|
		if reviewersSlice, ok := reviewersArg.([]interface{}); ok {
 | 
						|
			for _, reviewer := range reviewersSlice {
 | 
						|
				if reviewerStr, ok := reviewer.(string); ok {
 | 
						|
					reviewers = append(reviewers, reviewerStr)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	var teamReviewers []string
 | 
						|
	if teamReviewersArg, exists := req.GetArguments()["team_reviewers"]; exists {
 | 
						|
		if teamReviewersSlice, ok := teamReviewersArg.([]interface{}); ok {
 | 
						|
			for _, teamReviewer := range teamReviewersSlice {
 | 
						|
				if teamReviewerStr, ok := teamReviewer.(string); ok {
 | 
						|
					teamReviewers = append(teamReviewers, teamReviewerStr)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	client, err := gitea.ClientFromContext(ctx)
 | 
						|
	if err != nil {
 | 
						|
		return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
						|
	}
 | 
						|
 | 
						|
	_, err = client.CreateReviewRequests(owner, repo, int64(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, int64(index), err))
 | 
						|
	}
 | 
						|
 | 
						|
	// Return a success message instead of the Response object which contains non-serializable functions
 | 
						|
	successMsg := map[string]interface{}{
 | 
						|
		"message":        "Successfully created review requests",
 | 
						|
		"reviewers":      reviewers,
 | 
						|
		"team_reviewers": teamReviewers,
 | 
						|
		"pr_index":       int64(index),
 | 
						|
		"repository":     fmt.Sprintf("%s/%s", owner, repo),
 | 
						|
	}
 | 
						|
 | 
						|
	return to.TextResult(successMsg)
 | 
						|
}
 |