feat: add missing administration tools
Add 20+ new MCP tools for repository administration: - Repository: get_repo, repo_write (edit/delete/transfer), list_forks - Branch Protection: read (list/get) and write (create/edit/delete) - Webhooks: read (list/get) and write (create/edit/delete/test) - Collaborators: read (list) and write (add/delete) - Deploy Keys: read (list/get) and write (create/delete) - Topics: read (list) and write (add/delete/set) - Compare: get_compare for branch/tag/commit comparison - Organization: org_read (get/list_teams/get_team/list_team_members/list_team_repos) and org_write (create_team/delete_team/add_team_member/remove_team_member/add_team_repo/remove_team_repo) - PR enhancements: list_files and is_merged methods added to pull_request_read Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,379 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user