forked from gitea/gitea-mcp
Merge branch 'main' into comment-index
This commit is contained in:
418 operation/label/label.go Normal file
418
operation/label/label.go Normal file @@ -0,0 +1,418 @@ | ||||
package label | ||||
| ||||
import ( | ||||
"context" | ||||
"fmt" | ||||
| ||||
"gitea.com/gitea/gitea-mcp/pkg/gitea" | ||||
"gitea.com/gitea/gitea-mcp/pkg/log" | ||||
"gitea.com/gitea/gitea-mcp/pkg/ptr" | ||||
"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 ( | ||||
ListRepoLabelsToolName = "list_repo_labels" | ||||
GetRepoLabelToolName = "get_repo_label" | ||||
CreateRepoLabelToolName = "create_repo_label" | ||||
EditRepoLabelToolName = "edit_repo_label" | ||||
DeleteRepoLabelToolName = "delete_repo_label" | ||||
AddIssueLabelsToolName = "add_issue_labels" | ||||
ReplaceIssueLabelsToolName = "replace_issue_labels" | ||||
ClearIssueLabelsToolName = "clear_issue_labels" | ||||
RemoveIssueLabelToolName = "remove_issue_label" | ||||
) | ||||
| ||||
var ( | ||||
ListRepoLabelsTool = mcp.NewTool( | ||||
ListRepoLabelsToolName, | ||||
mcp.WithDescription("Lists all labels for a given repository"), | ||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), | ||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), | ||||
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), | ||||
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)), | ||||
) | ||||
| ||||
GetRepoLabelTool = mcp.NewTool( | ||||
GetRepoLabelToolName, | ||||
mcp.WithDescription("Gets a single label by its ID for a repository"), | ||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), | ||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), | ||||
mcp.WithNumber("id", mcp.Required(), mcp.Description("label ID")), | ||||
) | ||||
| ||||
CreateRepoLabelTool = mcp.NewTool( | ||||
CreateRepoLabelToolName, | ||||
mcp.WithDescription("Creates a new label for a repository"), | ||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), | ||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), | ||||
mcp.WithString("name", mcp.Required(), mcp.Description("label name")), | ||||
mcp.WithString("color", mcp.Required(), mcp.Description("label color (hex code, e.g., #RRGGBB)")), | ||||
mcp.WithString("description", mcp.Description("label description")), | ||||
) | ||||
| ||||
EditRepoLabelTool = mcp.NewTool( | ||||
EditRepoLabelToolName, | ||||
mcp.WithDescription("Edits an existing label in a repository"), | ||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), | ||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), | ||||
mcp.WithNumber("id", mcp.Required(), mcp.Description("label ID")), | ||||
mcp.WithString("name", mcp.Description("new label name")), | ||||
mcp.WithString("color", mcp.Description("new label color (hex code, e.g., #RRGGBB)")), | ||||
mcp.WithString("description", mcp.Description("new label description")), | ||||
) | ||||
| ||||
DeleteRepoLabelTool = mcp.NewTool( | ||||
DeleteRepoLabelToolName, | ||||
mcp.WithDescription("Deletes a label from a repository"), | ||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), | ||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), | ||||
mcp.WithNumber("id", mcp.Required(), mcp.Description("label ID")), | ||||
) | ||||
| ||||
AddIssueLabelsTool = mcp.NewTool( | ||||
AddIssueLabelsToolName, | ||||
mcp.WithDescription("Adds one or more labels to an issue"), | ||||
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("issue index")), | ||||
mcp.WithArray("labels", mcp.Required(), mcp.Description("array of label IDs to add"), mcp.Items(map[string]interface{}{"type": "number"})), | ||||
) | ||||
| ||||
ReplaceIssueLabelsTool = mcp.NewTool( | ||||
ReplaceIssueLabelsToolName, | ||||
mcp.WithDescription("Replaces all labels on an issue"), | ||||
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("issue index")), | ||||
mcp.WithArray("labels", mcp.Required(), mcp.Description("array of label IDs to replace with"), mcp.Items(map[string]interface{}{"type": "number"})), | ||||
) | ||||
| ||||
ClearIssueLabelsTool = mcp.NewTool( | ||||
ClearIssueLabelsToolName, | ||||
mcp.WithDescription("Removes all labels from an issue"), | ||||
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("issue index")), | ||||
) | ||||
| ||||
RemoveIssueLabelTool = mcp.NewTool( | ||||
RemoveIssueLabelToolName, | ||||
mcp.WithDescription("Removes a single label from an issue"), | ||||
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("issue index")), | ||||
mcp.WithNumber("label_id", mcp.Required(), mcp.Description("label ID to remove")), | ||||
) | ||||
) | ||||
| ||||
func init() { | ||||
Tool.RegisterRead(server.ServerTool{ | ||||
Tool: ListRepoLabelsTool, | ||||
Handler: ListRepoLabelsFn, | ||||
}) | ||||
Tool.RegisterRead(server.ServerTool{ | ||||
Tool: GetRepoLabelTool, | ||||
Handler: GetRepoLabelFn, | ||||
}) | ||||
Tool.RegisterWrite(server.ServerTool{ | ||||
Tool: CreateRepoLabelTool, | ||||
Handler: CreateRepoLabelFn, | ||||
}) | ||||
Tool.RegisterWrite(server.ServerTool{ | ||||
Tool: EditRepoLabelTool, | ||||
Handler: EditRepoLabelFn, | ||||
}) | ||||
Tool.RegisterWrite(server.ServerTool{ | ||||
Tool: DeleteRepoLabelTool, | ||||
Handler: DeleteRepoLabelFn, | ||||
}) | ||||
Tool.RegisterWrite(server.ServerTool{ | ||||
Tool: AddIssueLabelsTool, | ||||
Handler: AddIssueLabelsFn, | ||||
}) | ||||
Tool.RegisterWrite(server.ServerTool{ | ||||
Tool: ReplaceIssueLabelsTool, | ||||
Handler: ReplaceIssueLabelsFn, | ||||
}) | ||||
Tool.RegisterWrite(server.ServerTool{ | ||||
Tool: ClearIssueLabelsTool, | ||||
Handler: ClearIssueLabelsFn, | ||||
}) | ||||
Tool.RegisterWrite(server.ServerTool{ | ||||
Tool: RemoveIssueLabelTool, | ||||
Handler: RemoveIssueLabelFn, | ||||
}) | ||||
} | ||||
| ||||
func ListRepoLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { | ||||
log.Debugf("Called ListRepoLabelsFn") | ||||
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")) | ||||
} | ||||
page, ok := req.GetArguments()["page"].(float64) | ||||
if !ok { | ||||
page = 1 | ||||
} | ||||
pageSize, ok := req.GetArguments()["pageSize"].(float64) | ||||
if !ok { | ||||
pageSize = 100 | ||||
} | ||||
| ||||
opt := gitea_sdk.ListLabelsOptions{ | ||||
ListOptions: gitea_sdk.ListOptions{ | ||||
Page: int(page), | ||||
PageSize: int(pageSize), | ||||
}, | ||||
} | ||||
labels, _, err := gitea.Client().ListRepoLabels(owner, repo, opt) | ||||
if err != nil { | ||||
return to.ErrorResult(fmt.Errorf("list %v/%v/labels err: %v", owner, repo, err)) | ||||
} | ||||
return to.TextResult(labels) | ||||
} | ||||
| ||||
func GetRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { | ||||
log.Debugf("Called GetRepoLabelFn") | ||||
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")) | ||||
} | ||||
id, ok := req.GetArguments()["id"].(float64) | ||||
if !ok { | ||||
return to.ErrorResult(fmt.Errorf("label ID is required")) | ||||
} | ||||
| ||||
label, _, err := gitea.Client().GetRepoLabel(owner, repo, int64(id)) | ||||
if err != nil { | ||||
return to.ErrorResult(fmt.Errorf("get %v/%v/label/%v err: %v", owner, repo, int64(id), err)) | ||||
} | ||||
return to.TextResult(label) | ||||
} | ||||
| ||||
func CreateRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { | ||||
log.Debugf("Called CreateRepoLabelFn") | ||||
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")) | ||||
} | ||||
name, ok := req.GetArguments()["name"].(string) | ||||
if !ok { | ||||
return to.ErrorResult(fmt.Errorf("name is required")) | ||||
} | ||||
color, ok := req.GetArguments()["color"].(string) | ||||
if !ok { | ||||
return to.ErrorResult(fmt.Errorf("color is required")) | ||||
} | ||||
description, _ := req.GetArguments()["description"].(string) // Optional | ||||
| ||||
opt := gitea_sdk.CreateLabelOption{ | ||||
Name: name, | ||||
Color: color, | ||||
Description: description, | ||||
} | ||||
| ||||
label, _, err := gitea.Client().CreateLabel(owner, repo, opt) | ||||
if err != nil { | ||||
return to.ErrorResult(fmt.Errorf("create %v/%v/label err: %v", owner, repo, err)) | ||||
} | ||||
return to.TextResult(label) | ||||
} | ||||
| ||||
func EditRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { | ||||
log.Debugf("Called EditRepoLabelFn") | ||||
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")) | ||||
} | ||||
id, ok := req.GetArguments()["id"].(float64) | ||||
if !ok { | ||||
return to.ErrorResult(fmt.Errorf("label ID is required")) | ||||
} | ||||
| ||||
opt := gitea_sdk.EditLabelOption{} | ||||
if name, ok := req.GetArguments()["name"].(string); ok { | ||||
opt.Name = ptr.To(name) | ||||
} | ||||
if color, ok := req.GetArguments()["color"].(string); ok { | ||||
opt.Color = ptr.To(color) | ||||
} | ||||
if description, ok := req.GetArguments()["description"].(string); ok { | ||||
opt.Description = ptr.To(description) | ||||
} | ||||
| ||||
label, _, err := gitea.Client().EditLabel(owner, repo, int64(id), opt) | ||||
if err != nil { | ||||
return to.ErrorResult(fmt.Errorf("edit %v/%v/label/%v err: %v", owner, repo, int64(id), err)) | ||||
} | ||||
return to.TextResult(label) | ||||
} | ||||
| ||||
func DeleteRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { | ||||
log.Debugf("Called DeleteRepoLabelFn") | ||||
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")) | ||||
} | ||||
id, ok := req.GetArguments()["id"].(float64) | ||||
if !ok { | ||||
return to.ErrorResult(fmt.Errorf("label ID is required")) | ||||
} | ||||
| ||||
_, err := gitea.Client().DeleteLabel(owner, repo, int64(id)) | ||||
if err != nil { | ||||
return to.ErrorResult(fmt.Errorf("delete %v/%v/label/%v err: %v", owner, repo, int64(id), err)) | ||||
} | ||||
return to.TextResult("Label deleted successfully") | ||||
} | ||||
| ||||
func AddIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { | ||||
log.Debugf("Called AddIssueLabelsFn") | ||||
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("issue index is required")) | ||||
} | ||||
labelsRaw, ok := req.GetArguments()["labels"].([]interface{}) | ||||
if !ok { | ||||
return to.ErrorResult(fmt.Errorf("labels (array of IDs) is required")) | ||||
} | ||||
var labels []int64 | ||||
for _, l := range labelsRaw { | ||||
if labelID, ok := l.(float64); ok { | ||||
labels = append(labels, int64(labelID)) | ||||
} else { | ||||
return to.ErrorResult(fmt.Errorf("invalid label ID in labels array")) | ||||
} | ||||
} | ||||
| ||||
opt := gitea_sdk.IssueLabelsOption{ | ||||
Labels: labels, | ||||
} | ||||
| ||||
issueLabels, _, err := gitea.Client().AddIssueLabels(owner, repo, int64(index), opt) | ||||
if err != nil { | ||||
return to.ErrorResult(fmt.Errorf("add labels to %v/%v/issue/%v err: %v", owner, repo, int64(index), err)) | ||||
} | ||||
return to.TextResult(issueLabels) | ||||
} | ||||
| ||||
func ReplaceIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { | ||||
log.Debugf("Called ReplaceIssueLabelsFn") | ||||
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("issue index is required")) | ||||
} | ||||
labelsRaw, ok := req.GetArguments()["labels"].([]interface{}) | ||||
if !ok { | ||||
return to.ErrorResult(fmt.Errorf("labels (array of IDs) is required")) | ||||
} | ||||
var labels []int64 | ||||
for _, l := range labelsRaw { | ||||
if labelID, ok := l.(float64); ok { | ||||
labels = append(labels, int64(labelID)) | ||||
} else { | ||||
return to.ErrorResult(fmt.Errorf("invalid label ID in labels array")) | ||||
} | ||||
} | ||||
| ||||
opt := gitea_sdk.IssueLabelsOption{ | ||||
Labels: labels, | ||||
} | ||||
| ||||
issueLabels, _, err := gitea.Client().ReplaceIssueLabels(owner, repo, int64(index), opt) | ||||
if err != nil { | ||||
return to.ErrorResult(fmt.Errorf("replace labels on %v/%v/issue/%v err: %v", owner, repo, int64(index), err)) | ||||
} | ||||
return to.TextResult(issueLabels) | ||||
} | ||||
| ||||
func ClearIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { | ||||
log.Debugf("Called ClearIssueLabelsFn") | ||||
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("issue index is required")) | ||||
} | ||||
| ||||
_, err := gitea.Client().ClearIssueLabels(owner, repo, int64(index)) | ||||
if err != nil { | ||||
return to.ErrorResult(fmt.Errorf("clear labels on %v/%v/issue/%v err: %v", owner, repo, int64(index), err)) | ||||
} | ||||
return to.TextResult("Labels cleared successfully") | ||||
} | ||||
| ||||
func RemoveIssueLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { | ||||
log.Debugf("Called RemoveIssueLabelFn") | ||||
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("issue index is required")) | ||||
} | ||||
labelID, ok := req.GetArguments()["label_id"].(float64) | ||||
if !ok { | ||||
return to.ErrorResult(fmt.Errorf("label ID is required")) | ||||
} | ||||
| ||||
_, err := gitea.Client().DeleteIssueLabel(owner, repo, int64(index), int64(labelID)) | ||||
if err != nil { | ||||
return to.ErrorResult(fmt.Errorf("remove label %v from %v/%v/issue/%v err: %v", int64(labelID), owner, repo, int64(index), err)) | ||||
} | ||||
return to.TextResult("Label removed successfully") | ||||
} |
@@ -5,6 +5,7 @@ import ( | ||||
"time" | ||||
| ||||
"gitea.com/gitea/gitea-mcp/operation/issue" | ||||
"gitea.com/gitea/gitea-mcp/operation/label" | ||||
"gitea.com/gitea/gitea-mcp/operation/pull" | ||||
"gitea.com/gitea/gitea-mcp/operation/repo" | ||||
"gitea.com/gitea/gitea-mcp/operation/search" | ||||
@@ -28,6 +29,9 @@ func RegisterTool(s *server.MCPServer) { | ||||
// Issue Tool | ||||
s.AddTools(issue.Tool.Tools()...) | ||||
| ||||
// Label Tool | ||||
s.AddTools(label.Tool.Tools()...) | ||||
| ||||
// Pull Tool | ||||
s.AddTools(pull.Tool.Tools()...) | ||||
| ||||
|
@@ -149,6 +149,16 @@ func GetFileContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo | ||||
}) | ||||
| ||||
} | ||||
if err := scanner.Err(); err != nil { | ||||
return to.ErrorResult(fmt.Errorf("scan content err: %v", err)) | ||||
} | ||||
| ||||
// remove the last blank line if exists | ||||
// git does not consider the last line as a new line | ||||
if contentLines[len(contentLines)-1].Content == "" { | ||||
contentLines = contentLines[:len(contentLines)-1] | ||||
} | ||||
| ||||
contentBytes, err := json.MarshalIndent(contentLines, "", " ") | ||||
if err != nil { | ||||
return to.ErrorResult(fmt.Errorf("marshal content lines err: %v", err)) | ||||
|
@@ -34,6 +34,7 @@ var ( | ||||
mcp.WithString("title", mcp.Required(), mcp.Description("release title")), | ||||
mcp.WithBoolean("is_draft", mcp.Description("Whether the release is draft"), mcp.DefaultBool(false)), | ||||
mcp.WithBoolean("is_pre_release", mcp.Description("Whether the release is pre-release"), mcp.DefaultBool(false)), | ||||
mcp.WithString("body", mcp.Description("release body")), | ||||
) | ||||
| ||||
DeleteReleaseTool = mcp.NewTool( | ||||
@@ -131,11 +132,13 @@ func CreateReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo | ||||
} | ||||
isDraft, _ := req.GetArguments()["is_draft"].(bool) | ||||
isPreRelease, _ := req.GetArguments()["is_pre_release"].(bool) | ||||
body, _ := req.GetArguments()["body"].(string) | ||||
| ||||
_, _, err := gitea.Client().CreateRelease(owner, repo, gitea_sdk.CreateReleaseOption{ | ||||
TagName: tagName, | ||||
Target: target, | ||||
Title: title, | ||||
Note: body, | ||||
IsDraft: isDraft, | ||||
IsPrerelease: isPreRelease, | ||||
}) | ||||
|
Reference in New Issue
Block a user