package repo 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 ( ListStargazersToolName = "list_stargazers" ListForksToolName = "list_forks" GetWatchedReposToolName = "get_watched_repos" GetMyWatchedReposToolName = "get_my_watched_repos" CheckRepoWatchToolName = "check_repo_watch" WatchRepoToolName = "watch_repo" UnWatchRepoToolName = "unwatch_repo" ) var ( ListStargazersTool = mcp.NewTool( ListStargazersToolName, mcp.WithDescription("List a repository's stargazers"), 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)), ) ListForksTool = mcp.NewTool( ListForksToolName, mcp.WithDescription("List a repository's forks"), 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)), ) GetWatchedReposTool = mcp.NewTool( GetWatchedReposToolName, mcp.WithDescription("List repositories watched by a user"), mcp.WithString("username", mcp.Required(), mcp.Description("username")), ) GetMyWatchedReposTool = mcp.NewTool( GetMyWatchedReposToolName, mcp.WithDescription("List repositories watched by the authenticated user"), ) CheckRepoWatchTool = mcp.NewTool( CheckRepoWatchToolName, mcp.WithDescription("Check if the authenticated user is watching a repository"), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), ) WatchRepoTool = mcp.NewTool( WatchRepoToolName, mcp.WithDescription("Watch a repository"), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), ) UnWatchRepoTool = mcp.NewTool( UnWatchRepoToolName, mcp.WithDescription("Unwatch 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: ListStargazersTool, Handler: ListStargazersFn}) Tool.RegisterRead(server.ServerTool{Tool: ListForksTool, Handler: ListForksFn}) Tool.RegisterRead(server.ServerTool{Tool: GetWatchedReposTool, Handler: GetWatchedReposFn}) Tool.RegisterRead(server.ServerTool{Tool: GetMyWatchedReposTool, Handler: GetMyWatchedReposFn}) Tool.RegisterRead(server.ServerTool{Tool: CheckRepoWatchTool, Handler: CheckRepoWatchFn}) Tool.RegisterWrite(server.ServerTool{Tool: WatchRepoTool, Handler: WatchRepoFn}) Tool.RegisterWrite(server.ServerTool{Tool: UnWatchRepoTool, Handler: UnWatchRepoFn}) } func ListStargazersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called ListStargazersFn") owner, _ := req.GetArguments()["owner"].(string) repo, _ := req.GetArguments()["repo"].(string) if owner == "" || repo == "" { return to.ErrorResult(errors.New("owner and repo are 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)) } users, _, err := client.ListRepoStargazers(owner, repo, gitea_sdk.ListStargazersOptions{ ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)}, }) if err != nil { return to.ErrorResult(fmt.Errorf("list stargazers err: %v", err)) } return to.TextResult(users) } func ListForksFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called ListForksFn") owner, _ := req.GetArguments()["owner"].(string) repo, _ := req.GetArguments()["repo"].(string) if owner == "" || repo == "" { return to.ErrorResult(errors.New("owner and repo are 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.ListForks(owner, repo, gitea_sdk.ListForksOptions{ ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)}, }) if err != nil { return to.ErrorResult(fmt.Errorf("list forks err: %v", err)) } return to.TextResult(repos) } func GetWatchedReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called GetWatchedReposFn") username, _ := req.GetArguments()["username"].(string) if username == "" { 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)) } repos, _, err := client.GetWatchedRepos(username) if err != nil { return to.ErrorResult(fmt.Errorf("get watched repos err: %v", err)) } return to.TextResult(repos) } func GetMyWatchedReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called GetMyWatchedReposFn") 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("get my watched repos err: %v", err)) } return to.TextResult(repos) } func CheckRepoWatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called CheckRepoWatchFn") 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)) } watching, _, err := client.CheckRepoWatch(owner, repo) if err != nil { return to.ErrorResult(fmt.Errorf("check repo watch err: %v", err)) } return to.TextResult(map[string]any{"watching": watching}) } func WatchRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called WatchRepoFn") 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)) } _, err = client.WatchRepo(owner, repo) if err != nil { return to.ErrorResult(fmt.Errorf("watch repo err: %v", err)) } return to.TextResult(map[string]string{"status": "watching"}) } func UnWatchRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called UnWatchRepoFn") 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)) } _, err = client.UnWatchRepo(owner, repo) if err != nil { return to.ErrorResult(fmt.Errorf("unwatch repo err: %v", err)) } return to.TextResult(map[string]string{"status": "unwatched"}) }