package organization 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 ( ListOrgTeamsToolName = "list_org_teams" GetTeamToolName = "get_team" CreateTeamToolName = "create_team" EditTeamToolName = "edit_team" DeleteTeamToolName = "delete_team" ListTeamMembersToolName = "list_team_members" GetTeamMemberToolName = "get_team_member" AddTeamMemberToolName = "add_team_member" RemoveTeamMemberToolName = "remove_team_member" ListTeamReposToolName = "list_team_repos" AddTeamRepoToolName = "add_team_repo" RemoveTeamRepoToolName = "remove_team_repo" ) var ( ListOrgTeamsTool = mcp.NewTool( ListOrgTeamsToolName, mcp.WithDescription("List an organization's teams"), mcp.WithString("org", mcp.Required(), mcp.Description("organization name")), mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)), ) GetTeamTool = mcp.NewTool( GetTeamToolName, mcp.WithDescription("Get a team by ID"), mcp.WithNumber("id", mcp.Required(), mcp.Description("team ID")), ) CreateTeamTool = mcp.NewTool( CreateTeamToolName, mcp.WithDescription("Create a team in an organization"), mcp.WithString("org", mcp.Required(), mcp.Description("organization name")), mcp.WithString("name", mcp.Required(), mcp.Description("team name")), mcp.WithString("description", mcp.Description("team description")), mcp.WithString("permission", mcp.Description("team permission: read, write, admin"), mcp.DefaultString("read")), mcp.WithBoolean("can_create_org_repo", mcp.Description("whether team members can create repos in the org")), mcp.WithBoolean("includes_all_repositories", mcp.Description("whether team has access to all repos")), ) EditTeamTool = mcp.NewTool( EditTeamToolName, mcp.WithDescription("Edit a team"), mcp.WithNumber("id", mcp.Required(), mcp.Description("team ID")), mcp.WithString("name", mcp.Description("team name")), mcp.WithString("description", mcp.Description("team description")), mcp.WithString("permission", mcp.Description("team permission: read, write, admin")), mcp.WithBoolean("can_create_org_repo", mcp.Description("whether team members can create repos in the org")), mcp.WithBoolean("includes_all_repositories", mcp.Description("whether team has access to all repos")), ) DeleteTeamTool = mcp.NewTool( DeleteTeamToolName, mcp.WithDescription("Delete a team"), mcp.WithNumber("id", mcp.Required(), mcp.Description("team ID")), ) ListTeamMembersTool = mcp.NewTool( ListTeamMembersToolName, mcp.WithDescription("List a team's members"), mcp.WithNumber("id", mcp.Required(), mcp.Description("team ID")), mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)), ) GetTeamMemberTool = mcp.NewTool( GetTeamMemberToolName, mcp.WithDescription("Get a specific member of a team"), mcp.WithNumber("id", mcp.Required(), mcp.Description("team ID")), mcp.WithString("username", mcp.Required(), mcp.Description("username")), ) AddTeamMemberTool = mcp.NewTool( AddTeamMemberToolName, mcp.WithDescription("Add a member to a team"), mcp.WithNumber("id", mcp.Required(), mcp.Description("team ID")), mcp.WithString("username", mcp.Required(), mcp.Description("username to add")), ) RemoveTeamMemberTool = mcp.NewTool( RemoveTeamMemberToolName, mcp.WithDescription("Remove a member from a team"), mcp.WithNumber("id", mcp.Required(), mcp.Description("team ID")), mcp.WithString("username", mcp.Required(), mcp.Description("username to remove")), ) ListTeamReposTool = mcp.NewTool( ListTeamReposToolName, mcp.WithDescription("List a team's repositories"), mcp.WithNumber("id", mcp.Required(), mcp.Description("team ID")), mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)), ) AddTeamRepoTool = mcp.NewTool( AddTeamRepoToolName, mcp.WithDescription("Add a repository to a team"), mcp.WithNumber("id", mcp.Required(), mcp.Description("team ID")), mcp.WithString("org", mcp.Required(), mcp.Description("organization name")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), ) RemoveTeamRepoTool = mcp.NewTool( RemoveTeamRepoToolName, mcp.WithDescription("Remove a repository from a team"), mcp.WithNumber("id", mcp.Required(), mcp.Description("team ID")), mcp.WithString("org", mcp.Required(), mcp.Description("organization name")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), ) ) func init() { Tool.RegisterRead(server.ServerTool{Tool: ListOrgTeamsTool, Handler: ListOrgTeamsFn}) Tool.RegisterRead(server.ServerTool{Tool: GetTeamTool, Handler: GetTeamFn}) Tool.RegisterRead(server.ServerTool{Tool: ListTeamMembersTool, Handler: ListTeamMembersFn}) Tool.RegisterRead(server.ServerTool{Tool: GetTeamMemberTool, Handler: GetTeamMemberFn}) Tool.RegisterRead(server.ServerTool{Tool: ListTeamReposTool, Handler: ListTeamReposFn}) Tool.RegisterWrite(server.ServerTool{Tool: CreateTeamTool, Handler: CreateTeamFn}) Tool.RegisterWrite(server.ServerTool{Tool: EditTeamTool, Handler: EditTeamFn}) Tool.RegisterWrite(server.ServerTool{Tool: DeleteTeamTool, Handler: DeleteTeamFn}) Tool.RegisterWrite(server.ServerTool{Tool: AddTeamMemberTool, Handler: AddTeamMemberFn}) Tool.RegisterWrite(server.ServerTool{Tool: RemoveTeamMemberTool, Handler: RemoveTeamMemberFn}) Tool.RegisterWrite(server.ServerTool{Tool: AddTeamRepoTool, Handler: AddTeamRepoFn}) Tool.RegisterWrite(server.ServerTool{Tool: RemoveTeamRepoTool, Handler: RemoveTeamRepoFn}) } func ListOrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called ListOrgTeamsFn") org, ok := req.GetArguments()["org"].(string) if !ok { return to.ErrorResult(errors.New("org 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)) } teams, _, err := client.ListOrgTeams(org, gitea_sdk.ListTeamsOptions{ ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)}, }) if err != nil { return to.ErrorResult(fmt.Errorf("list org %s teams err: %v", org, err)) } return to.TextResult(teams) } func GetTeamFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called GetTeamFn") id, err := params.GetIndex(req.GetArguments(), "id") 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)) } team, _, err := client.GetTeam(id) if err != nil { return to.ErrorResult(fmt.Errorf("get team %d err: %v", id, err)) } return to.TextResult(team) } func CreateTeamFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called CreateTeamFn") org, ok := req.GetArguments()["org"].(string) if !ok { return to.ErrorResult(errors.New("org is required")) } name, ok := req.GetArguments()["name"].(string) if !ok { return to.ErrorResult(errors.New("name is required")) } opt := gitea_sdk.CreateTeamOption{ Name: name, } if v, ok := req.GetArguments()["description"].(string); ok { opt.Description = v } if v, ok := req.GetArguments()["permission"].(string); ok { opt.Permission = gitea_sdk.AccessMode(v) } if v, ok := req.GetArguments()["can_create_org_repo"].(bool); ok { opt.CanCreateOrgRepo = v } if v, ok := req.GetArguments()["includes_all_repositories"].(bool); ok { opt.IncludesAllRepositories = v } client, err := gitea.ClientFromContext(ctx) if err != nil { return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) } team, _, err := client.CreateTeam(org, opt) if err != nil { return to.ErrorResult(fmt.Errorf("create team %s in org %s err: %v", name, org, err)) } return to.TextResult(team) } func EditTeamFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called EditTeamFn") id, err := params.GetIndex(req.GetArguments(), "id") if err != nil { return to.ErrorResult(err) } opt := gitea_sdk.EditTeamOption{} if v, ok := req.GetArguments()["name"].(string); ok { opt.Name = v } if v, ok := req.GetArguments()["description"].(string); ok { opt.Description = &v } if v, ok := req.GetArguments()["permission"].(string); ok { perm := gitea_sdk.AccessMode(v) opt.Permission = perm } if v, ok := req.GetArguments()["can_create_org_repo"].(bool); ok { opt.CanCreateOrgRepo = &v } if v, ok := req.GetArguments()["includes_all_repositories"].(bool); ok { opt.IncludesAllRepositories = &v } client, err := gitea.ClientFromContext(ctx) if err != nil { return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) } _, err = client.EditTeam(id, opt) if err != nil { return to.ErrorResult(fmt.Errorf("edit team %d err: %v", id, err)) } team, _, err := client.GetTeam(id) if err != nil { return to.ErrorResult(fmt.Errorf("get team %d after edit err: %v", id, err)) } return to.TextResult(team) } func DeleteTeamFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called DeleteTeamFn") id, err := params.GetIndex(req.GetArguments(), "id") 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.DeleteTeam(id) if err != nil { return to.ErrorResult(fmt.Errorf("delete team %d err: %v", id, err)) } return to.TextResult(map[string]any{"status": "deleted", "team_id": id}) } func ListTeamMembersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called ListTeamMembersFn") id, err := params.GetIndex(req.GetArguments(), "id") if err != nil { return to.ErrorResult(err) } 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)) } members, _, err := client.ListTeamMembers(id, gitea_sdk.ListTeamMembersOptions{ ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)}, }) if err != nil { return to.ErrorResult(fmt.Errorf("list team %d members err: %v", id, err)) } return to.TextResult(members) } func GetTeamMemberFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called GetTeamMemberFn") id, err := params.GetIndex(req.GetArguments(), "id") if err != nil { return to.ErrorResult(err) } 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)) } member, _, err := client.GetTeamMember(id, username) if err != nil { return to.ErrorResult(fmt.Errorf("get team %d member %s err: %v", id, username, err)) } return to.TextResult(member) } func AddTeamMemberFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called AddTeamMemberFn") id, err := params.GetIndex(req.GetArguments(), "id") if err != nil { return to.ErrorResult(err) } 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)) } _, err = client.AddTeamMember(id, username) if err != nil { return to.ErrorResult(fmt.Errorf("add member %s to team %d err: %v", username, id, err)) } return to.TextResult(map[string]any{"status": "added", "team_id": id, "username": username}) } func RemoveTeamMemberFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called RemoveTeamMemberFn") id, err := params.GetIndex(req.GetArguments(), "id") if err != nil { return to.ErrorResult(err) } 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)) } _, err = client.RemoveTeamMember(id, username) if err != nil { return to.ErrorResult(fmt.Errorf("remove member %s from team %d err: %v", username, id, err)) } return to.TextResult(map[string]any{"status": "removed", "team_id": id, "username": username}) } func ListTeamReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called ListTeamReposFn") id, err := params.GetIndex(req.GetArguments(), "id") if err != nil { return to.ErrorResult(err) } 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.ListTeamRepositories(id, gitea_sdk.ListTeamRepositoriesOptions{ ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)}, }) if err != nil { return to.ErrorResult(fmt.Errorf("list team %d repos err: %v", id, err)) } return to.TextResult(repos) } func AddTeamRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called AddTeamRepoFn") id, err := params.GetIndex(req.GetArguments(), "id") if err != nil { return to.ErrorResult(err) } org, ok := req.GetArguments()["org"].(string) if !ok { return to.ErrorResult(errors.New("org 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.AddTeamRepository(id, org, repo) if err != nil { return to.ErrorResult(fmt.Errorf("add repo %s/%s to team %d err: %v", org, repo, id, err)) } return to.TextResult(map[string]any{"status": "added", "team_id": id, "org": org, "repo": repo}) } func RemoveTeamRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called RemoveTeamRepoFn") id, err := params.GetIndex(req.GetArguments(), "id") if err != nil { return to.ErrorResult(err) } org, ok := req.GetArguments()["org"].(string) if !ok { return to.ErrorResult(errors.New("org 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.RemoveTeamRepository(id, org, repo) if err != nil { return to.ErrorResult(fmt.Errorf("remove repo %s/%s from team %d err: %v", org, repo, id, err)) } return to.TextResult(map[string]any{"status": "removed", "team_id": id, "org": org, "repo": repo}) }