feat: expand MCP tool coverage from 93 to 299 tools
Some checks failed
release-nightly / release-image (push) Failing after 2m17s

Fork the official gitea-mcp and massively extend API coverage:

- Organization: orgs, members, teams, hooks, blocks, activity (34 tools)
- User: profile, followers, keys, emails, repos, blocks (30 tools)
- Repository: collaborators, webhooks, branch/tag protection, deploy keys,
  topics, git refs/trees/notes, commit status, stars/watchers, forks,
  transfers, mirrors, templates (53 tools)
- Issue: reactions, pins, subscriptions, timeline, templates (16 tools)
- Notifications: list, check, read, repo-scoped (7 tools)
- Settings: API, attachment, repo, UI settings (4 tools)
- Packages: list, get, delete, files, latest, link/unlink (7 tools)
- Miscellaneous: server version, gitignore/label/license templates,
  markdown/markup rendering, node info, signing keys (12 tools)
- Admin: user/org/repo CRUD, system webhooks, cron tasks, unadopted
  repos, emails, badges (23 tools)

Module path updated to git.lethalbits.com/lethalbits/gitea-mcp.
This commit is contained in:
Andrew Miller
2026-03-05 11:03:20 -05:00
parent 9ce5604e4c
commit df25d328d1
66 changed files with 6976 additions and 122 deletions

View File

