Rename the fork from gitea-mcp to gitea-mcp-extended to reflect the significantly expanded tool coverage (299 vs upstream's 93 tools). - Rename Go module path and all import references - Rename binary to gitea-mcp-extended in Makefile, Dockerfile, .gitignore - Point .goreleaser.yaml gitea_urls to git.lethalbits.com - Replace release-tag workflow with goreleaser + Generic Package Registry publishing - Replace release-nightly workflow with cross-platform build + nightly package publishing - Update CLAUDE.md project description and tool count
255 lines
9.3 KiB
Go
255 lines
9.3 KiB
Go
package user
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"git.lethalbits.com/lethalbits/gitea-mcp-extended/pkg/gitea"
|
|
"git.lethalbits.com/lethalbits/gitea-mcp-extended/pkg/log"
|
|
"git.lethalbits.com/lethalbits/gitea-mcp-extended/pkg/params"
|
|
"git.lethalbits.com/lethalbits/gitea-mcp-extended/pkg/to"
|
|
|
|
gitea_sdk "code.gitea.io/sdk/gitea"
|
|
"github.com/mark3labs/mcp-go/mcp"
|
|
"github.com/mark3labs/mcp-go/server"
|
|
)
|
|
|
|
const (
|
|
ListUserReposToolName = "list_user_repos"
|
|
ListMyStarredToolName = "list_my_starred"
|
|
StarRepoToolName = "star_repo"
|
|
UnstarRepoToolName = "unstar_repo"
|
|
CheckStarredToolName = "check_starred"
|
|
ListMySubscriptionsToolName = "list_my_subscriptions"
|
|
ListUserHeatmapToolName = "list_user_heatmap"
|
|
ListUserActivityToolName = "list_user_activity"
|
|
)
|
|
|
|
var (
|
|
ListUserReposTool = mcp.NewTool(
|
|
ListUserReposToolName,
|
|
mcp.WithDescription("List a user's repositories"),
|
|
mcp.WithString("username", mcp.Required(), mcp.Description("username")),
|
|
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
|
|
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
|
|
)
|
|
|
|
ListMyStarredTool = mcp.NewTool(
|
|
ListMyStarredToolName,
|
|
mcp.WithDescription("List repositories starred by the authenticated user"),
|
|
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
|
|
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
|
|
)
|
|
|
|
StarRepoTool = mcp.NewTool(
|
|
StarRepoToolName,
|
|
mcp.WithDescription("Star a repository"),
|
|
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
|
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
|
)
|
|
|
|
UnstarRepoTool = mcp.NewTool(
|
|
UnstarRepoToolName,
|
|
mcp.WithDescription("Unstar a repository"),
|
|
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
|
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
|
)
|
|
|
|
CheckStarredTool = mcp.NewTool(
|
|
CheckStarredToolName,
|
|
mcp.WithDescription("Check if the authenticated user has starred a repository"),
|
|
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
|
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
|
)
|
|
|
|
ListMySubscriptionsTool = mcp.NewTool(
|
|
ListMySubscriptionsToolName,
|
|
mcp.WithDescription("List repositories watched by the authenticated user"),
|
|
)
|
|
|
|
ListUserHeatmapTool = mcp.NewTool(
|
|
ListUserHeatmapToolName,
|
|
mcp.WithDescription("Get a user's contribution heatmap data"),
|
|
mcp.WithString("username", mcp.Required(), mcp.Description("username")),
|
|
)
|
|
|
|
ListUserActivityTool = mcp.NewTool(
|
|
ListUserActivityToolName,
|
|
mcp.WithDescription("List a user's activity feeds"),
|
|
mcp.WithString("username", mcp.Required(), mcp.Description("username")),
|
|
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
|
|
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
|
|
)
|
|
)
|
|
|
|
func init() {
|
|
Tool.RegisterRead(server.ServerTool{Tool: ListUserReposTool, Handler: ListUserReposFn})
|
|
Tool.RegisterRead(server.ServerTool{Tool: ListMyStarredTool, Handler: ListMyStarredFn})
|
|
Tool.RegisterRead(server.ServerTool{Tool: CheckStarredTool, Handler: CheckStarredFn})
|
|
Tool.RegisterRead(server.ServerTool{Tool: ListMySubscriptionsTool, Handler: ListMySubscriptionsFn})
|
|
Tool.RegisterRead(server.ServerTool{Tool: ListUserHeatmapTool, Handler: ListUserHeatmapFn})
|
|
Tool.RegisterRead(server.ServerTool{Tool: ListUserActivityTool, Handler: ListUserActivityFn})
|
|
Tool.RegisterWrite(server.ServerTool{Tool: StarRepoTool, Handler: StarRepoFn})
|
|
Tool.RegisterWrite(server.ServerTool{Tool: UnstarRepoTool, Handler: UnstarRepoFn})
|
|
}
|
|
|
|
func ListUserReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
log.Debugf("Called ListUserReposFn")
|
|
username, ok := req.GetArguments()["username"].(string)
|
|
if !ok {
|
|
return to.ErrorResult(errors.New("username is required"))
|
|
}
|
|
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))
|
|
}
|
|
repos, _, err := client.ListUserRepos(username, gitea_sdk.ListReposOptions{
|
|
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
|
|
})
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("list %s repos err: %v", username, err))
|
|
}
|
|
return to.TextResult(repos)
|
|
}
|
|
|
|
func ListMyStarredFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
log.Debugf("Called ListMyStarredFn")
|
|
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))
|
|
}
|
|
repos, _, err := client.GetMyStarredRepos()
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("list my starred repos err: %v", err))
|
|
}
|
|
// Manual pagination since SDK returns all
|
|
start := (int(page) - 1) * int(pageSize)
|
|
if start >= len(repos) {
|
|
return to.TextResult([]*gitea_sdk.Repository{})
|
|
}
|
|
end := start + int(pageSize)
|
|
if end > len(repos) {
|
|
end = len(repos)
|
|
}
|
|
return to.TextResult(repos[start:end])
|
|
}
|
|
|
|
func StarRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
log.Debugf("Called StarRepoFn")
|
|
owner, ok := req.GetArguments()["owner"].(string)
|
|
if !ok {
|
|
return to.ErrorResult(errors.New("owner is required"))
|
|
}
|
|
repo, ok := req.GetArguments()["repo"].(string)
|
|
if !ok {
|
|
return to.ErrorResult(errors.New("repo is required"))
|
|
}
|
|
client, err := gitea.ClientFromContext(ctx)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
|
}
|
|
_, err = client.StarRepo(owner, repo)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("star %s/%s err: %v", owner, repo, err))
|
|
}
|
|
return to.TextResult(map[string]string{"status": "starred", "owner": owner, "repo": repo})
|
|
}
|
|
|
|
func UnstarRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
log.Debugf("Called UnstarRepoFn")
|
|
owner, ok := req.GetArguments()["owner"].(string)
|
|
if !ok {
|
|
return to.ErrorResult(errors.New("owner is required"))
|
|
}
|
|
repo, ok := req.GetArguments()["repo"].(string)
|
|
if !ok {
|
|
return to.ErrorResult(errors.New("repo is required"))
|
|
}
|
|
client, err := gitea.ClientFromContext(ctx)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
|
}
|
|
_, err = client.UnStarRepo(owner, repo)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("unstar %s/%s err: %v", owner, repo, err))
|
|
}
|
|
return to.TextResult(map[string]string{"status": "unstarred", "owner": owner, "repo": repo})
|
|
}
|
|
|
|
func CheckStarredFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
log.Debugf("Called CheckStarredFn")
|
|
owner, ok := req.GetArguments()["owner"].(string)
|
|
if !ok {
|
|
return to.ErrorResult(errors.New("owner is required"))
|
|
}
|
|
repo, ok := req.GetArguments()["repo"].(string)
|
|
if !ok {
|
|
return to.ErrorResult(errors.New("repo is required"))
|
|
}
|
|
client, err := gitea.ClientFromContext(ctx)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
|
}
|
|
isStarred, _, err := client.IsRepoStarring(owner, repo)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("check starred %s/%s err: %v", owner, repo, err))
|
|
}
|
|
return to.TextResult(map[string]any{"owner": owner, "repo": repo, "is_starred": isStarred})
|
|
}
|
|
|
|
func ListMySubscriptionsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
log.Debugf("Called ListMySubscriptionsFn")
|
|
client, err := gitea.ClientFromContext(ctx)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
|
}
|
|
repos, _, err := client.GetMyWatchedRepos()
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("list my subscriptions err: %v", err))
|
|
}
|
|
return to.TextResult(repos)
|
|
}
|
|
|
|
func ListUserHeatmapFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
log.Debugf("Called ListUserHeatmapFn")
|
|
username, ok := req.GetArguments()["username"].(string)
|
|
if !ok {
|
|
return to.ErrorResult(errors.New("username is required"))
|
|
}
|
|
client, err := gitea.ClientFromContext(ctx)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
|
}
|
|
heatmap, _, err := client.GetUserHeatmap(username)
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("get %s heatmap err: %v", username, err))
|
|
}
|
|
return to.TextResult(heatmap)
|
|
}
|
|
|
|
func ListUserActivityFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
log.Debugf("Called ListUserActivityFn")
|
|
username, ok := req.GetArguments()["username"].(string)
|
|
if !ok {
|
|
return to.ErrorResult(errors.New("username is required"))
|
|
}
|
|
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))
|
|
}
|
|
feeds, _, err := client.ListUserActivityFeeds(username, gitea_sdk.ListUserActivityFeedsOptions{
|
|
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
|
|
})
|
|
if err != nil {
|
|
return to.ErrorResult(fmt.Errorf("list %s activity err: %v", username, err))
|
|
}
|
|
return to.TextResult(feeds)
|
|
}
|