mirror of
https://gitea.com/gitea/gitea-mcp.git
synced 2026-08-02 23:11:37 +00:00
21aa4684d9
`-O` / `--tools` can already narrow the exposed tool set, but it needs every tool spelled out by name. `-S` / `--scope` (`GITEA_SCOPES`) complements it by selecting whole scopes, using the same names the `Scope` column of the tool tables documents. ```bash gitea-mcp -S issue,pull_request # only those scopes gitea-mcp --scope repository,branch --tools get_me # those scopes plus one extra tool ``` ## What changed - Each scope is one `tool.Tool` registry carrying a canonical name, so `operation/repo` is split into the six scopes its files already imply: `repository` (`repo.go` + `tree.go`), `file`, `branch`, `tag`, `commit`, `release`. The other twelve packages map 1:1, giving 18 scopes. - The two allowlists combine as a **union**: with neither set every tool loads; with only `--tools` set behaviour is unchanged; with both set, the selected scopes' tools plus the individually named tools load. `-r` / `GITEA_READONLY` still hides write tools on top. - Scope names are normalized on input (case, spaces, hyphens), so `Pull Request`, `pull-request` and `PULL_REQUEST` all resolve to `pull_request`. Unknown names only warn and list the valid scopes, mirroring how `--tools` treats unknown tool names. - The `Scope` column of all three READMEs now uses the canonical names verbatim, so it doubles as the reference for `--scope`, and `TestReadmeToolTables` compares that column against the registry in both directions — no translation table needed. - Flag parsing moves from `cmd.init()` into `Execute()`. `main()` only ever calls `Execute()`, so this is behaviourally equivalent, and it makes the `cmd` package testable at all: previously the `init()` parse of `os.Args` hit `flag.CommandLine`'s `ExitOnError` on `go test`'s own `-test.*` flags. ## Verification `make fmt`, `make lint-go` (0 issues) and `go test ./...` all pass. New tests cover the filter matrix (no filters / scope-only / tools-only regression / union / read-only interaction / unknown scope), scope-name uniqueness across `domainTools`, and the flag+env parsing and normalization. Smoke-tested `tools/list` over stdio against the built binary: | flags | tools exposed | | :-- | :-- | | _none_ | 54 (identical to `main`) | | `-S branch` | `create_branch`, `delete_branch`, `list_branches` | | `--scope 'Pull Request,TAG'` | the 4 `pull_request` + 4 `tag` tools | | `-O get_me` | `get_me` (unchanged) | | `-S commit -O get_me` | `get_commit`, `list_commits`, `get_me` | | `-S file -r` | `get_dir_contents`, `get_file_contents` | | `-S bogus,issue` | the 4 `issue` tools, plus a warning naming the valid scopes | Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/219 Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: yp05327 <576951401@qq.com>
541 lines
18 KiB
Go
541 lines
18 KiB
Go
package issue
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
|
|
"gitea.com/gitea/gitea-mcp/pkg/annotation"
|
|
"gitea.com/gitea/gitea-mcp/pkg/gitea"
|
|
"gitea.com/gitea/gitea-mcp/pkg/params"
|
|
"gitea.com/gitea/gitea-mcp/pkg/slim"
|
|
"gitea.com/gitea/gitea-mcp/pkg/to"
|
|
"gitea.com/gitea/gitea-mcp/pkg/tool"
|
|
|
|
gitea_sdk "gitea.dev/sdk"
|
|
"github.com/mark3labs/mcp-go/mcp"
|
|
"github.com/mark3labs/mcp-go/server"
|
|
)
|
|
|
|
// issueWithAssets / commentWithAssets wrap the SDK types to capture the
|
|
// `assets` field that the SDK currently drops on these endpoints.
|
|
type issueWithAssets struct {
|
|
gitea_sdk.Issue
|
|
Assets []*gitea_sdk.Attachment `json:"assets"`
|
|
}
|
|
|
|
type commentWithAssets struct {
|
|
gitea_sdk.Comment
|
|
Assets []*gitea_sdk.Attachment `json:"assets"`
|
|
}
|
|
|
|
var Tool = tool.New("issue")
|
|
|
|
const (
|
|
ListRepoIssuesToolName = "list_issues"
|
|
IssueReadToolName = "issue_read"
|
|
IssueWriteToolName = "issue_write"
|
|
)
|
|
|
|
var (
|
|
ListRepoIssuesTool = mcp.NewTool(
|
|
ListRepoIssuesToolName,
|
|
mcp.WithDescription("List issues in a repository (or pull requests, via the 'type' filter), filterable by state, labels, milestones, and update time range."),
|
|
mcp.WithToolAnnotation(annotation.ReadOnly("List repository issues")),
|
|
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
|
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
|
mcp.WithString("state", mcp.DefaultString("all")),
|
|
mcp.WithString("type", mcp.Description("issues or pulls"), mcp.Enum("issues", "pulls")),
|
|
mcp.WithArray("labels", mcp.Description("label name filter"), mcp.Items(map[string]any{"type": "string"})),
|
|
mcp.WithArray("milestones", mcp.Description("milestone name or ID filter"), mcp.Items(map[string]any{"type": "string"})),
|
|
mcp.WithString("since", mcp.Description("updated after ISO 8601")),
|
|
mcp.WithString("before", mcp.Description("updated before ISO 8601")),
|
|
mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1)),
|
|
mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)),
|
|
)
|
|
|
|
IssueReadTool = mcp.NewTool(
|
|
IssueReadToolName,
|
|
mcp.WithDescription("Read issue: details, comments, or labels."),
|
|
mcp.WithToolAnnotation(annotation.ReadOnly("Read issue details")),
|
|
mcp.WithString("method", mcp.Required(), mcp.Enum("get", "get_comments", "get_labels")),
|
|
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
|
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
|
mcp.WithNumber("issue_number", mcp.Required()),
|
|
)
|
|
|
|
IssueWriteTool = mcp.NewTool(
|
|
IssueWriteToolName,
|
|
mcp.WithDescription("Write issues: create, update, manage comments and labels."),
|
|
mcp.WithToolAnnotation(annotation.Write("Create or update issues, comments, and labels")),
|
|
mcp.WithString("method", mcp.Required(), mcp.Enum("create", "update", "add_comment", "edit_comment", "add_labels", "remove_label", "replace_labels", "clear_labels")),
|
|
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
|
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
|
mcp.WithNumber("issue_number", mcp.Description("required except for 'create'")),
|
|
mcp.WithString("title", mcp.Description("required for 'create'")),
|
|
mcp.WithString("body", mcp.Description("required for 'create'/'add_comment'/'edit_comment'")),
|
|
mcp.WithArray("assignees", mcp.Items(map[string]any{"type": "string"})),
|
|
mcp.WithNumber("milestone"),
|
|
mcp.WithString("state", mcp.Enum("open", "closed", "all")),
|
|
mcp.WithNumber("commentID", mcp.Description("for 'edit_comment'")),
|
|
mcp.WithArray("labels", mcp.Description("label IDs"), mcp.Items(map[string]any{"type": "number"})),
|
|
mcp.WithNumber("label_id", mcp.Description("for 'remove_label'")),
|
|
mcp.WithString("ref", mcp.Description("branch to associate")),
|
|
mcp.WithString("deadline", mcp.Description("ISO 8601")),
|
|
mcp.WithBoolean("remove_deadline"),
|
|
)
|
|
)
|
|
|
|
func init() {
|
|
Tool.RegisterRead(server.ServerTool{
|
|
Tool: ListRepoIssuesTool,
|
|
Handler: listRepoIssuesFn,
|
|
})
|
|
Tool.RegisterRead(server.ServerTool{
|
|
Tool: IssueReadTool,
|
|
Handler: issueReadFn,
|
|
})
|
|
Tool.RegisterWrite(server.ServerTool{
|
|
Tool: IssueWriteTool,
|
|
Handler: issueWriteFn,
|
|
})
|
|
}
|
|
|
|
func issueReadFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
args := req.GetArguments()
|
|
method, err := params.GetString(args, "method")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
switch method {
|
|
case "get":
|
|
return getIssueByIndexFn(ctx, req)
|
|
case "get_comments":
|
|
return getIssueCommentsByIndexFn(ctx, req)
|
|
case "get_labels":
|
|
return getIssueLabelsFn(ctx, req)
|
|
default:
|
|
return to.ErrorResult(fmt.Errorf("unknown method: %s", method))
|
|
}
|
|
}
|
|
|
|
func issueWriteFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
args := req.GetArguments()
|
|
method, err := params.GetString(args, "method")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
switch method {
|
|
case "create":
|
|
return createIssueFn(ctx, req)
|
|
case "update":
|
|
return editIssueFn(ctx, req)
|
|
case "add_comment":
|
|
return createIssueCommentFn(ctx, req)
|
|
case "edit_comment":
|
|
return editIssueCommentFn(ctx, req)
|
|
case "add_labels":
|
|
return addIssueLabelsFn(ctx, req)
|
|
case "remove_label":
|
|
return removeIssueLabelFn(ctx, req)
|
|
case "replace_labels":
|
|
return replaceIssueLabelsFn(ctx, req)
|
|
case "clear_labels":
|
|
return clearIssueLabelsFn(ctx, req)
|
|
default:
|
|
return to.ErrorResult(fmt.Errorf("unknown method: %s", method))
|
|
}
|
|
}
|
|
|
|
func getIssueByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
owner, err := params.GetString(req.GetArguments(), "owner")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
repo, err := params.GetString(req.GetArguments(), "repo")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
index, err := params.GetIndex(req.GetArguments(), "issue_number")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
var issue issueWithAssets
|
|
path := fmt.Sprintf("repos/%s/%s/issues/%d", url.PathEscape(owner), url.PathEscape(repo), index)
|
|
if _, err := gitea.DoJSON(ctx, "GET", path, nil, nil, &issue); err != nil {
|
|
return to.ErrorResult(fmt.Errorf("get %v/%v/issue/%v err: %v", owner, repo, index, err))
|
|
}
|
|
m := slimIssue(&issue.Issue)
|
|
m["body"] = slim.BodyWithAttachments(issue.Body, issue.Assets)
|
|
return to.TextResult(m)
|
|
}
|
|
|
|
func listRepoIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
owner, err := params.GetString(req.GetArguments(), "owner")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
repo, err := params.GetString(req.GetArguments(), "repo")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
state, ok := req.GetArguments()["state"].(string)
|
|
if !ok {
|
|
state = "all"
|
|
}
|
|
labels := params.GetStringSlice(req.GetArguments(), "labels")
|
|
milestones := params.GetStringSlice(req.GetArguments(), "milestones")
|
|
page, pageSize := params.GetPagination(req.GetArguments(), 30)
|
|
opt := gitea_sdk.ListIssueOption{
|
|
State: gitea_sdk.StateType(state),
|
|
Labels: labels,
|
|
Milestones: milestones,
|
|
ListOptions: gitea_sdk.ListOptions{
|
|
Page: page,
|
|
PageSize: pageSize,
|
|
},
|
|
}
|
|
switch req.GetArguments()["type"] {
|
|
case "issues":
|
|
opt.Type = gitea_sdk.IssueTypeIssue
|
|
case "pulls":
|
|
opt.Type = gitea_sdk.IssueTypePull
|
|
}
|
|
if t := params.GetOptionalTime(req.GetArguments(), "since"); t != nil {
|
|
opt.Since = *t
|
|
}
|
|
if t := params.GetOptionalTime(req.GetArguments(), "before"); t != nil {
|
|
opt.Before = *t
|
|
}
|
|
client, err := gitea.ClientFromContext(ctx)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
|
}
|
|
issues, _, err := client.Issues.ListRepoIssues(ctx, owner, repo, opt)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("get %v/%v/issues err: %v", owner, repo, err))
|
|
}
|
|
return to.TextResult(slimIssues(issues))
|
|
}
|
|
|
|
func createIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
owner, err := params.GetString(req.GetArguments(), "owner")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
repo, err := params.GetString(req.GetArguments(), "repo")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
title, err := params.GetString(req.GetArguments(), "title")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
body, err := params.GetString(req.GetArguments(), "body")
|
|
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))
|
|
}
|
|
opt := gitea_sdk.CreateIssueOption{
|
|
Title: title,
|
|
Body: body,
|
|
}
|
|
opt.Assignees = params.GetStringSlice(req.GetArguments(), "assignees")
|
|
if val, exists := req.GetArguments()["milestone"]; exists {
|
|
if milestone, ok := params.ToInt64(val); ok {
|
|
opt.Milestone = milestone
|
|
}
|
|
}
|
|
if labelIDs, err := params.GetInt64Slice(req.GetArguments(), "labels"); err == nil {
|
|
opt.Labels = labelIDs
|
|
}
|
|
if ref, ok := req.GetArguments()["ref"].(string); ok {
|
|
opt.Ref = ref
|
|
}
|
|
opt.Deadline = params.GetOptionalTime(req.GetArguments(), "deadline")
|
|
issue, _, err := client.Issues.CreateIssue(ctx, owner, repo, opt)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("create %v/%v/issue err: %v", owner, repo, err))
|
|
}
|
|
|
|
return to.TextResult(slimIssue(issue))
|
|
}
|
|
|
|
func createIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
owner, err := params.GetString(req.GetArguments(), "owner")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
repo, err := params.GetString(req.GetArguments(), "repo")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
index, err := params.GetIndex(req.GetArguments(), "issue_number")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
body, err := params.GetString(req.GetArguments(), "body")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
opt := gitea_sdk.CreateIssueCommentOption{
|
|
Body: body,
|
|
}
|
|
client, err := gitea.ClientFromContext(ctx)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
|
}
|
|
issueComment, _, err := client.Issues.CreateIssueComment(ctx, owner, repo, index, opt)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("create %v/%v/issue/%v/comment err: %v", owner, repo, index, err))
|
|
}
|
|
|
|
return to.TextResult(slimComment(issueComment))
|
|
}
|
|
|
|
func editIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
owner, err := params.GetString(req.GetArguments(), "owner")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
repo, err := params.GetString(req.GetArguments(), "repo")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
index, err := params.GetIndex(req.GetArguments(), "issue_number")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
|
|
args := req.GetArguments()
|
|
opt := gitea_sdk.EditIssueOption{
|
|
Body: params.GetPresentStringPtr(args, "body"),
|
|
Ref: params.GetPresentStringPtr(args, "ref"),
|
|
Assignees: params.GetStringSlice(args, "assignees"),
|
|
Deadline: params.GetOptionalTime(args, "deadline"),
|
|
RemoveDeadline: params.GetOptionalBoolPtr(args, "remove_deadline"),
|
|
}
|
|
if title, ok := args["title"].(string); ok {
|
|
opt.Title = title
|
|
}
|
|
if val, exists := args["milestone"]; exists {
|
|
if milestone, ok := params.ToInt64(val); ok {
|
|
opt.Milestone = &milestone
|
|
}
|
|
}
|
|
if state, ok := args["state"].(string); ok {
|
|
s := gitea_sdk.StateType(state)
|
|
opt.State = &s
|
|
}
|
|
|
|
client, err := gitea.ClientFromContext(ctx)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
|
}
|
|
issue, _, err := client.Issues.EditIssue(ctx, owner, repo, index, opt)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("edit %v/%v/issue/%v err: %v", owner, repo, index, err))
|
|
}
|
|
|
|
return to.TextResult(slimIssue(issue))
|
|
}
|
|
|
|
func editIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
owner, err := params.GetString(req.GetArguments(), "owner")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
repo, err := params.GetString(req.GetArguments(), "repo")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
commentID, err := params.GetIndex(req.GetArguments(), "commentID")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
body, err := params.GetString(req.GetArguments(), "body")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
opt := gitea_sdk.EditIssueCommentOption{
|
|
Body: body,
|
|
}
|
|
client, err := gitea.ClientFromContext(ctx)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
|
}
|
|
issueComment, _, err := client.Issues.EditIssueComment(ctx, owner, repo, commentID, opt)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("edit %v/%v/issues/comments/%v err: %v", owner, repo, commentID, err))
|
|
}
|
|
|
|
return to.TextResult(slimComment(issueComment))
|
|
}
|
|
|
|
func getIssueCommentsByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
owner, err := params.GetString(req.GetArguments(), "owner")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
repo, err := params.GetString(req.GetArguments(), "repo")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
index, err := params.GetIndex(req.GetArguments(), "issue_number")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
var comments []commentWithAssets
|
|
path := fmt.Sprintf("repos/%s/%s/issues/%d/comments", url.PathEscape(owner), url.PathEscape(repo), index)
|
|
if _, err := gitea.DoJSON(ctx, "GET", path, nil, nil, &comments); err != nil {
|
|
return to.ErrorResult(fmt.Errorf("get %v/%v/issues/%v/comments err: %v", owner, repo, index, err))
|
|
}
|
|
out := make([]map[string]any, 0, len(comments))
|
|
for i := range comments {
|
|
m := slimComment(&comments[i].Comment)
|
|
m["body"] = slim.BodyWithAttachments(comments[i].Body, comments[i].Assets)
|
|
out = append(out, m)
|
|
}
|
|
return to.TextResult(out)
|
|
}
|
|
|
|
func getIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
owner, err := params.GetString(req.GetArguments(), "owner")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
repo, err := params.GetString(req.GetArguments(), "repo")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
index, err := params.GetIndex(req.GetArguments(), "issue_number")
|
|
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))
|
|
}
|
|
labels, _, err := client.Issues.GetIssueLabels(ctx, owner, repo, index, gitea_sdk.ListLabelsOptions{})
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("get %v/%v/issues/%v/labels err: %v", owner, repo, index, err))
|
|
}
|
|
return to.TextResult(slim.Labels(labels))
|
|
}
|
|
|
|
func addIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
owner, err := params.GetString(req.GetArguments(), "owner")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
repo, err := params.GetString(req.GetArguments(), "repo")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
index, err := params.GetIndex(req.GetArguments(), "issue_number")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
labels, err := params.GetInt64Slice(req.GetArguments(), "labels")
|
|
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))
|
|
}
|
|
issueLabels, _, err := client.Issues.AddIssueLabels(ctx, owner, repo, index, gitea_sdk.IssueLabelsOption{Labels: labels})
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("add labels to %v/%v/issue/%v err: %v", owner, repo, index, err))
|
|
}
|
|
return to.TextResult(slim.Labels(issueLabels))
|
|
}
|
|
|
|
func replaceIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
owner, err := params.GetString(req.GetArguments(), "owner")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
repo, err := params.GetString(req.GetArguments(), "repo")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
index, err := params.GetIndex(req.GetArguments(), "issue_number")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
labels, err := params.GetInt64Slice(req.GetArguments(), "labels")
|
|
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))
|
|
}
|
|
issueLabels, _, err := client.Issues.ReplaceIssueLabels(ctx, owner, repo, index, gitea_sdk.IssueLabelsOption{Labels: labels})
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("replace labels on %v/%v/issue/%v err: %v", owner, repo, index, err))
|
|
}
|
|
return to.TextResult(slim.Labels(issueLabels))
|
|
}
|
|
|
|
func clearIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
owner, err := params.GetString(req.GetArguments(), "owner")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
repo, err := params.GetString(req.GetArguments(), "repo")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
index, err := params.GetIndex(req.GetArguments(), "issue_number")
|
|
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.Issues.ClearIssueLabels(ctx, owner, repo, index)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("clear labels on %v/%v/issue/%v err: %v", owner, repo, index, err))
|
|
}
|
|
return to.TextResult("Labels cleared successfully")
|
|
}
|
|
|
|
func removeIssueLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
owner, err := params.GetString(req.GetArguments(), "owner")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
repo, err := params.GetString(req.GetArguments(), "repo")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
index, err := params.GetIndex(req.GetArguments(), "issue_number")
|
|
if err != nil {
|
|
return to.ErrorResult(err)
|
|
}
|
|
labelID, err := params.GetIndex(req.GetArguments(), "label_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.Issues.DeleteIssueLabel(ctx, owner, repo, index, labelID)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("remove label %v from %v/%v/issue/%v err: %v", labelID, owner, repo, index, err))
|
|
}
|
|
return to.TextResult("Label removed successfully")
|
|
}
|