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:
@@ -3,3 +3,4 @@ gitea-mcp
|
||||
gitea-mcp.exe
|
||||
*.log
|
||||
tmp
|
||||
gitea-mcp-new
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"gitea.com/gitea/gitea-mcp/operation/label"
|
||||
"gitea.com/gitea/gitea-mcp/operation/milestone"
|
||||
"gitea.com/gitea/gitea-mcp/operation/notification"
|
||||
"gitea.com/gitea/gitea-mcp/operation/org"
|
||||
"gitea.com/gitea/gitea-mcp/operation/pull"
|
||||
"gitea.com/gitea/gitea-mcp/operation/repo"
|
||||
"gitea.com/gitea/gitea-mcp/operation/search"
|
||||
@@ -69,6 +70,9 @@ func RegisterTool(s *server.MCPServer) {
|
||||
// Time Tracking Tool
|
||||
s.AddTools(timetracking.Tool.Tools()...)
|
||||
|
||||
// Organization Tool
|
||||
s.AddTools(org.Tool.Tools()...)
|
||||
|
||||
s.DeleteTools("")
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package org
|
||||
|
||||
import (
|
||||
gitea_sdk "code.gitea.io/sdk/gitea"
|
||||
)
|
||||
|
||||
func slimOrg(o *gitea_sdk.Organization) map[string]any {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
return map[string]any{
|
||||
"id": o.ID,
|
||||
"username": o.UserName,
|
||||
"full_name": o.FullName,
|
||||
"description": o.Description,
|
||||
"website": o.Website,
|
||||
"location": o.Location,
|
||||
"visibility": o.Visibility,
|
||||
"avatar_url": o.AvatarURL,
|
||||
}
|
||||
}
|
||||
|
||||
func slimTeam(t *gitea_sdk.Team) map[string]any {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
return map[string]any{
|
||||
"id": t.ID,
|
||||
"name": t.Name,
|
||||
"description": t.Description,
|
||||
"permission": t.Permission,
|
||||
"units": t.Units,
|
||||
"includes_all_repositories": t.IncludesAllRepositories,
|
||||
"can_create_org_repo": t.CanCreateOrgRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func slimTeams(teams []*gitea_sdk.Team) []map[string]any {
|
||||
out := make([]map[string]any, 0, len(teams))
|
||||
for _, t := range teams {
|
||||
out = append(out, slimTeam(t))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func slimUser(u *gitea_sdk.User) map[string]any {
|
||||
if u == nil {
|
||||
return nil
|
||||
}
|
||||
return map[string]any{
|
||||
"id": u.ID,
|
||||
"login": u.UserName,
|
||||
"full_name": u.FullName,
|
||||
"email": u.Email,
|
||||
"avatar_url": u.AvatarURL,
|
||||
}
|
||||
}
|
||||
|
||||
func slimUsers(users []*gitea_sdk.User) []map[string]any {
|
||||
out := make([]map[string]any, 0, len(users))
|
||||
for _, u := range users {
|
||||
out = append(out, slimUser(u))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func slimRepos(repos []*gitea_sdk.Repository) []map[string]any {
|
||||
out := make([]map[string]any, 0, len(repos))
|
||||
for _, r := range repos {
|
||||
if r == nil {
|
||||
continue
|
||||
}
|
||||
m := map[string]any{
|
||||
"id": r.ID,
|
||||
"full_name": r.FullName,
|
||||
"html_url": r.HTMLURL,
|
||||
"private": r.Private,
|
||||
}
|
||||
if r.Owner != nil {
|
||||
m["owner"] = r.Owner.UserName
|
||||
}
|
||||
out = append(out, m)
|
||||
}
|
||||
return out
|
||||
}
|
||||
+76
-2
@@ -40,8 +40,8 @@ var (
|
||||
|
||||
PullRequestReadTool = mcp.NewTool(
|
||||
PullRequestReadToolName,
|
||||
mcp.WithDescription("Get pull request information. Use method 'get' for PR details, 'get_diff' for diff, 'get_reviews'/'get_review'/'get_review_comments' for review data."),
|
||||
mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("get", "get_diff", "get_reviews", "get_review", "get_review_comments")),
|
||||
mcp.WithDescription("Get pull request information. Use method 'get' for PR details, 'get_diff' for diff, 'get_reviews'/'get_review'/'get_review_comments' for review data, 'list_files' for changed files, 'is_merged' to check merge status."),
|
||||
mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("get", "get_diff", "get_reviews", "get_review", "get_review_comments", "list_files", "is_merged")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||
mcp.WithNumber("index", mcp.Required(), mcp.Description("pull request index")),
|
||||
@@ -140,11 +140,85 @@ func pullRequestReadFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
|
||||
return getPullRequestReviewFn(ctx, req)
|
||||
case "get_review_comments":
|
||||
return listPullRequestReviewCommentsFn(ctx, req)
|
||||
case "list_files":
|
||||
return listPullRequestFilesFn(ctx, req)
|
||||
case "is_merged":
|
||||
return isPullRequestMergedFn(ctx, req)
|
||||
default:
|
||||
return to.ErrorResult(fmt.Errorf("unknown method: %s", method))
|
||||
}
|
||||
}
|
||||
|
||||
func listPullRequestFilesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called listPullRequestFilesFn")
|
||||
args := req.GetArguments()
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repoName, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
index, err := params.GetIndex(args, "index")
|
||||
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))
|
||||
}
|
||||
files, _, err := client.ListPullRequestFiles(owner, repoName, index, gitea_sdk.ListPullRequestFilesOptions{
|
||||
ListOptions: gitea_sdk.ListOptions{
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("list PR files %v/%v#%v err: %v", owner, repoName, index, err))
|
||||
}
|
||||
|
||||
out := make([]map[string]any, 0, len(files))
|
||||
for _, f := range files {
|
||||
out = append(out, map[string]any{
|
||||
"filename": f.Filename,
|
||||
"status": f.Status,
|
||||
"additions": f.Additions,
|
||||
"deletions": f.Deletions,
|
||||
"changes": f.Changes,
|
||||
})
|
||||
}
|
||||
return to.TextResult(out)
|
||||
}
|
||||
|
||||
func isPullRequestMergedFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called isPullRequestMergedFn")
|
||||
args := req.GetArguments()
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repoName, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
index, err := params.GetIndex(args, "index")
|
||||
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))
|
||||
}
|
||||
merged, _, err := client.IsPullRequestMerged(owner, repoName, index)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("check PR merged %v/%v#%v err: %v", owner, repoName, index, err))
|
||||
}
|
||||
return to.TextResult(map[string]any{"merged": merged})
|
||||
}
|
||||
|
||||
func pullRequestWriteFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
method, err := params.GetString(req.GetArguments(), "method")
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
package repo
|
||||
|
||||
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_sdk "code.gitea.io/sdk/gitea"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
)
|
||||
|
||||
const (
|
||||
BranchProtectionReadToolName = "branch_protection_read"
|
||||
BranchProtectionWriteToolName = "branch_protection_write"
|
||||
)
|
||||
|
||||
var (
|
||||
BranchProtectionReadTool = mcp.NewTool(
|
||||
BranchProtectionReadToolName,
|
||||
mcp.WithDescription("Read branch protection rules. Use method 'list' to list all rules, 'get' to get a specific rule."),
|
||||
mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("list", "get")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||
mcp.WithString("name", mcp.Description("branch protection rule name (required for 'get')")),
|
||||
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
|
||||
mcp.WithNumber("perPage", mcp.Description("results per page"), mcp.DefaultNumber(30)),
|
||||
)
|
||||
|
||||
BranchProtectionWriteTool = mcp.NewTool(
|
||||
BranchProtectionWriteToolName,
|
||||
mcp.WithDescription("Create, edit, or delete branch protection rules."),
|
||||
mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("create", "edit", "delete")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||
mcp.WithString("branch_name", mcp.Description("branch name or pattern (required for 'create')")),
|
||||
mcp.WithString("rule_name", mcp.Description("rule name (required for 'edit', 'delete')")),
|
||||
mcp.WithBoolean("enable_push", mcp.Description("enable push to protected branch")),
|
||||
mcp.WithArray("push_whitelist_usernames", mcp.Description("usernames allowed to push"), mcp.Items(map[string]any{"type": "string"})),
|
||||
mcp.WithArray("push_whitelist_teams", mcp.Description("team names allowed to push"), mcp.Items(map[string]any{"type": "string"})),
|
||||
mcp.WithBoolean("enable_merge_whitelist", mcp.Description("enable merge whitelist")),
|
||||
mcp.WithArray("merge_whitelist_usernames", mcp.Description("usernames allowed to merge"), mcp.Items(map[string]any{"type": "string"})),
|
||||
mcp.WithArray("merge_whitelist_teams", mcp.Description("team names allowed to merge"), mcp.Items(map[string]any{"type": "string"})),
|
||||
mcp.WithNumber("required_approvals", mcp.Description("number of required approvals")),
|
||||
mcp.WithBoolean("block_on_rejected_reviews", mcp.Description("block merge on rejected reviews")),
|
||||
mcp.WithBoolean("block_on_outdated_branch", mcp.Description("block merge on outdated branch")),
|
||||
mcp.WithBoolean("dismiss_stale_approvals", mcp.Description("dismiss stale approvals on new commits")),
|
||||
mcp.WithBoolean("require_signed_commits", mcp.Description("require signed commits")),
|
||||
mcp.WithString("protected_file_patterns", mcp.Description("protected file patterns (semicolon separated)")),
|
||||
mcp.WithString("unprotected_file_patterns", mcp.Description("unprotected file patterns (semicolon separated)")),
|
||||
)
|
||||
)
|
||||
|
||||
func init() {
|
||||
Tool.RegisterRead(server.ServerTool{
|
||||
Tool: BranchProtectionReadTool,
|
||||
Handler: branchProtectionReadFn,
|
||||
})
|
||||
Tool.RegisterWrite(server.ServerTool{
|
||||
Tool: BranchProtectionWriteTool,
|
||||
Handler: branchProtectionWriteFn,
|
||||
})
|
||||
}
|
||||
|
||||
func branchProtectionReadFn(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 "list":
|
||||
return listBranchProtectionsFn(ctx, req)
|
||||
case "get":
|
||||
return getBranchProtectionFn(ctx, req)
|
||||
default:
|
||||
return to.ErrorResult(fmt.Errorf("unknown method: %s", method))
|
||||
}
|
||||
}
|
||||
|
||||
func listBranchProtectionsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called listBranchProtectionsFn")
|
||||
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))
|
||||
}
|
||||
bps, _, err := client.ListBranchProtections(owner, repo, gitea_sdk.ListBranchProtectionsOptions{})
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("list branch protections %v/%v err: %v", owner, repo, err))
|
||||
}
|
||||
return to.TextResult(slimBranchProtections(bps))
|
||||
}
|
||||
|
||||
func getBranchProtectionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called getBranchProtectionFn")
|
||||
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)
|
||||
}
|
||||
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))
|
||||
}
|
||||
bp, _, err := client.GetBranchProtection(owner, repo, name)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get branch protection %v in %v/%v err: %v", name, owner, repo, err))
|
||||
}
|
||||
return to.TextResult(slimBranchProtection(bp))
|
||||
}
|
||||
|
||||
func branchProtectionWriteFn(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":
|
||||
return createBranchProtectionFn(ctx, req)
|
||||
case "edit":
|
||||
return editBranchProtectionFn(ctx, req)
|
||||
case "delete":
|
||||
return deleteBranchProtectionFn(ctx, req)
|
||||
default:
|
||||
return to.ErrorResult(fmt.Errorf("unknown method: %s", method))
|
||||
}
|
||||
}
|
||||
|
||||
func createBranchProtectionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called createBranchProtectionFn")
|
||||
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)
|
||||
}
|
||||
branchName, err := params.GetString(args, "branch_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.CreateBranchProtectionOption{
|
||||
BranchName: branchName,
|
||||
}
|
||||
if v, ok := args["enable_push"].(bool); ok {
|
||||
opt.EnablePush = v
|
||||
}
|
||||
opt.PushWhitelistUsernames = params.GetStringSlice(args, "push_whitelist_usernames")
|
||||
opt.PushWhitelistTeams = params.GetStringSlice(args, "push_whitelist_teams")
|
||||
if v, ok := args["enable_merge_whitelist"].(bool); ok {
|
||||
opt.EnableMergeWhitelist = v
|
||||
}
|
||||
opt.MergeWhitelistUsernames = params.GetStringSlice(args, "merge_whitelist_usernames")
|
||||
opt.MergeWhitelistTeams = params.GetStringSlice(args, "merge_whitelist_teams")
|
||||
if val, exists := args["required_approvals"]; exists {
|
||||
if i, ok := params.ToInt64(val); ok {
|
||||
opt.RequiredApprovals = i
|
||||
}
|
||||
}
|
||||
if v, ok := args["block_on_rejected_reviews"].(bool); ok {
|
||||
opt.BlockOnRejectedReviews = v
|
||||
}
|
||||
if v, ok := args["block_on_outdated_branch"].(bool); ok {
|
||||
opt.BlockOnOutdatedBranch = v
|
||||
}
|
||||
if v, ok := args["dismiss_stale_approvals"].(bool); ok {
|
||||
opt.DismissStaleApprovals = v
|
||||
}
|
||||
if v, ok := args["require_signed_commits"].(bool); ok {
|
||||
opt.RequireSignedCommits = v
|
||||
}
|
||||
if v, ok := args["protected_file_patterns"].(string); ok {
|
||||
opt.ProtectedFilePatterns = v
|
||||
}
|
||||
if v, ok := args["unprotected_file_patterns"].(string); ok {
|
||||
opt.UnprotectedFilePatterns = v
|
||||
}
|
||||
|
||||
bp, _, err := client.CreateBranchProtection(owner, repo, opt)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("create branch protection in %v/%v err: %v", owner, repo, err))
|
||||
}
|
||||
return to.TextResult(slimBranchProtection(bp))
|
||||
}
|
||||
|
||||
func editBranchProtectionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called editBranchProtectionFn")
|
||||
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)
|
||||
}
|
||||
ruleName, err := params.GetString(args, "rule_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.EditBranchProtectionOption{}
|
||||
if v, ok := args["enable_push"].(bool); ok {
|
||||
opt.EnablePush = &v
|
||||
}
|
||||
if v := params.GetStringSlice(args, "push_whitelist_usernames"); v != nil {
|
||||
opt.PushWhitelistUsernames = v
|
||||
}
|
||||
if v := params.GetStringSlice(args, "push_whitelist_teams"); v != nil {
|
||||
opt.PushWhitelistTeams = v
|
||||
}
|
||||
if v, ok := args["enable_merge_whitelist"].(bool); ok {
|
||||
opt.EnableMergeWhitelist = &v
|
||||
}
|
||||
if v := params.GetStringSlice(args, "merge_whitelist_usernames"); v != nil {
|
||||
opt.MergeWhitelistUsernames = v
|
||||
}
|
||||
if v := params.GetStringSlice(args, "merge_whitelist_teams"); v != nil {
|
||||
opt.MergeWhitelistTeams = v
|
||||
}
|
||||
if val, exists := args["required_approvals"]; exists {
|
||||
if i, ok := params.ToInt64(val); ok {
|
||||
opt.RequiredApprovals = &i
|
||||
}
|
||||
}
|
||||
if v, ok := args["block_on_rejected_reviews"].(bool); ok {
|
||||
opt.BlockOnRejectedReviews = &v
|
||||
}
|
||||
if v, ok := args["block_on_outdated_branch"].(bool); ok {
|
||||
opt.BlockOnOutdatedBranch = &v
|
||||
}
|
||||
if v, ok := args["dismiss_stale_approvals"].(bool); ok {
|
||||
opt.DismissStaleApprovals = &v
|
||||
}
|
||||
if v, ok := args["require_signed_commits"].(bool); ok {
|
||||
opt.RequireSignedCommits = &v
|
||||
}
|
||||
if v, ok := args["protected_file_patterns"].(string); ok {
|
||||
opt.ProtectedFilePatterns = &v
|
||||
}
|
||||
if v, ok := args["unprotected_file_patterns"].(string); ok {
|
||||
opt.UnprotectedFilePatterns = &v
|
||||
}
|
||||
|
||||
bp, _, err := client.EditBranchProtection(owner, repo, ruleName, opt)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("edit branch protection %v in %v/%v err: %v", ruleName, owner, repo, err))
|
||||
}
|
||||
return to.TextResult(slimBranchProtection(bp))
|
||||
}
|
||||
|
||||
func deleteBranchProtectionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called deleteBranchProtectionFn")
|
||||
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)
|
||||
}
|
||||
ruleName, err := params.GetString(args, "rule_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))
|
||||
}
|
||||
_, err = client.DeleteBranchProtection(owner, repo, ruleName)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("delete branch protection %v from %v/%v err: %v", ruleName, owner, repo, err))
|
||||
}
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Branch protection rule '%s' deleted from %s/%s", ruleName, owner, repo)), nil
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package repo
|
||||
|
||||
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_sdk "code.gitea.io/sdk/gitea"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
)
|
||||
|
||||
const (
|
||||
CollaboratorReadToolName = "collaborator_read"
|
||||
CollaboratorWriteToolName = "collaborator_write"
|
||||
)
|
||||
|
||||
var (
|
||||
CollaboratorReadTool = mcp.NewTool(
|
||||
CollaboratorReadToolName,
|
||||
mcp.WithDescription("List repository collaborators."),
|
||||
mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("list")),
|
||||
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.WithNumber("perPage", mcp.Description("results per page"), mcp.DefaultNumber(30)),
|
||||
)
|
||||
|
||||
CollaboratorWriteTool = mcp.NewTool(
|
||||
CollaboratorWriteToolName,
|
||||
mcp.WithDescription("Add or remove repository collaborators. Use method 'add' to grant access, 'delete' to revoke."),
|
||||
mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("add", "delete")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||
mcp.WithString("username", mcp.Required(), mcp.Description("collaborator username")),
|
||||
mcp.WithString("permission", mcp.Description("permission level (for 'add')"), mcp.Enum("read", "write", "admin")),
|
||||
)
|
||||
)
|
||||
|
||||
func init() {
|
||||
Tool.RegisterRead(server.ServerTool{
|
||||
Tool: CollaboratorReadTool,
|
||||
Handler: collaboratorReadFn,
|
||||
})
|
||||
Tool.RegisterWrite(server.ServerTool{
|
||||
Tool: CollaboratorWriteTool,
|
||||
Handler: collaboratorWriteFn,
|
||||
})
|
||||
}
|
||||
|
||||
func collaboratorReadFn(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 "list":
|
||||
return listCollaboratorsFn(ctx, req)
|
||||
default:
|
||||
return to.ErrorResult(fmt.Errorf("unknown method: %s", method))
|
||||
}
|
||||
}
|
||||
|
||||
func listCollaboratorsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called listCollaboratorsFn")
|
||||
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))
|
||||
}
|
||||
users, _, err := client.ListCollaborators(owner, repo, gitea_sdk.ListCollaboratorsOptions{
|
||||
ListOptions: gitea_sdk.ListOptions{
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("list collaborators %v/%v err: %v", owner, repo, err))
|
||||
}
|
||||
return to.TextResult(slimUsers(users))
|
||||
}
|
||||
|
||||
func collaboratorWriteFn(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 "add":
|
||||
return addCollaboratorFn(ctx, req)
|
||||
case "delete":
|
||||
return deleteCollaboratorFn(ctx, req)
|
||||
default:
|
||||
return to.ErrorResult(fmt.Errorf("unknown method: %s", method))
|
||||
}
|
||||
}
|
||||
|
||||
func addCollaboratorFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called addCollaboratorFn")
|
||||
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)
|
||||
}
|
||||
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))
|
||||
}
|
||||
opt := gitea_sdk.AddCollaboratorOption{}
|
||||
if perm, ok := args["permission"].(string); ok {
|
||||
am := gitea_sdk.AccessMode(perm)
|
||||
opt.Permission = &am
|
||||
}
|
||||
_, err = client.AddCollaborator(owner, repo, username, opt)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("add collaborator %v to %v/%v err: %v", username, owner, repo, err))
|
||||
}
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Collaborator %s added to %s/%s", username, owner, repo)), nil
|
||||
}
|
||||
|
||||
func deleteCollaboratorFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called deleteCollaboratorFn")
|
||||
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)
|
||||
}
|
||||
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.DeleteCollaborator(owner, repo, username)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("delete collaborator %v from %v/%v err: %v", username, owner, repo, err))
|
||||
}
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Collaborator %s removed from %s/%s", username, owner, repo)), nil
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package repo
|
||||
|
||||
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"
|
||||
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
)
|
||||
|
||||
const (
|
||||
GetCompareToolName = "get_compare"
|
||||
)
|
||||
|
||||
var (
|
||||
GetCompareTool = mcp.NewTool(
|
||||
GetCompareToolName,
|
||||
mcp.WithDescription("Compare two branches, tags, or commits. Returns commits and diff stats between the base and head."),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||
mcp.WithString("basehead", mcp.Required(), mcp.Description("comparison in format 'base...head' (e.g. 'main...feature-branch')")),
|
||||
)
|
||||
)
|
||||
|
||||
func init() {
|
||||
Tool.RegisterRead(server.ServerTool{
|
||||
Tool: GetCompareTool,
|
||||
Handler: getCompareFn,
|
||||
})
|
||||
}
|
||||
|
||||
func getCompareFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called getCompareFn")
|
||||
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)
|
||||
}
|
||||
basehead, err := params.GetString(args, "basehead")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
|
||||
var result map[string]any
|
||||
_, err = gitea.DoJSON(ctx, "GET", fmt.Sprintf("repos/%s/%s/compare/%s", owner, repo, basehead), nil, nil, &result)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("compare %v in %v/%v err: %v", basehead, owner, repo, err))
|
||||
}
|
||||
return to.TextResult(result)
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
package repo
|
||||
|
||||
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_sdk "code.gitea.io/sdk/gitea"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
)
|
||||
|
||||
const (
|
||||
DeployKeyReadToolName = "deploy_key_read"
|
||||
DeployKeyWriteToolName = "deploy_key_write"
|
||||
)
|
||||
|
||||
var (
|
||||
DeployKeyReadTool = mcp.NewTool(
|
||||
DeployKeyReadToolName,
|
||||
mcp.WithDescription("Read deploy keys. Use method 'list' to list all, 'get' to get a specific key."),
|
||||
mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("list", "get")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||
mcp.WithNumber("id", mcp.Description("deploy key ID (required for 'get')")),
|
||||
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
|
||||
mcp.WithNumber("perPage", mcp.Description("results per page"), mcp.DefaultNumber(30)),
|
||||
)
|
||||
|
||||
DeployKeyWriteTool = mcp.NewTool(
|
||||
DeployKeyWriteToolName,
|
||||
mcp.WithDescription("Create or delete deploy keys. Use method 'create' to add a new key, 'delete' to remove."),
|
||||
mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("create", "delete")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||
mcp.WithNumber("id", mcp.Description("deploy key ID (required for 'delete')")),
|
||||
mcp.WithString("title", mcp.Description("key title (required for 'create')")),
|
||||
mcp.WithString("key", mcp.Description("SSH public key content (required for 'create')")),
|
||||
mcp.WithBoolean("read_only", mcp.Description("whether the key has read-only access (default true)")),
|
||||
)
|
||||
)
|
||||
|
||||
func init() {
|
||||
Tool.RegisterRead(server.ServerTool{
|
||||
Tool: DeployKeyReadTool,
|
||||
Handler: deployKeyReadFn,
|
||||
})
|
||||
Tool.RegisterWrite(server.ServerTool{
|
||||
Tool: DeployKeyWriteTool,
|
||||
Handler: deployKeyWriteFn,
|
||||
})
|
||||
}
|
||||
|
||||
func deployKeyReadFn(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 "list":
|
||||
return listDeployKeysFn(ctx, req)
|
||||
case "get":
|
||||
return getDeployKeyFn(ctx, req)
|
||||
default:
|
||||
return to.ErrorResult(fmt.Errorf("unknown method: %s", method))
|
||||
}
|
||||
}
|
||||
|
||||
func listDeployKeysFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called listDeployKeysFn")
|
||||
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))
|
||||
}
|
||||
keys, _, err := client.ListDeployKeys(owner, repo, gitea_sdk.ListDeployKeysOptions{
|
||||
ListOptions: gitea_sdk.ListOptions{
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("list deploy keys %v/%v err: %v", owner, repo, err))
|
||||
}
|
||||
return to.TextResult(slimDeployKeys(keys))
|
||||
}
|
||||
|
||||
func getDeployKeyFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called getDeployKeyFn")
|
||||
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)
|
||||
}
|
||||
id, err := params.GetIndex(args, "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))
|
||||
}
|
||||
key, _, err := client.GetDeployKey(owner, repo, id)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get deploy key %v in %v/%v err: %v", id, owner, repo, err))
|
||||
}
|
||||
return to.TextResult(slimDeployKey(key))
|
||||
}
|
||||
|
||||
func deployKeyWriteFn(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":
|
||||
return createDeployKeyFn(ctx, req)
|
||||
case "delete":
|
||||
return deleteDeployKeyFn(ctx, req)
|
||||
default:
|
||||
return to.ErrorResult(fmt.Errorf("unknown method: %s", method))
|
||||
}
|
||||
}
|
||||
|
||||
func createDeployKeyFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called createDeployKeyFn")
|
||||
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)
|
||||
}
|
||||
title, err := params.GetString(args, "title")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
key, err := params.GetString(args, "key")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
readOnly := true
|
||||
if v, ok := args["read_only"].(bool); ok {
|
||||
readOnly = v
|
||||
}
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
dk, _, err := client.CreateDeployKey(owner, repo, gitea_sdk.CreateKeyOption{
|
||||
Title: title,
|
||||
Key: key,
|
||||
ReadOnly: readOnly,
|
||||
})
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("create deploy key in %v/%v err: %v", owner, repo, err))
|
||||
}
|
||||
return to.TextResult(slimDeployKey(dk))
|
||||
}
|
||||
|
||||
func deleteDeployKeyFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called deleteDeployKeyFn")
|
||||
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)
|
||||
}
|
||||
id, err := params.GetIndex(args, "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.DeleteDeployKey(owner, repo, id)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("delete deploy key %v from %v/%v err: %v", id, owner, repo, err))
|
||||
}
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Deploy key %d deleted from %s/%s", id, owner, repo)), nil
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -206,6 +206,146 @@ func slimTree(t *gitea_sdk.GitTreeResponse) map[string]any {
|
||||
}
|
||||
}
|
||||
|
||||
func slimRepoDetail(r *gitea_sdk.Repository) map[string]any {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
m := slimRepo(r)
|
||||
m["website"] = r.Website
|
||||
m["has_issues"] = r.HasIssues
|
||||
m["has_wiki"] = r.HasWiki
|
||||
m["has_pull_requests"] = r.HasPullRequests
|
||||
m["has_projects"] = r.HasProjects
|
||||
m["mirror"] = r.Mirror
|
||||
m["internal"] = r.Internal
|
||||
m["empty"] = r.Empty
|
||||
m["size"] = r.Size
|
||||
if r.Permissions != nil {
|
||||
m["permissions"] = map[string]any{
|
||||
"admin": r.Permissions.Admin,
|
||||
"push": r.Permissions.Push,
|
||||
"pull": r.Permissions.Pull,
|
||||
}
|
||||
}
|
||||
if r.Parent != nil {
|
||||
m["parent"] = map[string]any{
|
||||
"full_name": r.Parent.FullName,
|
||||
"html_url": r.Parent.HTMLURL,
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func slimUser(u *gitea_sdk.User) map[string]any {
|
||||
if u == nil {
|
||||
return nil
|
||||
}
|
||||
return map[string]any{
|
||||
"id": u.ID,
|
||||
"login": u.UserName,
|
||||
"full_name": u.FullName,
|
||||
"email": u.Email,
|
||||
"avatar_url": u.AvatarURL,
|
||||
"is_admin": u.IsAdmin,
|
||||
}
|
||||
}
|
||||
|
||||
func slimUsers(users []*gitea_sdk.User) []map[string]any {
|
||||
out := make([]map[string]any, 0, len(users))
|
||||
for _, u := range users {
|
||||
out = append(out, slimUser(u))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func slimDeployKey(k *gitea_sdk.DeployKey) map[string]any {
|
||||
if k == nil {
|
||||
return nil
|
||||
}
|
||||
return map[string]any{
|
||||
"id": k.ID,
|
||||
"title": k.Title,
|
||||
"fingerprint": k.Fingerprint,
|
||||
"key": k.Key,
|
||||
"url": k.URL,
|
||||
"read_only": k.ReadOnly,
|
||||
"created_at": k.Created,
|
||||
}
|
||||
}
|
||||
|
||||
func slimDeployKeys(keys []*gitea_sdk.DeployKey) []map[string]any {
|
||||
out := make([]map[string]any, 0, len(keys))
|
||||
for _, k := range keys {
|
||||
out = append(out, slimDeployKey(k))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func slimBranchProtection(bp *gitea_sdk.BranchProtection) map[string]any {
|
||||
if bp == nil {
|
||||
return nil
|
||||
}
|
||||
return map[string]any{
|
||||
"branch_name": bp.BranchName,
|
||||
"rule_name": bp.RuleName,
|
||||
"enable_push": bp.EnablePush,
|
||||
"push_whitelist_usernames": bp.PushWhitelistUsernames,
|
||||
"push_whitelist_teams": bp.PushWhitelistTeams,
|
||||
"enable_merge_whitelist": bp.EnableMergeWhitelist,
|
||||
"merge_whitelist_usernames": bp.MergeWhitelistUsernames,
|
||||
"merge_whitelist_teams": bp.MergeWhitelistTeams,
|
||||
"required_approvals": bp.RequiredApprovals,
|
||||
"enable_approvals_whitelist": bp.EnableApprovalsWhitelist,
|
||||
"block_on_rejected_reviews": bp.BlockOnRejectedReviews,
|
||||
"block_on_outdated_branch": bp.BlockOnOutdatedBranch,
|
||||
"dismiss_stale_approvals": bp.DismissStaleApprovals,
|
||||
"require_signed_commits": bp.RequireSignedCommits,
|
||||
"protected_file_patterns": bp.ProtectedFilePatterns,
|
||||
"unprotected_file_patterns": bp.UnprotectedFilePatterns,
|
||||
"created_at": bp.Created,
|
||||
"updated_at": bp.Updated,
|
||||
}
|
||||
}
|
||||
|
||||
func slimBranchProtections(bps []*gitea_sdk.BranchProtection) []map[string]any {
|
||||
out := make([]map[string]any, 0, len(bps))
|
||||
for _, bp := range bps {
|
||||
out = append(out, slimBranchProtection(bp))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func slimHook(h *gitea_sdk.Hook) map[string]any {
|
||||
if h == nil {
|
||||
return nil
|
||||
}
|
||||
m := map[string]any{
|
||||
"id": h.ID,
|
||||
"type": h.Type,
|
||||
"active": h.Active,
|
||||
"events": h.Events,
|
||||
"created_at": h.Created,
|
||||
"updated_at": h.Updated,
|
||||
}
|
||||
if h.Config != nil {
|
||||
if url, ok := h.Config["url"]; ok {
|
||||
m["url"] = url
|
||||
}
|
||||
if ct, ok := h.Config["content_type"]; ok {
|
||||
m["content_type"] = ct
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func slimHooks(hooks []*gitea_sdk.Hook) []map[string]any {
|
||||
out := make([]map[string]any, 0, len(hooks))
|
||||
for _, h := range hooks {
|
||||
out = append(out, slimHook(h))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func slimDirEntries(entries []*gitea_sdk.ContentsResponse) []map[string]any {
|
||||
out := make([]map[string]any, 0, len(entries))
|
||||
for _, c := range entries {
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
package repo
|
||||
|
||||
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_sdk "code.gitea.io/sdk/gitea"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
)
|
||||
|
||||
const (
|
||||
TopicReadToolName = "topic_read"
|
||||
TopicWriteToolName = "topic_write"
|
||||
)
|
||||
|
||||
var (
|
||||
TopicReadTool = mcp.NewTool(
|
||||
TopicReadToolName,
|
||||
mcp.WithDescription("List repository topics."),
|
||||
mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("list")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||
)
|
||||
|
||||
TopicWriteTool = mcp.NewTool(
|
||||
TopicWriteToolName,
|
||||
mcp.WithDescription("Manage repository topics. Use 'add' to add one, 'delete' to remove one, 'set' to replace all topics."),
|
||||
mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("add", "delete", "set")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||
mcp.WithString("topic", mcp.Description("single topic name (required for 'add', 'delete')")),
|
||||
mcp.WithArray("topics", mcp.Description("list of topic names (required for 'set')"), mcp.Items(map[string]any{"type": "string"})),
|
||||
)
|
||||
)
|
||||
|
||||
func init() {
|
||||
Tool.RegisterRead(server.ServerTool{
|
||||
Tool: TopicReadTool,
|
||||
Handler: topicReadFn,
|
||||
})
|
||||
Tool.RegisterWrite(server.ServerTool{
|
||||
Tool: TopicWriteTool,
|
||||
Handler: topicWriteFn,
|
||||
})
|
||||
}
|
||||
|
||||
func topicReadFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called topicReadFn")
|
||||
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))
|
||||
}
|
||||
topics, _, err := client.ListRepoTopics(owner, repo, gitea_sdk.ListRepoTopicsOptions{})
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("list topics %v/%v err: %v", owner, repo, err))
|
||||
}
|
||||
return to.TextResult(topics)
|
||||
}
|
||||
|
||||
func topicWriteFn(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 "add":
|
||||
return addTopicFn(ctx, req)
|
||||
case "delete":
|
||||
return deleteTopicFn(ctx, req)
|
||||
case "set":
|
||||
return setTopicsFn(ctx, req)
|
||||
default:
|
||||
return to.ErrorResult(fmt.Errorf("unknown method: %s", method))
|
||||
}
|
||||
}
|
||||
|
||||
func addTopicFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called addTopicFn")
|
||||
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)
|
||||
}
|
||||
topic, err := params.GetString(args, "topic")
|
||||
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.AddRepoTopic(owner, repo, topic)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("add topic %v to %v/%v err: %v", topic, owner, repo, err))
|
||||
}
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Topic '%s' added to %s/%s", topic, owner, repo)), nil
|
||||
}
|
||||
|
||||
func deleteTopicFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called deleteTopicFn")
|
||||
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)
|
||||
}
|
||||
topic, err := params.GetString(args, "topic")
|
||||
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.DeleteRepoTopic(owner, repo, topic)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("delete topic %v from %v/%v err: %v", topic, owner, repo, err))
|
||||
}
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Topic '%s' removed from %s/%s", topic, owner, repo)), nil
|
||||
}
|
||||
|
||||
func setTopicsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called setTopicsFn")
|
||||
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)
|
||||
}
|
||||
topics := params.GetStringSlice(args, "topics")
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
_, err = client.SetRepoTopics(owner, repo, topics)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("set topics for %v/%v err: %v", owner, repo, err))
|
||||
}
|
||||
return to.TextResult(map[string]any{
|
||||
"message": fmt.Sprintf("Topics set for %s/%s", owner, repo),
|
||||
"topics": topics,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
package repo
|
||||
|
||||
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_sdk "code.gitea.io/sdk/gitea"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
)
|
||||
|
||||
const (
|
||||
WebhookReadToolName = "webhook_read"
|
||||
WebhookWriteToolName = "webhook_write"
|
||||
)
|
||||
|
||||
var (
|
||||
WebhookReadTool = mcp.NewTool(
|
||||
WebhookReadToolName,
|
||||
mcp.WithDescription("Read repository webhooks. Use method 'list' to list all, 'get' to get a specific webhook."),
|
||||
mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("list", "get")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||
mcp.WithNumber("id", mcp.Description("webhook ID (required for 'get')")),
|
||||
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
|
||||
mcp.WithNumber("perPage", mcp.Description("results per page"), mcp.DefaultNumber(30)),
|
||||
)
|
||||
|
||||
WebhookWriteTool = mcp.NewTool(
|
||||
WebhookWriteToolName,
|
||||
mcp.WithDescription("Create, edit, delete, or test repository webhooks."),
|
||||
mcp.WithString("method", mcp.Required(), mcp.Description("operation to perform"), mcp.Enum("create", "edit", "delete", "test")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||
mcp.WithNumber("id", mcp.Description("webhook ID (required for 'edit', 'delete', 'test')")),
|
||||
mcp.WithString("type", mcp.Description("webhook type (for 'create')"), mcp.Enum("gitea", "slack", "discord", "dingtalk", "telegram", "msteams", "feishu", "wechatwork", "packagist")),
|
||||
mcp.WithString("url", mcp.Description("target URL (required for 'create', optional for 'edit')")),
|
||||
mcp.WithString("content_type", mcp.Description("content type"), mcp.Enum("json", "form")),
|
||||
mcp.WithString("secret", mcp.Description("webhook secret")),
|
||||
mcp.WithArray("events", mcp.Description("event types to trigger on (e.g. push, pull_request, issues)"), mcp.Items(map[string]any{"type": "string"})),
|
||||
mcp.WithBoolean("active", mcp.Description("whether the webhook is active")),
|
||||
mcp.WithString("branch_filter", mcp.Description("branch filter pattern")),
|
||||
)
|
||||
)
|
||||
|
||||
func init() {
|
||||
Tool.RegisterRead(server.ServerTool{
|
||||
Tool: WebhookReadTool,
|
||||
Handler: webhookReadFn,
|
||||
})
|
||||
Tool.RegisterWrite(server.ServerTool{
|
||||
Tool: WebhookWriteTool,
|
||||
Handler: webhookWriteFn,
|
||||
})
|
||||
}
|
||||
|
||||
func webhookReadFn(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 "list":
|
||||
return listWebhooksFn(ctx, req)
|
||||
case "get":
|
||||
return getWebhookFn(ctx, req)
|
||||
default:
|
||||
return to.ErrorResult(fmt.Errorf("unknown method: %s", method))
|
||||
}
|
||||
}
|
||||
|
||||
func listWebhooksFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called listWebhooksFn")
|
||||
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))
|
||||
}
|
||||
hooks, _, err := client.ListRepoHooks(owner, repo, gitea_sdk.ListHooksOptions{
|
||||
ListOptions: gitea_sdk.ListOptions{
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("list webhooks %v/%v err: %v", owner, repo, err))
|
||||
}
|
||||
return to.TextResult(slimHooks(hooks))
|
||||
}
|
||||
|
||||
func getWebhookFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called getWebhookFn")
|
||||
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)
|
||||
}
|
||||
id, err := params.GetIndex(args, "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))
|
||||
}
|
||||
hook, _, err := client.GetRepoHook(owner, repo, id)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get webhook %v in %v/%v err: %v", id, owner, repo, err))
|
||||
}
|
||||
return to.TextResult(slimHook(hook))
|
||||
}
|
||||
|
||||
func webhookWriteFn(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":
|
||||
return createWebhookFn(ctx, req)
|
||||
case "edit":
|
||||
return editWebhookFn(ctx, req)
|
||||
case "delete":
|
||||
return deleteWebhookFn(ctx, req)
|
||||
case "test":
|
||||
return testWebhookFn(ctx, req)
|
||||
default:
|
||||
return to.ErrorResult(fmt.Errorf("unknown method: %s", method))
|
||||
}
|
||||
}
|
||||
|
||||
func createWebhookFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called createWebhookFn")
|
||||
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)
|
||||
}
|
||||
hookType := params.GetOptionalString(args, "type", "gitea")
|
||||
url, err := params.GetString(args, "url")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
contentType := params.GetOptionalString(args, "content_type", "json")
|
||||
secret, _ := args["secret"].(string)
|
||||
events := params.GetStringSlice(args, "events")
|
||||
active := true
|
||||
if v, ok := args["active"].(bool); ok {
|
||||
active = v
|
||||
}
|
||||
branchFilter, _ := args["branch_filter"].(string)
|
||||
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||
}
|
||||
|
||||
opt := gitea_sdk.CreateHookOption{
|
||||
Type: gitea_sdk.HookType(hookType),
|
||||
Config: map[string]string{
|
||||
"url": url,
|
||||
"content_type": contentType,
|
||||
"secret": secret,
|
||||
},
|
||||
Events: events,
|
||||
Active: active,
|
||||
BranchFilter: branchFilter,
|
||||
}
|
||||
|
||||
hook, _, err := client.CreateRepoHook(owner, repo, opt)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("create webhook in %v/%v err: %v", owner, repo, err))
|
||||
}
|
||||
return to.TextResult(slimHook(hook))
|
||||
}
|
||||
|
||||
func editWebhookFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called editWebhookFn")
|
||||
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)
|
||||
}
|
||||
id, err := params.GetIndex(args, "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))
|
||||
}
|
||||
|
||||
opt := gitea_sdk.EditHookOption{}
|
||||
config := map[string]string{}
|
||||
if v, ok := args["url"].(string); ok {
|
||||
config["url"] = v
|
||||
}
|
||||
if v, ok := args["content_type"].(string); ok {
|
||||
config["content_type"] = v
|
||||
}
|
||||
if v, ok := args["secret"].(string); ok {
|
||||
config["secret"] = v
|
||||
}
|
||||
if len(config) > 0 {
|
||||
opt.Config = config
|
||||
}
|
||||
if events := params.GetStringSlice(args, "events"); events != nil {
|
||||
opt.Events = events
|
||||
}
|
||||
if v, ok := args["active"].(bool); ok {
|
||||
opt.Active = &v
|
||||
}
|
||||
if v, ok := args["branch_filter"].(string); ok {
|
||||
opt.BranchFilter = v
|
||||
}
|
||||
|
||||
_, err = client.EditRepoHook(owner, repo, id, opt)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("edit webhook %v in %v/%v err: %v", id, owner, repo, err))
|
||||
}
|
||||
// EditRepoHook doesn't return the hook, fetch it
|
||||
hook, _, err := client.GetRepoHook(owner, repo, id)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("get webhook %v in %v/%v after edit err: %v", id, owner, repo, err))
|
||||
}
|
||||
return to.TextResult(slimHook(hook))
|
||||
}
|
||||
|
||||
func deleteWebhookFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called deleteWebhookFn")
|
||||
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)
|
||||
}
|
||||
id, err := params.GetIndex(args, "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.DeleteRepoHook(owner, repo, id)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("delete webhook %v from %v/%v err: %v", id, owner, repo, err))
|
||||
}
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Webhook %d deleted from %s/%s", id, owner, repo)), nil
|
||||
}
|
||||
|
||||
func testWebhookFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
log.Debugf("Called testWebhookFn")
|
||||
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)
|
||||
}
|
||||
id, err := params.GetIndex(args, "id")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
// TestRepoHook is not in the SDK - use raw REST
|
||||
_, err = gitea.DoJSON(ctx, "POST", fmt.Sprintf("repos/%s/%s/hooks/%d/tests", owner, repo, id), nil, nil, nil)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("test webhook %v in %v/%v err: %v", id, owner, repo, err))
|
||||
}
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Webhook %d test triggered for %s/%s", id, owner, repo)), nil
|
||||
}
|
||||
Reference in New Issue
Block a user