package org 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.com/gitea/gitea-mcp/pkg/tool" gitea_sdk "code.gitea.io/sdk/gitea" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) var Tool = tool.New() const ( OrgReadToolName = "org_read" OrgWriteToolName = "org_write" ) var ( OrgReadTool = mcp.NewTool( OrgReadToolName, mcp.WithDescription("Read organization and team information. Methods: 'get' org details, 'list_teams', 'get_team', 'list_team_members', 'list_team_repos'."), mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("get", "list_teams", "get_team", "list_team_members", "list_team_repos")), mcp.WithString("org", mcp.Description("organization name (required for 'get', 'list_teams')")), mcp.WithNumber("team_id", mcp.Description("team ID (required for 'get_team', 'list_team_members', 'list_team_repos')")), mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), mcp.WithNumber("perPage", mcp.Description("results per page"), mcp.DefaultNumber(30)), ) OrgWriteTool = mcp.NewTool( OrgWriteToolName, mcp.WithDescription("Manage organization teams and membership. Methods: 'create_team', 'delete_team', 'add_team_member', 'remove_team_member', 'add_team_repo', 'remove_team_repo'."), mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("create_team", "delete_team", "add_team_member", "remove_team_member", "add_team_repo", "remove_team_repo")), mcp.WithString("org", mcp.Description("organization name (required for 'create_team', 'add_team_repo', 'remove_team_repo')")), mcp.WithNumber("team_id", mcp.Description("team ID (required for 'delete_team', 'add_team_member', 'remove_team_member', 'add_team_repo', 'remove_team_repo')")), mcp.WithString("username", mcp.Description("username (required for 'add_team_member', 'remove_team_member')")), mcp.WithString("repo", mcp.Description("repository name (required for 'add_team_repo', 'remove_team_repo')")), mcp.WithString("name", mcp.Description("team name (required for 'create_team')")), mcp.WithString("description", mcp.Description("team description (for 'create_team')")), mcp.WithString("permission", mcp.Description("team permission level (for 'create_team')"), mcp.Enum("read", "write", "admin")), mcp.WithArray("units", mcp.Description("team units (for 'create_team'), e.g. repo.code, repo.issues, repo.pulls"), mcp.Items(map[string]any{"type": "string"})), mcp.WithBoolean("includes_all_repositories", mcp.Description("include all org repos (for 'create_team')")), ) ) func init() { Tool.RegisterRead(server.ServerTool{ Tool: OrgReadTool, Handler: orgReadFn, }) Tool.RegisterWrite(server.ServerTool{ Tool: OrgWriteTool, Handler: orgWriteFn, }) } func orgReadFn(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 "get": return getOrgFn(ctx, req) case "list_teams": return listTeamsFn(ctx, req) case "get_team": return getTeamFn(ctx, req) case "list_team_members": return listTeamMembersFn(ctx, req) case "list_team_repos": return listTeamReposFn(ctx, req) default: return to.ErrorResult(fmt.Errorf("unknown method: %s", method)) } } func getOrgFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called getOrgFn") args := req.GetArguments() orgName, err := params.GetString(args, "org") 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)) } o, _, err := client.GetOrg(orgName) if err != nil { return to.ErrorResult(fmt.Errorf("get org %v err: %v", orgName, err)) } return to.TextResult(slimOrg(o)) } func listTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called listTeamsFn") args := req.GetArguments() orgName, err := params.GetString(args, "org") 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)) } teams, _, err := client.ListOrgTeams(orgName, gitea_sdk.ListTeamsOptions{ ListOptions: gitea_sdk.ListOptions{ Page: page, PageSize: pageSize, }, }) if err != nil { return to.ErrorResult(fmt.Errorf("list teams for org %v err: %v", orgName, err)) } return to.TextResult(slimTeams(teams)) } func getTeamFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called getTeamFn") args := req.GetArguments() teamID, err := params.GetIndex(args, "team_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(teamID) if err != nil { return to.ErrorResult(fmt.Errorf("get team %v err: %v", teamID, err)) } return to.TextResult(slimTeam(team)) } func listTeamMembersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called listTeamMembersFn") args := req.GetArguments() teamID, err := params.GetIndex(args, "team_id") 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)) } members, _, err := client.ListTeamMembers(teamID, gitea_sdk.ListTeamMembersOptions{ ListOptions: gitea_sdk.ListOptions{ Page: page, PageSize: pageSize, }, }) if err != nil { return to.ErrorResult(fmt.Errorf("list team %v members err: %v", teamID, err)) } return to.TextResult(slimUsers(members)) } func listTeamReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called listTeamReposFn") args := req.GetArguments() teamID, err := params.GetIndex(args, "team_id") 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)) } repos, _, err := client.ListTeamRepositories(teamID, gitea_sdk.ListTeamRepositoriesOptions{ ListOptions: gitea_sdk.ListOptions{ Page: page, PageSize: pageSize, }, }) if err != nil { return to.ErrorResult(fmt.Errorf("list team %v repos err: %v", teamID, err)) } return to.TextResult(slimRepos(repos)) } func orgWriteFn(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 "create_team": return createTeamFn(ctx, req) case "delete_team": return deleteTeamFn(ctx, req) case "add_team_member": return addTeamMemberFn(ctx, req) case "remove_team_member": return removeTeamMemberFn(ctx, req) case "add_team_repo": return addTeamRepoFn(ctx, req) case "remove_team_repo": return removeTeamRepoFn(ctx, req) default: return to.ErrorResult(fmt.Errorf("unknown method: %s", method)) } } func createTeamFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called createTeamFn") args := req.GetArguments() orgName, err := params.GetString(args, "org") if err != nil { return to.ErrorResult(err) } name, err := params.GetString(args, "name") 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.CreateTeamOption{ Name: name, } if v, ok := args["description"].(string); ok { opt.Description = v } if v, ok := args["permission"].(string); ok { switch v { case "read": opt.Permission = gitea_sdk.AccessModeRead case "write": opt.Permission = gitea_sdk.AccessModeWrite case "admin": opt.Permission = gitea_sdk.AccessModeAdmin } } if unitStrings := params.GetStringSlice(args, "units"); unitStrings != nil { units := make([]gitea_sdk.RepoUnitType, 0, len(unitStrings)) for _, u := range unitStrings { units = append(units, gitea_sdk.RepoUnitType(u)) } opt.Units = units } if v, ok := args["includes_all_repositories"].(bool); ok { opt.IncludesAllRepositories = v } team, _, err := client.CreateTeam(orgName, opt) if err != nil { return to.ErrorResult(fmt.Errorf("create team in org %v err: %v", orgName, err)) } return to.TextResult(slimTeam(team)) } func deleteTeamFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called deleteTeamFn") args := req.GetArguments() teamID, err := params.GetIndex(args, "team_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(teamID) if err != nil { return to.ErrorResult(fmt.Errorf("delete team %v err: %v", teamID, err)) } return mcp.NewToolResultText(fmt.Sprintf("Team %d deleted", teamID)), nil } func addTeamMemberFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called addTeamMemberFn") args := req.GetArguments() teamID, err := params.GetIndex(args, "team_id") 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.AddTeamMember(teamID, username) if err != nil { return to.ErrorResult(fmt.Errorf("add %v to team %v err: %v", username, teamID, err)) } return mcp.NewToolResultText(fmt.Sprintf("User %s added to team %d", username, teamID)), nil } func removeTeamMemberFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called removeTeamMemberFn") args := req.GetArguments() teamID, err := params.GetIndex(args, "team_id") 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.RemoveTeamMember(teamID, username) if err != nil { return to.ErrorResult(fmt.Errorf("remove %v from team %v err: %v", username, teamID, err)) } return mcp.NewToolResultText(fmt.Sprintf("User %s removed from team %d", username, teamID)), nil } func addTeamRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called addTeamRepoFn") args := req.GetArguments() teamID, err := params.GetIndex(args, "team_id") if err != nil { return to.ErrorResult(err) } orgName, err := params.GetString(args, "org") if err != nil { return to.ErrorResult(err) } repoName, 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)) } _, err = client.AddTeamRepository(teamID, orgName, repoName) if err != nil { return to.ErrorResult(fmt.Errorf("add repo %v/%v to team %v err: %v", orgName, repoName, teamID, err)) } return mcp.NewToolResultText(fmt.Sprintf("Repo %s/%s added to team %d", orgName, repoName, teamID)), nil } func removeTeamRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called removeTeamRepoFn") args := req.GetArguments() teamID, err := params.GetIndex(args, "team_id") if err != nil { return to.ErrorResult(err) } orgName, err := params.GetString(args, "org") if err != nil { return to.ErrorResult(err) } repoName, 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)) } _, err = client.RemoveTeamRepository(teamID, orgName, repoName) if err != nil { return to.ErrorResult(fmt.Errorf("remove repo %v/%v from team %v err: %v", orgName, repoName, teamID, err)) } return mcp.NewToolResultText(fmt.Sprintf("Repo %s/%s removed from team %d", orgName, repoName, teamID)), nil }