package repo import ( "context" "errors" "fmt" "git.lethalbits.com/lethalbits/gitea-mcp-extended/pkg/gitea" "git.lethalbits.com/lethalbits/gitea-mcp-extended/pkg/log" "git.lethalbits.com/lethalbits/gitea-mcp-extended/pkg/params" "git.lethalbits.com/lethalbits/gitea-mcp-extended/pkg/to" gitea_sdk "code.gitea.io/sdk/gitea" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) const ( ListRepoHooksToolName = "list_repo_hooks" GetRepoHookToolName = "get_repo_hook" CreateRepoHookToolName = "create_repo_hook" EditRepoHookToolName = "edit_repo_hook" DeleteRepoHookToolName = "delete_repo_hook" ) var ( ListRepoHooksTool = mcp.NewTool( ListRepoHooksToolName, mcp.WithDescription("List a repository's webhooks"), 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("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)), ) GetRepoHookTool = mcp.NewTool( GetRepoHookToolName, mcp.WithDescription("Get a repository webhook by ID"), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithNumber("id", mcp.Required(), mcp.Description("webhook ID")), ) CreateRepoHookTool = mcp.NewTool( CreateRepoHookToolName, mcp.WithDescription("Create a webhook for a repository"), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("type", mcp.Required(), mcp.Description("hook type: gitea, gogs, slack, discord, dingtalk, telegram, msteams, feishu, wechatwork, packagist")), mcp.WithString("url", mcp.Required(), mcp.Description("target URL for the webhook")), mcp.WithString("content_type", mcp.Description("content type: json or form (default: json)")), mcp.WithString("secret", mcp.Description("webhook secret")), mcp.WithBoolean("active", mcp.Description("whether the webhook is active (default: true)")), ) EditRepoHookTool = mcp.NewTool( EditRepoHookToolName, mcp.WithDescription("Edit a repository webhook"), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithNumber("id", mcp.Required(), mcp.Description("webhook ID")), mcp.WithString("url", mcp.Description("target URL")), mcp.WithString("content_type", mcp.Description("content type: json or form")), mcp.WithString("secret", mcp.Description("webhook secret")), mcp.WithBoolean("active", mcp.Description("whether the webhook is active")), ) DeleteRepoHookTool = mcp.NewTool( DeleteRepoHookToolName, mcp.WithDescription("Delete a repository webhook"), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithNumber("id", mcp.Required(), mcp.Description("webhook ID")), ) ) func init() { Tool.RegisterRead(server.ServerTool{Tool: ListRepoHooksTool, Handler: ListRepoHooksFn}) Tool.RegisterRead(server.ServerTool{Tool: GetRepoHookTool, Handler: GetRepoHookFn}) Tool.RegisterWrite(server.ServerTool{Tool: CreateRepoHookTool, Handler: CreateRepoHookFn}) Tool.RegisterWrite(server.ServerTool{Tool: EditRepoHookTool, Handler: EditRepoHookFn}) Tool.RegisterWrite(server.ServerTool{Tool: DeleteRepoHookTool, Handler: DeleteRepoHookFn}) } func ListRepoHooksFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called ListRepoHooksFn") owner, _ := req.GetArguments()["owner"].(string) repo, _ := req.GetArguments()["repo"].(string) if owner == "" || repo == "" { return to.ErrorResult(errors.New("owner and repo are required")) } page := params.GetOptionalInt(req.GetArguments(), "page", 1) pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100) 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: int(page), PageSize: int(pageSize)}, }) if err != nil { return to.ErrorResult(fmt.Errorf("list repo hooks err: %v", err)) } return to.TextResult(hooks) } func GetRepoHookFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called GetRepoHookFn") owner, _ := req.GetArguments()["owner"].(string) repo, _ := req.GetArguments()["repo"].(string) if owner == "" || repo == "" { return to.ErrorResult(errors.New("owner and repo are required")) } id, err := params.GetIndex(req.GetArguments(), "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 repo hook err: %v", err)) } return to.TextResult(hook) } func CreateRepoHookFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called CreateRepoHookFn") owner, _ := req.GetArguments()["owner"].(string) repo, _ := req.GetArguments()["repo"].(string) hookType, _ := req.GetArguments()["type"].(string) url, _ := req.GetArguments()["url"].(string) if owner == "" || repo == "" || hookType == "" || url == "" { return to.ErrorResult(errors.New("owner, repo, type, and url are required")) } contentType := "json" if v, ok := req.GetArguments()["content_type"].(string); ok { contentType = v } config := map[string]string{ "url": url, "content_type": contentType, } if v, ok := req.GetArguments()["secret"].(string); ok { config["secret"] = v } opt := gitea_sdk.CreateHookOption{ Type: gitea_sdk.HookType(hookType), Config: config, Active: true, } if v, ok := req.GetArguments()["active"].(bool); ok { opt.Active = v } client, err := gitea.ClientFromContext(ctx) if err != nil { return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) } hook, _, err := client.CreateRepoHook(owner, repo, opt) if err != nil { return to.ErrorResult(fmt.Errorf("create repo hook err: %v", err)) } return to.TextResult(hook) } func EditRepoHookFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called EditRepoHookFn") owner, _ := req.GetArguments()["owner"].(string) repo, _ := req.GetArguments()["repo"].(string) if owner == "" || repo == "" { return to.ErrorResult(errors.New("owner and repo are required")) } id, err := params.GetIndex(req.GetArguments(), "id") if err != nil { return to.ErrorResult(err) } opt := gitea_sdk.EditHookOption{} config := map[string]string{} if v, ok := req.GetArguments()["url"].(string); ok { config["url"] = v } if v, ok := req.GetArguments()["content_type"].(string); ok { config["content_type"] = v } if v, ok := req.GetArguments()["secret"].(string); ok { config["secret"] = v } if len(config) > 0 { opt.Config = config } if v, ok := req.GetArguments()["active"].(bool); ok { opt.Active = &v } client, err := gitea.ClientFromContext(ctx) if err != nil { return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) } _, err = client.EditRepoHook(owner, repo, id, opt) if err != nil { return to.ErrorResult(fmt.Errorf("edit repo hook err: %v", err)) } return to.TextResult(map[string]string{"status": "updated"}) } func DeleteRepoHookFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called DeleteRepoHookFn") owner, _ := req.GetArguments()["owner"].(string) repo, _ := req.GetArguments()["repo"].(string) if owner == "" || repo == "" { return to.ErrorResult(errors.New("owner and repo are required")) } id, err := params.GetIndex(req.GetArguments(), "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 repo hook err: %v", err)) } return to.TextResult(map[string]string{"status": "deleted"}) }