forgejo-mcp/operation/org/teams.go
Christoph Görn dd9116fbca
fix: 🚨 clear golangci-lint v2 backlog (closes bead forgejo-mcp-hc9)
- errorlint: fmt.Errorf %v→%w across all handlers (errors now wrap the
  underlying SDK/HTTP error for errors.Is/As); errors.Is for
  context.Canceled in cmd
- staticcheck quickfixes: Fprintf over WriteString(Sprintf), tagged
  switches, omit inferred 'any'; QF1001 resolved by hoisting hex checks
  into isHex predicates (normalizeColor, validateSHA)
- govet: reflect.Ptr → reflect.Pointer in pkg/ptr
- errcheck: explicit '_ =' for fs.Parse (ExitOnError) and deferred
  zap Sync
- repair four test assertions the autofix broke (it dropped the
  're.Code != X' half of combined type-assert checks, inverting the
  test); all errors.As sites use 'var re *resource.ResourceError'

golangci-lint: 0 issues. staticcheck, vet, tests: clean.
2026-06-11 00:03:36 +02:00

248 lines
7.9 KiB
Go

package org
import (
"context"
"errors"
"fmt"
"codeberg.org/goern/forgejo-mcp/v2/operation/params"
"codeberg.org/goern/forgejo-mcp/v2/pkg/forgejo"
"codeberg.org/goern/forgejo-mcp/v2/pkg/log"
"codeberg.org/goern/forgejo-mcp/v2/pkg/to"
forgejo_sdk "codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v3"
"github.com/mark3labs/mcp-go/mcp"
)
const (
ListOrgTeamsToolName = "list_org_teams"
CreateOrgTeamToolName = "create_org_team"
AddTeamMemberToolName = "add_team_member"
RemoveTeamMemberToolName = "remove_team_member"
AddTeamRepoToolName = "add_team_repo"
RemoveTeamRepoToolName = "remove_team_repo"
)
var (
ListOrgTeamsTool = mcp.NewTool(
ListOrgTeamsToolName,
mcp.WithDescription("List teams in an organization"),
mcp.WithString("org", mcp.Required(), mcp.Description(params.Org)),
mcp.WithNumber("page", mcp.Required(), mcp.Description(params.Page), mcp.DefaultNumber(1), mcp.Min(1)),
mcp.WithNumber("limit", mcp.Required(), mcp.Description(params.Limit), mcp.DefaultNumber(100), mcp.Min(1)),
)
CreateOrgTeamTool = mcp.NewTool(
CreateOrgTeamToolName,
mcp.WithDescription("Create a team in an organization"),
mcp.WithString("org", mcp.Required(), mcp.Description(params.Org)),
mcp.WithString("name", mcp.Required(), mcp.Description("Team name")),
mcp.WithString("description", mcp.Description(params.Description)),
mcp.WithString("permission", mcp.Description("Access level: read, write, or admin (default: read)")),
mcp.WithBoolean("can_create_org_repo", mcp.Description("Whether members can create repos in the org")),
mcp.WithBoolean("includes_all_repositories", mcp.Description("Whether team has access to all org repos")),
)
AddTeamMemberTool = mcp.NewTool(
AddTeamMemberToolName,
mcp.WithDescription("Add a user to a team"),
mcp.WithNumber("team_id", mcp.Required(), mcp.Description("Team ID")),
mcp.WithString("user", mcp.Required(), mcp.Description(params.User)),
)
RemoveTeamMemberTool = mcp.NewTool(
RemoveTeamMemberToolName,
mcp.WithDescription("Remove a user from a team"),
mcp.WithNumber("team_id", mcp.Required(), mcp.Description("Team ID")),
mcp.WithString("user", mcp.Required(), mcp.Description(params.User)),
)
AddTeamRepoTool = mcp.NewTool(
AddTeamRepoToolName,
mcp.WithDescription("Add a repository to a team"),
mcp.WithNumber("team_id", mcp.Required(), mcp.Description("Team ID")),
mcp.WithString("org", mcp.Required(), mcp.Description(params.Org)),
mcp.WithString("repo", mcp.Required(), mcp.Description(params.Repo)),
)
RemoveTeamRepoTool = mcp.NewTool(
RemoveTeamRepoToolName,
mcp.WithDescription("Remove a repository from a team"),
mcp.WithNumber("team_id", mcp.Required(), mcp.Description("Team ID")),
mcp.WithString("org", mcp.Required(), mcp.Description(params.Org)),
mcp.WithString("repo", mcp.Required(), mcp.Description(params.Repo)),
)
)
func ListOrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListOrgTeamsFn")
orgName, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("organization name is required"))
}
page, ok := req.GetArguments()["page"].(float64)
if !ok {
page = 1
}
limit, ok := req.GetArguments()["limit"].(float64)
if !ok {
limit = 100
}
opt := forgejo_sdk.ListTeamsOptions{
ListOptions: forgejo_sdk.ListOptions{
Page: int(page),
PageSize: int(limit),
},
}
client, err := forgejo.Client(ctx)
if err != nil {
return to.ErrorResult(err)
}
teams, _, err := client.ListOrgTeams(orgName, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("list org teams err: %w", err))
}
return to.TextResult(teams)
}
func CreateOrgTeamFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateOrgTeamFn")
orgName, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("organization name is required"))
}
name, ok := req.GetArguments()["name"].(string)
if !ok {
return to.ErrorResult(errors.New("team name is required"))
}
permission := forgejo_sdk.AccessModeRead
if v, ok := req.GetArguments()["permission"].(string); ok && v != "" {
permission = forgejo_sdk.AccessMode(v)
}
opt := forgejo_sdk.CreateTeamOption{
Name: name,
Permission: permission,
}
if v, ok := req.GetArguments()["description"].(string); ok {
opt.Description = 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 := forgejo.Client(ctx)
if err != nil {
return to.ErrorResult(err)
}
team, _, err := client.CreateTeam(orgName, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("create org team err: %w", err))
}
return to.TextResult(team)
}
func AddTeamMemberFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AddTeamMemberFn")
teamIDFloat, ok := req.GetArguments()["team_id"].(float64)
if !ok {
return to.ErrorResult(errors.New("team_id is required"))
}
teamID := int64(teamIDFloat)
user, ok := req.GetArguments()["user"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
client, err := forgejo.Client(ctx)
if err != nil {
return to.ErrorResult(err)
}
_, err = client.AddTeamMember(teamID, user)
if err != nil {
return to.ErrorResult(fmt.Errorf("add team member err: %w", err))
}
return to.TextResult(fmt.Sprintf("User '%s' added to team %d", user, teamID))
}
func RemoveTeamMemberFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called RemoveTeamMemberFn")
teamIDFloat, ok := req.GetArguments()["team_id"].(float64)
if !ok {
return to.ErrorResult(errors.New("team_id is required"))
}
teamID := int64(teamIDFloat)
user, ok := req.GetArguments()["user"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
client, err := forgejo.Client(ctx)
if err != nil {
return to.ErrorResult(err)
}
_, err = client.RemoveTeamMember(teamID, user)
if err != nil {
return to.ErrorResult(fmt.Errorf("remove team member err: %w", err))
}
return to.TextResult(fmt.Sprintf("User '%s' removed from team %d", user, teamID))
}
func AddTeamRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AddTeamRepoFn")
teamIDFloat, ok := req.GetArguments()["team_id"].(float64)
if !ok {
return to.ErrorResult(errors.New("team_id is required"))
}
teamID := int64(teamIDFloat)
orgName, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("organization name is required"))
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return to.ErrorResult(errors.New("repository name is required"))
}
client, err := forgejo.Client(ctx)
if err != nil {
return to.ErrorResult(err)
}
_, err = client.AddTeamRepository(teamID, orgName, repo)
if err != nil {
return to.ErrorResult(fmt.Errorf("add team repo err: %w", err))
}
return to.TextResult(fmt.Sprintf("Repository '%s/%s' added to team %d", orgName, repo, teamID))
}
func RemoveTeamRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called RemoveTeamRepoFn")
teamIDFloat, ok := req.GetArguments()["team_id"].(float64)
if !ok {
return to.ErrorResult(errors.New("team_id is required"))
}
teamID := int64(teamIDFloat)
orgName, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("organization name is required"))
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return to.ErrorResult(errors.New("repository name is required"))
}
client, err := forgejo.Client(ctx)
if err != nil {
return to.ErrorResult(err)
}
_, err = client.RemoveTeamRepository(teamID, orgName, repo)
if err != nil {
return to.ErrorResult(fmt.Errorf("remove team repo err: %w", err))
}
return to.TextResult(fmt.Sprintf("Repository '%s/%s' removed from team %d", orgName, repo, teamID))
}