@@ -5,11 +5,11 @@ import (
"errors"
"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"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/tool"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"

151
operation/issue/pin.go Normal file
View File

@@ -0,0 +1,151 @@
package issue
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
ListPinnedIssuesToolName = "list_pinned_issues"
PinIssueToolName = "pin_issue"
UnpinIssueToolName = "unpin_issue"
MoveIssuePinToolName = "move_issue_pin"
)
var (
ListPinnedIssuesTool = mcp.NewTool(
ListPinnedIssuesToolName,
mcp.WithDescription("List pinned issues in a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
)
PinIssueTool = mcp.NewTool(
PinIssueToolName,
mcp.WithDescription("Pin an issue in a repository"),
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")),
)
UnpinIssueTool = mcp.NewTool(
UnpinIssueToolName,
mcp.WithDescription("Unpin an issue from a repository"),
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")),
)
MoveIssuePinTool = mcp.NewTool(
MoveIssuePinToolName,
mcp.WithDescription("Move a pinned issue to a new position"),
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("position", mcp.Required(), mcp.Description("new position (1-based)")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListPinnedIssuesTool, Handler: ListPinnedIssuesFn})
Tool.RegisterWrite(server.ServerTool{Tool: PinIssueTool, Handler: PinIssueFn})
Tool.RegisterWrite(server.ServerTool{Tool: UnpinIssueTool, Handler: UnpinIssueFn})
Tool.RegisterWrite(server.ServerTool{Tool: MoveIssuePinTool, Handler: MoveIssuePinFn})
}
func ListPinnedIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListPinnedIssuesFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
issues, _, err := client.ListRepoPinnedIssues(owner, repo)
if err != nil {
return to.ErrorResult(fmt.Errorf("list pinned issues err: %v", err))
}
return to.TextResult(issues)
}
func PinIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called PinIssueFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
index, err := params.GetIndex(req.GetArguments(), "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))
}
_, err = client.PinIssue(owner, repo, index)
if err != nil {
return to.ErrorResult(fmt.Errorf("pin issue err: %v", err))
}
return to.TextResult(map[string]string{"status": "pinned"})
}
func UnpinIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called UnpinIssueFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
index, err := params.GetIndex(req.GetArguments(), "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))
}
_, err = client.UnpinIssue(owner, repo, index)
if err != nil {
return to.ErrorResult(fmt.Errorf("unpin issue err: %v", err))
}
return to.TextResult(map[string]string{"status": "unpinned"})
}
func MoveIssuePinFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called MoveIssuePinFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil {
return to.ErrorResult(err)
}
position, ok := req.GetArguments()["position"].(float64)
if !ok {
return to.ErrorResult(errors.New("position is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.MoveIssuePin(owner, repo, index, int64(position))
if err != nil {
return to.ErrorResult(fmt.Errorf("move issue pin err: %v", err))
}
return to.TextResult(map[string]any{"status": "moved", "position": int64(position)})
}

224
operation/issue/reaction.go Normal file
View File

@@ -0,0 +1,224 @@
package issue
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
ListIssueReactionsToolName = "list_issue_reactions"
PostIssueReactionToolName = "post_issue_reaction"
DeleteIssueReactionToolName = "delete_issue_reaction"
GetIssueCommentReactionsToolName = "get_issue_comment_reactions"
PostIssueCommentReactionToolName = "post_issue_comment_reaction"
DeleteIssueCommentReactionToolName = "delete_issue_comment_reaction"
)
var (
ListIssueReactionsTool = mcp.NewTool(
ListIssueReactionsToolName,
mcp.WithDescription("List reactions 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")),
)
PostIssueReactionTool = mcp.NewTool(
PostIssueReactionToolName,
mcp.WithDescription("Add a reaction 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.WithString("reaction", mcp.Required(), mcp.Description("reaction emoji (e.g., +1, -1, laugh, confused, heart, hooray, rocket, eyes)")),
)
DeleteIssueReactionTool = mcp.NewTool(
DeleteIssueReactionToolName,
mcp.WithDescription("Remove a reaction 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.WithString("reaction", mcp.Required(), mcp.Description("reaction emoji to remove")),
)
GetIssueCommentReactionsTool = mcp.NewTool(
GetIssueCommentReactionsToolName,
mcp.WithDescription("List reactions on an issue comment"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithNumber("comment_id", mcp.Required(), mcp.Description("comment ID")),
)
PostIssueCommentReactionTool = mcp.NewTool(
PostIssueCommentReactionToolName,
mcp.WithDescription("Add a reaction to an issue comment"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithNumber("comment_id", mcp.Required(), mcp.Description("comment ID")),
mcp.WithString("reaction", mcp.Required(), mcp.Description("reaction emoji")),
)
DeleteIssueCommentReactionTool = mcp.NewTool(
DeleteIssueCommentReactionToolName,
mcp.WithDescription("Remove a reaction from an issue comment"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithNumber("comment_id", mcp.Required(), mcp.Description("comment ID")),
mcp.WithString("reaction", mcp.Required(), mcp.Description("reaction emoji to remove")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListIssueReactionsTool, Handler: ListIssueReactionsFn})
Tool.RegisterRead(server.ServerTool{Tool: GetIssueCommentReactionsTool, Handler: GetIssueCommentReactionsFn})
Tool.RegisterWrite(server.ServerTool{Tool: PostIssueReactionTool, Handler: PostIssueReactionFn})
Tool.RegisterWrite(server.ServerTool{Tool: DeleteIssueReactionTool, Handler: DeleteIssueReactionFn})
Tool.RegisterWrite(server.ServerTool{Tool: PostIssueCommentReactionTool, Handler: PostIssueCommentReactionFn})
Tool.RegisterWrite(server.ServerTool{Tool: DeleteIssueCommentReactionTool, Handler: DeleteIssueCommentReactionFn})
}
func ListIssueReactionsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListIssueReactionsFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
index, err := params.GetIndex(req.GetArguments(), "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))
}
reactions, _, err := client.ListIssueReactions(owner, repo, index, gitea_sdk.ListIssueReactionsOptions{})
if err != nil {
return to.ErrorResult(fmt.Errorf("list issue reactions err: %v", err))
}
return to.TextResult(reactions)
}
func PostIssueReactionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called PostIssueReactionFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
reaction, _ := req.GetArguments()["reaction"].(string)
if owner == "" || repo == "" || reaction == "" {
return to.ErrorResult(errors.New("owner, repo, and reaction are required"))
}
index, err := params.GetIndex(req.GetArguments(), "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))
}
r, _, err := client.PostIssueReaction(owner, repo, index, reaction)
if err != nil {
return to.ErrorResult(fmt.Errorf("post issue reaction err: %v", err))
}
return to.TextResult(r)
}
func DeleteIssueReactionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteIssueReactionFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
reaction, _ := req.GetArguments()["reaction"].(string)
if owner == "" || repo == "" || reaction == "" {
return to.ErrorResult(errors.New("owner, repo, and reaction are required"))
}
index, err := params.GetIndex(req.GetArguments(), "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))
}
_, err = client.DeleteIssueReaction(owner, repo, index, reaction)
if err != nil {
return to.ErrorResult(fmt.Errorf("delete issue reaction err: %v", err))
}
return to.TextResult(map[string]string{"status": "removed", "reaction": reaction})
}
func GetIssueCommentReactionsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetIssueCommentReactionsFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
commentID, err := params.GetIndex(req.GetArguments(), "comment_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))
}
reactions, _, err := client.GetIssueCommentReactions(owner, repo, commentID)
if err != nil {
return to.ErrorResult(fmt.Errorf("get issue comment reactions err: %v", err))
}
return to.TextResult(reactions)
}
func PostIssueCommentReactionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called PostIssueCommentReactionFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
reaction, _ := req.GetArguments()["reaction"].(string)
if owner == "" || repo == "" || reaction == "" {
return to.ErrorResult(errors.New("owner, repo, and reaction are required"))
}
commentID, err := params.GetIndex(req.GetArguments(), "comment_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))
}
r, _, err := client.PostIssueCommentReaction(owner, repo, commentID, reaction)
if err != nil {
return to.ErrorResult(fmt.Errorf("post issue comment reaction err: %v", err))
}
return to.TextResult(r)
}
func DeleteIssueCommentReactionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteIssueCommentReactionFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
reaction, _ := req.GetArguments()["reaction"].(string)
if owner == "" || repo == "" || reaction == "" {
return to.ErrorResult(errors.New("owner, repo, and reaction are required"))
}
commentID, err := params.GetIndex(req.GetArguments(), "comment_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.DeleteIssueCommentReaction(owner, repo, commentID, reaction)
if err != nil {
return to.ErrorResult(fmt.Errorf("delete issue comment reaction err: %v", err))
}
return to.TextResult(map[string]string{"status": "removed", "reaction": reaction})
}

