package organization 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 ( ListOrgHooksToolName = "list_org_hooks" GetOrgHookToolName = "get_org_hook" CreateOrgHookToolName = "create_org_hook" EditOrgHookToolName = "edit_org_hook" DeleteOrgHookToolName = "delete_org_hook" ) var ( ListOrgHooksTool = mcp.NewTool( ListOrgHooksToolName, mcp.WithDescription("List an organization's webhooks"), mcp.WithString("org", mcp.Required(), mcp.Description("organization name")), mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)), mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)), ) GetOrgHookTool = mcp.NewTool( GetOrgHookToolName, mcp.WithDescription("Get an organization webhook by ID"), mcp.WithString("org", mcp.Required(), mcp.Description("organization name")), mcp.WithNumber("id", mcp.Required(), mcp.Description("webhook ID")), ) CreateOrgHookTool = mcp.NewTool( CreateOrgHookToolName, mcp.WithDescription("Create an organization webhook"), mcp.WithString("org", mcp.Required(), mcp.Description("organization name")), mcp.WithString("type", mcp.Required(), mcp.Description("hook type: gitea, slack, discord, dingtalk, telegram, msteams, feishu, matrix, 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"), mcp.DefaultString("json")), mcp.WithString("secret", mcp.Description("webhook secret")), mcp.WithBoolean("active", mcp.Description("whether the webhook is active (default: true)")), mcp.WithArray("events", mcp.Description("list of events to trigger on"), mcp.Items(map[string]any{"type": "string"})), ) EditOrgHookTool = mcp.NewTool( EditOrgHookToolName, mcp.WithDescription("Edit an organization webhook"), mcp.WithString("org", mcp.Required(), mcp.Description("organization 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")), mcp.WithArray("events", mcp.Description("list of events to trigger on"), mcp.Items(map[string]any{"type": "string"})), ) DeleteOrgHookTool = mcp.NewTool( DeleteOrgHookToolName, mcp.WithDescription("Delete an organization webhook"), mcp.WithString("org", mcp.Required(), mcp.Description("organization name")), mcp.WithNumber("id", mcp.Required(), mcp.Description("webhook ID")), ) ) func init() { Tool.RegisterRead(server.ServerTool{Tool: ListOrgHooksTool, Handler: ListOrgHooksFn}) Tool.RegisterRead(server.ServerTool{Tool: GetOrgHookTool, Handler: GetOrgHookFn}) Tool.RegisterWrite(server.ServerTool{Tool: CreateOrgHookTool, Handler: CreateOrgHookFn}) Tool.RegisterWrite(server.ServerTool{Tool: EditOrgHookTool, Handler: EditOrgHookFn}) Tool.RegisterWrite(server.ServerTool{Tool: DeleteOrgHookTool, Handler: DeleteOrgHookFn}) } func ListOrgHooksFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called ListOrgHooksFn") org, ok := req.GetArguments()["org"].(string) if !ok { return to.ErrorResult(errors.New("org is 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.ListOrgHooks(org, gitea_sdk.ListHooksOptions{ ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)}, }) if err != nil { return to.ErrorResult(fmt.Errorf("list org %s hooks err: %v", org, err)) } return to.TextResult(hooks) } func GetOrgHookFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called GetOrgHookFn") org, ok := req.GetArguments()["org"].(string) if !ok { return to.ErrorResult(errors.New("org is 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.GetOrgHook(org, id) if err != nil { return to.ErrorResult(fmt.Errorf("get org %s hook %d err: %v", org, id, err)) } return to.TextResult(hook) } func CreateOrgHookFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called CreateOrgHookFn") org, ok := req.GetArguments()["org"].(string) if !ok { return to.ErrorResult(errors.New("org is required")) } hookType, ok := req.GetArguments()["type"].(string) if !ok { return to.ErrorResult(errors.New("type is required")) } hookURL, ok := req.GetArguments()["url"].(string) if !ok { return to.ErrorResult(errors.New("url is required")) } contentType := "json" if v, ok := req.GetArguments()["content_type"].(string); ok { contentType = v } config := map[string]string{ "url": hookURL, "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 } if eventsArg, exists := req.GetArguments()["events"]; exists { if eventsSlice, ok := eventsArg.([]any); ok { events := make([]string, 0, len(eventsSlice)) for _, e := range eventsSlice { if s, ok := e.(string); ok { events = append(events, s) } } opt.Events = events } } client, err := gitea.ClientFromContext(ctx) if err != nil { return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) } hook, _, err := client.CreateOrgHook(org, opt) if err != nil { return to.ErrorResult(fmt.Errorf("create org %s hook err: %v", org, err)) } return to.TextResult(hook) } func EditOrgHookFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called EditOrgHookFn") org, ok := req.GetArguments()["org"].(string) if !ok { return to.ErrorResult(errors.New("org is 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 } if eventsArg, exists := req.GetArguments()["events"]; exists { if eventsSlice, ok := eventsArg.([]any); ok { events := make([]string, 0, len(eventsSlice)) for _, e := range eventsSlice { if s, ok := e.(string); ok { events = append(events, s) } } opt.Events = events } } client, err := gitea.ClientFromContext(ctx) if err != nil { return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) } _, err = client.EditOrgHook(org, id, opt) if err != nil { return to.ErrorResult(fmt.Errorf("edit org %s hook %d err: %v", org, id, err)) } hook, _, err := client.GetOrgHook(org, id) if err != nil { return to.ErrorResult(fmt.Errorf("get org %s hook %d after edit err: %v", org, id, err)) } return to.TextResult(hook) } func DeleteOrgHookFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { log.Debugf("Called DeleteOrgHookFn") org, ok := req.GetArguments()["org"].(string) if !ok { return to.ErrorResult(errors.New("org is 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.DeleteOrgHook(org, id) if err != nil { return to.ErrorResult(fmt.Errorf("delete org %s hook %d err: %v", org, id, err)) } return to.TextResult(map[string]any{"status": "deleted", "org": org, "hook_id": id}) }