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 ( CollaboratorReadToolName = "collaborator_read" CollaboratorWriteToolName = "collaborator_write" ) var ( CollaboratorReadTool = mcp.NewTool( CollaboratorReadToolName, mcp.WithDescription("List repository collaborators."), 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")), mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), mcp.WithNumber("perPage", mcp.Description("results per page"), mcp.DefaultNumber(30)), ) CollaboratorWriteTool = mcp.NewTool( CollaboratorWriteToolName, mcp.WithDescription("Add or remove repository collaborators. Use method 'add' to grant access, 'delete' to revoke."), mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("add", "delete")), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("username", mcp.Required(), mcp.Description("collaborator username")), mcp.WithString("permission", mcp.Description("permission level (for 'add')"), mcp.Enum("read", "write", "admin")), ) ) func init() { Tool.RegisterRead(server.ServerTool{ Tool: CollaboratorReadTool, Handler: collaboratorReadFn, }) Tool.RegisterWrite(server.ServerTool{ Tool: CollaboratorWriteTool, Handler: collaboratorWriteFn, }) } func collaboratorReadFn(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 "list": return listCollaboratorsFn(ctx, req) default: return to.ErrorResult(fmt.Errorf("unknown method: %s", method)) } } func listCollaboratorsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called listCollaboratorsFn") 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) } page, pageSize := params.GetPagination(args, 30) client, err := gitea.ClientFromContext(ctx) if err != nil { return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) } users, _, err := client.ListCollaborators(owner, repo, gitea_sdk.ListCollaboratorsOptions{ ListOptions: gitea_sdk.ListOptions{ Page: page, PageSize: pageSize, }, }) if err != nil { return to.ErrorResult(fmt.Errorf("list collaborators %v/%v err: %v", owner, repo, err)) } return to.TextResult(slimUsers(users)) } func collaboratorWriteFn(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 addCollaboratorFn(ctx, req) case "delete": return deleteCollaboratorFn(ctx, req) default: return to.ErrorResult(fmt.Errorf("unknown method: %s", method)) } } func addCollaboratorFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called addCollaboratorFn") 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) } username, err := params.GetString(args, "username") 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)) } opt := gitea_sdk.AddCollaboratorOption{} if perm, ok := args["permission"].(string); ok { am := gitea_sdk.AccessMode(perm) opt.Permission = &am } _, err = client.AddCollaborator(owner, repo, username, opt) if err != nil { return to.ErrorResult(fmt.Errorf("add collaborator %v to %v/%v err: %v", username, owner, repo, err)) } return mcp.NewToolResultText(fmt.Sprintf("Collaborator %s added to %s/%s", username, owner, repo)), nil } func deleteCollaboratorFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called deleteCollaboratorFn") 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) } username, err := params.GetString(args, "username") 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.DeleteCollaborator(owner, repo, username) if err != nil { return to.ErrorResult(fmt.Errorf("delete collaborator %v from %v/%v err: %v", username, owner, repo, err)) } return mcp.NewToolResultText(fmt.Sprintf("Collaborator %s removed from %s/%s", username, owner, repo)), nil }