View File

@@ -0,0 +1,152 @@
package issue
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
ListIssueSubscribersToolName = "list_issue_subscribers"
CheckIssueSubscriptionToolName = "check_issue_subscription"
IssueSubscribeToolName = "issue_subscribe"
IssueUnSubscribeToolName = "issue_unsubscribe"
)
var (
ListIssueSubscribersTool = mcp.NewTool(
ListIssueSubscribersToolName,
mcp.WithDescription("List users subscribed 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")),
)
CheckIssueSubscriptionTool = mcp.NewTool(
CheckIssueSubscriptionToolName,
mcp.WithDescription("Check if the authenticated user is subscribed 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")),
)
IssueSubscribeTool = mcp.NewTool(
IssueSubscribeToolName,
mcp.WithDescription("Subscribe 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")),
)
IssueUnSubscribeTool = mcp.NewTool(
IssueUnSubscribeToolName,
mcp.WithDescription("Unsubscribe 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")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListIssueSubscribersTool, Handler: ListIssueSubscribersFn})
Tool.RegisterRead(server.ServerTool{Tool: CheckIssueSubscriptionTool, Handler: CheckIssueSubscriptionFn})
Tool.RegisterWrite(server.ServerTool{Tool: IssueSubscribeTool, Handler: IssueSubscribeFn})
Tool.RegisterWrite(server.ServerTool{Tool: IssueUnSubscribeTool, Handler: IssueUnSubscribeFn})
}
func ListIssueSubscribersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListIssueSubscribersFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
index, err := params.GetIndex(req.GetArguments(), "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))
}
users, _, err := client.ListIssueSubscribers(owner, repo, index, gitea_sdk.ListIssueSubscribersOptions{})
if err != nil {
return to.ErrorResult(fmt.Errorf("list issue subscribers err: %v", err))
}
return to.TextResult(users)
}
func CheckIssueSubscriptionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CheckIssueSubscriptionFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
index, err := params.GetIndex(req.GetArguments(), "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))
}
info, _, err := client.CheckIssueSubscription(owner, repo, index)
if err != nil {
return to.ErrorResult(fmt.Errorf("check issue subscription err: %v", err))
}
return to.TextResult(info)
}
func IssueSubscribeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called IssueSubscribeFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
index, err := params.GetIndex(req.GetArguments(), "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))
}
_, err = client.IssueSubscribe(owner, repo, index)
if err != nil {
return to.ErrorResult(fmt.Errorf("issue subscribe err: %v", err))
}
return to.TextResult(map[string]string{"status": "subscribed"})
}
func IssueUnSubscribeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called IssueUnSubscribeFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
index, err := params.GetIndex(req.GetArguments(), "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))
}
_, err = client.IssueUnSubscribe(owner, repo, index)
if err != nil {
return to.ErrorResult(fmt.Errorf("issue unsubscribe err: %v", err))
}
return to.TextResult(map[string]string{"status": "unsubscribed"})
}

View File

@@ -0,0 +1,89 @@
package issue
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
ListIssueTimelineToolName = "list_issue_timeline"
GetIssueTemplatesToolName = "get_issue_templates"
)
var (
ListIssueTimelineTool = mcp.NewTool(
ListIssueTimelineToolName,
mcp.WithDescription("List timeline events for an issue (comments, labels, milestones, references, etc.)"),
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("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
GetIssueTemplatesTool = mcp.NewTool(
GetIssueTemplatesToolName,
mcp.WithDescription("Get issue templates for a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListIssueTimelineTool, Handler: ListIssueTimelineFn})
Tool.RegisterRead(server.ServerTool{Tool: GetIssueTemplatesTool, Handler: GetIssueTemplatesFn})
}
func ListIssueTimelineFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListIssueTimelineFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil {
return to.ErrorResult(err)
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
events, _, err := client.ListIssueTimeline(owner, repo, index, gitea_sdk.ListIssueCommentOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list issue timeline err: %v", err))
}
return to.TextResult(events)
}
func GetIssueTemplatesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetIssueTemplatesFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
templates, _, err := client.GetIssueTemplates(owner, repo)
if err != nil {
return to.ErrorResult(fmt.Errorf("get issue templates err: %v", err))
}
return to.TextResult(templates)
}