package repo import ( "context" "errors" "fmt" "strings" "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/to" gitea_sdk "code.gitea.io/sdk/gitea" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) const ( ListRepoTopicsToolName = "list_repo_topics" SetRepoTopicsToolName = "set_repo_topics" AddRepoTopicToolName = "add_repo_topic" DeleteRepoTopicToolName = "delete_repo_topic" ) var ( ListRepoTopicsTool = mcp.NewTool( ListRepoTopicsToolName, mcp.WithDescription("List a repository's topics"), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), ) SetRepoTopicsTool = mcp.NewTool( SetRepoTopicsToolName, mcp.WithDescription("Replace all topics of a repository with a new list"), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("topics", mcp.Required(), mcp.Description("comma-separated list of topics")), ) AddRepoTopicTool = mcp.NewTool( AddRepoTopicToolName, mcp.WithDescription("Add a topic to a repository"), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("topic", mcp.Required(), mcp.Description("topic name to add")), ) DeleteRepoTopicTool = mcp.NewTool( DeleteRepoTopicToolName, mcp.WithDescription("Remove a topic from a repository"), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("topic", mcp.Required(), mcp.Description("topic name to remove")), ) ) func init() { Tool.RegisterRead(server.ServerTool{Tool: ListRepoTopicsTool, Handler: ListRepoTopicsFn}) Tool.RegisterWrite(server.ServerTool{Tool: SetRepoTopicsTool, Handler: SetRepoTopicsFn}) Tool.RegisterWrite(server.ServerTool{Tool: AddRepoTopicTool, Handler: AddRepoTopicFn}) Tool.RegisterWrite(server.ServerTool{Tool: DeleteRepoTopicTool, Handler: DeleteRepoTopicFn}) } func ListRepoTopicsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called ListRepoTopicsFn") 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)) } topics, _, err := client.ListRepoTopics(owner, repo, gitea_sdk.ListRepoTopicsOptions{}) if err != nil { return to.ErrorResult(fmt.Errorf("list repo topics err: %v", err)) } return to.TextResult(topics) } func SetRepoTopicsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called SetRepoTopicsFn") owner, _ := req.GetArguments()["owner"].(string) repo, _ := req.GetArguments()["repo"].(string) topicsStr, _ := req.GetArguments()["topics"].(string) if owner == "" || repo == "" { return to.ErrorResult(errors.New("owner and repo are required")) } var topics []string if topicsStr != "" { for _, t := range splitAndTrim(topicsStr) { if t != "" { topics = append(topics, t) } } } client, err := gitea.ClientFromContext(ctx) if err != nil { return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) } _, err = client.SetRepoTopics(owner, repo, topics) if err != nil { return to.ErrorResult(fmt.Errorf("set repo topics err: %v", err)) } return to.TextResult(map[string]any{"status": "updated", "topics": topics}) } func AddRepoTopicFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called AddRepoTopicFn") owner, _ := req.GetArguments()["owner"].(string) repo, _ := req.GetArguments()["repo"].(string) topic, _ := req.GetArguments()["topic"].(string) if owner == "" || repo == "" || topic == "" { return to.ErrorResult(errors.New("owner, repo, and topic are required")) } client, err := gitea.ClientFromContext(ctx) if err != nil { return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) } _, err = client.AddRepoTopic(owner, repo, topic) if err != nil { return to.ErrorResult(fmt.Errorf("add repo topic err: %v", err)) } return to.TextResult(map[string]string{"status": "added", "topic": topic}) } func DeleteRepoTopicFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called DeleteRepoTopicFn") owner, _ := req.GetArguments()["owner"].(string) repo, _ := req.GetArguments()["repo"].(string) topic, _ := req.GetArguments()["topic"].(string) if owner == "" || repo == "" || topic == "" { return to.ErrorResult(errors.New("owner, repo, and topic are required")) } client, err := gitea.ClientFromContext(ctx) if err != nil { return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) } _, err = client.DeleteRepoTopic(owner, repo, topic) if err != nil { return to.ErrorResult(fmt.Errorf("delete repo topic err: %v", err)) } return to.TextResult(map[string]string{"status": "deleted", "topic": topic}) } func splitAndTrim(s string) []string { var result []string for _, part := range strings.Split(s, ",") { trimmed := strings.TrimSpace(part) if trimmed != "" { result = append(result, trimmed) } } return result }