mirror of
https://gitea.com/gitea/gitea-mcp.git
synced 2026-03-14 00:45:13 +00:00
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>
813 lines
26 KiB
Go
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))
|
|
}
|