package tools import ( "context" "encoding/json" "fmt" "gitea.d-ma.be/mathias/gitea-mcp/internal/allowlist" "gitea.d-ma.be/mathias/gitea-mcp/internal/gitea" "gitea.d-ma.be/mathias/gitea-mcp/internal/registry" ) type RepoMirrorPush struct { c *gitea.Client a *allowlist.Allowlist } func NewRepoMirrorPush(c *gitea.Client, a *allowlist.Allowlist) *RepoMirrorPush { return &RepoMirrorPush{c: c, a: a} } func (t *RepoMirrorPush) Descriptor() registry.ToolDescriptor { return registry.ToolDescriptor{ Name: "repo_mirror_push", Description: "Manage push mirrors for a repository: add, list, or delete.", InputSchema: json.RawMessage(`{ "type":"object", "properties":{ "owner":{"type":"string"}, "name":{"type":"string"}, "action":{"type":"string","enum":["add","list","delete"]}, "remote_address":{"type":"string","description":"Mirror target URL (required for add)."}, "remote_username":{"type":"string"}, "remote_password":{"type":"string","description":"Never logged or returned."}, "interval":{"type":"string","description":"Sync interval, e.g. '8h0m0s'."}, "sync_on_commit":{"type":"boolean"}, "mirror_name":{"type":"string","description":"Remote name to delete (required for delete)."} }, "required":["owner","name","action"] }`), } } type repoMirrorPushArgs struct { Owner string `json:"owner"` Name string `json:"name"` Action string `json:"action"` RemoteAddress string `json:"remote_address"` RemoteUsername string `json:"remote_username"` RemotePassword string `json:"remote_password"` Interval string `json:"interval"` SyncOnCommit bool `json:"sync_on_commit"` MirrorName string `json:"mirror_name"` } // safeMirror omits remote_password so it is never returned to the caller. type safeMirror struct { ID int `json:"id"` RemoteName string `json:"remote_name"` RemoteAddress string `json:"remote_address"` Interval string `json:"interval"` SyncOnCommit bool `json:"sync_on_commit"` } func toSafeMirror(m *gitea.PushMirror) safeMirror { return safeMirror{ ID: m.ID, RemoteName: m.RemoteName, RemoteAddress: m.RemoteAddress, Interval: m.Interval, SyncOnCommit: m.SyncOnCommit, } } func (t *RepoMirrorPush) Call(ctx context.Context, raw json.RawMessage) (json.RawMessage, error) { var args repoMirrorPushArgs if err := parseArgs(raw, &args); err != nil { return nil, err } if err := t.a.Check(args.Owner); err != nil { return nil, err } switch args.Action { case "add": m, err := t.c.AddPushMirror(ctx, args.Owner, args.Name, gitea.AddPushMirrorArgs{ RemoteAddress: args.RemoteAddress, RemoteUsername: args.RemoteUsername, RemotePassword: args.RemotePassword, Interval: args.Interval, SyncOnCommit: args.SyncOnCommit, }) if err != nil { return nil, err } return textOK(toSafeMirror(m)) case "list": mirrors, err := t.c.ListPushMirrors(ctx, args.Owner, args.Name) if err != nil { return nil, err } safe := make([]safeMirror, len(mirrors)) for i := range mirrors { safe[i] = toSafeMirror(&mirrors[i]) } return textOK(safe) case "delete": if args.MirrorName == "" { return nil, fmt.Errorf("mirror_name is required for action=delete") } if err := t.c.DeletePushMirror(ctx, args.Owner, args.Name, args.MirrorName); err != nil { return nil, err } return textOK(map[string]string{"status": "deleted", "mirror_name": args.MirrorName}) default: return nil, fmt.Errorf("unknown action %q: must be add, list, or delete", args.Action) } }