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:
2026-04-04 08:41:36 +00:00
parent 08128b9471
commit 4b4b5ee247
13 changed files with 2120 additions and 2 deletions
+220
View File
@@ -23,6 +23,9 @@ const (
ForkRepoToolName = "fork_repo"
ListMyReposToolName = "list_my_repos"
ListOrgReposToolName = "list_org_repos"
GetRepoToolName = "get_repo"
RepoWriteToolName = "repo_write"
ListForksToolName = "list_forks"
)
var (
@@ -67,6 +70,40 @@ var (
mcp.WithNumber("page", mcp.Required(), mcp.Description("Page number"), mcp.DefaultNumber(1), mcp.Min(1)),
mcp.WithNumber("pageSize", mcp.Required(), mcp.Description("Page size number"), mcp.DefaultNumber(100), mcp.Min(1)),
)
GetRepoTool = mcp.NewTool(
GetRepoToolName,
mcp.WithDescription("Get repository details including settings, permissions, and metadata"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
)
RepoWriteTool = mcp.NewTool(
RepoWriteToolName,
mcp.WithDescription("Edit, delete, or transfer a repository. Use method 'edit' to change settings, 'delete' to remove, 'transfer' to change ownership."),
mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("edit", "delete", "transfer")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("description", mcp.Description("repository description (for 'edit')")),
mcp.WithString("website", mcp.Description("repository website URL (for 'edit')")),
mcp.WithBoolean("private", mcp.Description("whether the repository is private (for 'edit')")),
mcp.WithBoolean("has_issues", mcp.Description("enable issues (for 'edit')")),
mcp.WithBoolean("has_wiki", mcp.Description("enable wiki (for 'edit')")),
mcp.WithBoolean("has_pull_requests", mcp.Description("enable pull requests (for 'edit')")),
mcp.WithString("default_branch", mcp.Description("default branch name (for 'edit')")),
mcp.WithBoolean("archived", mcp.Description("archive/unarchive the repository (for 'edit')")),
mcp.WithString("new_owner", mcp.Description("new owner username or org (required for 'transfer')")),
mcp.WithArray("team_ids", mcp.Description("team IDs to grant access to (for 'transfer')"), mcp.Items(map[string]any{"type": "number"})),
)
ListForksTool = mcp.NewTool(
ListForksToolName,
mcp.WithDescription("List forks of a repository"),
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.Min(1)),
mcp.WithNumber("perPage", mcp.Description("results per page"), mcp.DefaultNumber(30), mcp.Min(1)),
)
)
func init() {
@@ -86,6 +123,18 @@ func init() {
Tool: ListOrgReposTool,
Handler: ListOrgReposFn,
})
Tool.RegisterRead(server.ServerTool{
Tool: GetRepoTool,
Handler: GetRepoFn,
})
Tool.RegisterWrite(server.ServerTool{
Tool: RepoWriteTool,
Handler: repoWriteFn,
})
Tool.RegisterRead(server.ServerTool{
Tool: ListForksTool,
Handler: ListForksFn,
})
}
func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
@@ -229,3 +278,174 @@ func ListOrgReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
}
return to.TextResult(repos)
}
func GetRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetRepoFn")
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)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
r, _, err := client.GetRepo(owner, repo)
if err != nil {
return to.ErrorResult(fmt.Errorf("get repo %v/%v err: %v", owner, repo, err))
}
return to.TextResult(slimRepoDetail(r))
}
func repoWriteFn(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 "edit":
return editRepoFn(ctx, req)
case "delete":
return deleteRepoFn(ctx, req)
case "transfer":
return transferRepoFn(ctx, req)
default:
return to.ErrorResult(fmt.Errorf("unknown method: %s", method))
}
}
func editRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called editRepoFn")
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)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
opt := gitea_sdk.EditRepoOption{}
if v, ok := args["description"].(string); ok {
opt.Description = &v
}
if v, ok := args["website"].(string); ok {
opt.Website = &v
}
if v, ok := args["private"].(bool); ok {
opt.Private = &v
}
if v, ok := args["has_issues"].(bool); ok {
opt.HasIssues = &v
}
if v, ok := args["has_wiki"].(bool); ok {
opt.HasWiki = &v
}
if v, ok := args["has_pull_requests"].(bool); ok {
opt.HasPullRequests = &v
}
if v, ok := args["default_branch"].(string); ok {
opt.DefaultBranch = &v
}
if v, ok := args["archived"].(bool); ok {
opt.Archived = &v
}
r, _, err := client.EditRepo(owner, repo, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("edit repo %v/%v err: %v", owner, repo, err))
}
return to.TextResult(slimRepo(r))
}
func deleteRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called deleteRepoFn")
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)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.DeleteRepo(owner, repo)
if err != nil {
return to.ErrorResult(fmt.Errorf("delete repo %v/%v err: %v", owner, repo, err))
}
return mcp.NewToolResultText(fmt.Sprintf("Repository %s/%s deleted successfully", owner, repo)), nil
}
func transferRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called transferRepoFn")
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)
}
newOwner, err := params.GetString(args, "new_owner")
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.TransferRepoOption{
NewOwner: newOwner,
}
if teamIDs, err := params.GetInt64Slice(args, "team_ids"); err == nil {
opt.TeamIDs = &teamIDs
}
r, _, err := client.TransferRepo(owner, repo, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("transfer repo %v/%v err: %v", owner, repo, err))
}
return to.TextResult(slimRepo(r))
}
func ListForksFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListForksFn")
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))
}
forks, _, err := client.ListForks(owner, repo, gitea_sdk.ListForksOptions{
ListOptions: gitea_sdk.ListOptions{
Page: page,
PageSize: pageSize,
},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list forks %v/%v err: %v", owner, repo, err))
}
return to.TextResult(slimRepos(forks))
}