package repo import ( "context" "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_sdk "code.gitea.io/sdk/gitea" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) const ( TopicReadToolName = "topic_read" TopicWriteToolName = "topic_write" ) var ( TopicReadTool = mcp.NewTool( TopicReadToolName, mcp.WithDescription("List repository topics."), mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("list")), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), ) TopicWriteTool = mcp.NewTool( TopicWriteToolName, mcp.WithDescription("Manage repository topics. Use 'add' to add one, 'delete' to remove one, 'set' to replace all topics."), mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("add", "delete", "set")), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("topic", mcp.Description("single topic name (required for 'add', 'delete')")), mcp.WithArray("topics", mcp.Description("list of topic names (required for 'set')"), mcp.Items(map[string]any{"type": "string"})), ) ) func init() { Tool.RegisterRead(server.ServerTool{ Tool: TopicReadTool, Handler: topicReadFn, }) Tool.RegisterWrite(server.ServerTool{ Tool: TopicWriteTool, Handler: topicWriteFn, }) } func topicReadFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called topicReadFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { return to.ErrorResult(err) } repo, err := params.GetString(args, "repo") 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)) } topics, _, err := client.ListRepoTopics(owner, repo, gitea_sdk.ListRepoTopicsOptions{}) if err != nil { return to.ErrorResult(fmt.Errorf("list topics %v/%v err: %v", owner, repo, err)) } return to.TextResult(topics) } func topicWriteFn(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 "add": return addTopicFn(ctx, req) case "delete": return deleteTopicFn(ctx, req) case "set": return setTopicsFn(ctx, req) default: return to.ErrorResult(fmt.Errorf("unknown method: %s", method)) } } func addTopicFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called addTopicFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { return to.ErrorResult(err) } repo, err := params.GetString(args, "repo") if err != nil { return to.ErrorResult(err) } topic, err := params.GetString(args, "topic") 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.AddRepoTopic(owner, repo, topic) if err != nil { return to.ErrorResult(fmt.Errorf("add topic %v to %v/%v err: %v", topic, owner, repo, err)) } return mcp.NewToolResultText(fmt.Sprintf("Topic '%s' added to %s/%s", topic, owner, repo)), nil } func deleteTopicFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called deleteTopicFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { return to.ErrorResult(err) } repo, err := params.GetString(args, "repo") if err != nil { return to.ErrorResult(err) } topic, err := params.GetString(args, "topic") 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.DeleteRepoTopic(owner, repo, topic) if err != nil { return to.ErrorResult(fmt.Errorf("delete topic %v from %v/%v err: %v", topic, owner, repo, err)) } return mcp.NewToolResultText(fmt.Sprintf("Topic '%s' removed from %s/%s", topic, owner, repo)), nil } func setTopicsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called setTopicsFn") args := req.GetArguments() owner, err := params.GetString(args, "owner") if err != nil { return to.ErrorResult(err) } repo, err := params.GetString(args, "repo") if err != nil { return to.ErrorResult(err) } topics := params.GetStringSlice(args, "topics") 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 topics for %v/%v err: %v", owner, repo, err)) } return to.TextResult(map[string]any{ "message": fmt.Sprintf("Topics set for %s/%s", owner, repo), "topics": topics, }) }