Files
gitea-mcp/internal/tools/repo_mirror_push.go
Mathias Bergqvist e2da495581
All checks were successful
CD / Lint / Test / Vet (pull_request) Successful in 7s
CD / Build & Import (pull_request) Has been skipped
CD / Deploy via GitOps (pull_request) Has been skipped
feat(tools): add repo_create, repo_update, repo_mirror_push (#12, #13, #16)
repo_create: POST /user/repos or /orgs/{org}/repos, is_org flag routes
repo_update: PATCH /repos/{owner}/{repo}, confirm required when private=false
repo_mirror_push: add/list/delete push mirrors, password never returned
2026-05-15 10:14:18 +02:00

118 lines
3.5 KiB
Go

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)
}
}