Files
gitea-mcp/docs/superpowers/specs/2026-05-06-gitops-agent-tools-design.md
Mathias Bergqvist 0cd465fb68 docs: add GitOps agent tools design spec
9 new tools to enable full autonomous GitOps loop: repo_status,
branch_list/delete/protection_get, pr_list/merge, dir_list,
file_delete, tag_create.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 21:51:39 +02:00

6.6 KiB

GitOps Agent Tools — Design Spec

Date: 2026-05-06 Status: Approved

Goal

Extend the Gitea MCP server with the tools an AI agent needs to drive a full GitOps development loop autonomously — reading repo state, deciding on a branching strategy, making changes, opening and merging PRs, and tagging releases — without any local git tooling.

The agent selects between feature-branch and trunk-based development based on branch protection rules it reads at runtime.


New Tools (9)

All tools follow the existing pattern: one file in internal/tools/, one Gitea client method in internal/gitea/, allowlist check on owner, table-driven tests in both packages.

repo_status

Convenience read tool — returns branch list, open PRs, and protection info for a target branch in a single call. Designed for the agent's first query on any repo so it can decide its strategy.

Inputs: owner, name, branch (optional — defaults to repo default branch) Output: { branches: [...], open_prs: [...], protection: { protected, required_approvals, push_whitelist, merge_whitelist } } Implementation: calls ListBranches + ListPullRequests(state=open) + GetBranchProtection internally, composes result. No new Gitea API surface.


branch_list

Inputs: owner, name, page (optional), limit (optional, default 30) Output: array of { name, sha } Gitea endpoint: GET /api/v1/repos/{owner}/{repo}/branches


branch_delete

Inputs: owner, name, branch Output: confirmation message Gitea endpoint: DELETE /api/v1/repos/{owner}/{repo}/branches/{branch} Error handling: 403 from Gitea (protected branch) surfaced as a descriptive error.


branch_protection_get

Inputs: owner, name, branch Output: { protected, required_approvals, push_whitelist, merge_whitelist } Gitea endpoint: GET /api/v1/repos/{owner}/{repo}/branch_protections/{branch} Error handling: 404 → return { protected: false }, not an error. Allows agent to make clean boolean decisions.


pr_list

Inputs: owner, name, state (open/closed/all, default open), head (optional branch filter), page, limit Output: array of { number, title, state, head_branch, base_branch, draft, html_url } Gitea endpoint: GET /api/v1/repos/{owner}/{repo}/pulls


pr_merge

Inputs: owner, name, index, style (merge/squash/rebase, default merge), merge_message_title (optional), merge_message_field (optional) Output: { merged: true, commit_sha } — if Gitea returns 204 No Content (some merge styles), output is { merged: true } without commit_sha. Gitea endpoint: POST /api/v1/repos/{owner}/{repo}/pulls/{index}/merge Error handling: 405 (checks failing) and 409 (merge conflict) passed through with the Gitea error message intact so the agent understands why it failed.


dir_list

Inputs: owner, name, path (empty string = repo root), ref (optional branch/tag/SHA) Output: array of { name, path, type (file|dir|symlink), sha, size } Gitea endpoint: GET /api/v1/repos/{owner}/{repo}/contents/{path} Note: same endpoint as file_read but returns an array when path is a directory. Client detects response shape (array vs object). If called on a file path, returns a descriptive error: "path is a file, not a directory — use file_read".


file_delete

Inputs: owner, name, path, branch, message, sha (required — current blob SHA) Output: { commit_sha, html_url } Gitea endpoint: DELETE /api/v1/repos/{owner}/{repo}/contents/{path}


tag_create

Inputs: owner, name, tag (tag name), target (branch name or commit SHA), message (optional — creates annotated tag if set) Output: { tag, commit_sha, html_url } Gitea endpoint: POST /api/v1/repos/{owner}/{repo}/tags


Gitea Client Methods

New methods on gitea.Client:

Method Endpoint HTTP verb
ListBranches(ctx, owner, repo, page, limit) /api/v1/repos/{owner}/{repo}/branches GET
DeleteBranch(ctx, owner, repo, branch) /api/v1/repos/{owner}/{repo}/branches/{branch} DELETE
GetBranchProtection(ctx, owner, repo, branch) /api/v1/repos/{owner}/{repo}/branch_protections/{branch} GET
ListPullRequests(ctx, owner, repo, state, head, page, limit) /api/v1/repos/{owner}/{repo}/pulls GET
MergePullRequest(ctx, owner, repo, index, args) /api/v1/repos/{owner}/{repo}/pulls/{index}/merge POST
ListContents(ctx, owner, repo, path, ref) /api/v1/repos/{owner}/{repo}/contents/{path} GET
DeleteFile(ctx, owner, repo, path, args) /api/v1/repos/{owner}/{repo}/contents/{path} DELETE
CreateTag(ctx, owner, repo, args) /api/v1/repos/{owner}/{repo}/tags POST

Architecture

No structural changes. Each new tool is:

  • One file: internal/tools/<tool_name>.go + internal/tools/<tool_name>_test.go
  • One client method: internal/gitea/<domain>.go (added to existing domain files where logical)
  • Registered in cmd/gitea-mcp/main.go

repo_status is the only tool with internal composition — it calls three client methods and merges their results. It has no dedicated client method of its own.

New client methods go in existing domain files:

  • Branch methods → internal/gitea/files.go (already has BranchExists, CreateBranch)
  • PR methods → internal/gitea/pulls.go
  • Contents (dir_list, file_delete) → internal/gitea/files.go
  • Tags → new internal/gitea/tags.go

Testing

Pattern: table-driven tests with a httptest.NewServer mock, same as file_write_branch_test.go.

Each tool covers:

  • Happy path
  • 404 response
  • Allowlist rejection
  • Tool-specific edge cases:
    • branch_delete: 403 protected branch
    • branch_protection_get: 404 → {protected: false} not error
    • dir_list: file path → descriptive error
    • pr_merge: 405 checks failing, 409 merge conflict
    • repo_status: any one sub-call failing propagates the error

Agent Decision Flow (Reference)

1. repo_status(owner, name)
   → if branch.protected && required_approvals > 0:
       use feature-branch workflow
   → else:
       use trunk-based workflow

Feature-branch workflow:
  file_write_branch (auto-creates branch)
  → pr_create
  → [wait for CI via workflow_run_status]
  → pr_merge
  → branch_delete

Trunk-based workflow:
  file_write_branch(branch=main)
  → [optionally] tag_create

Post-merge (either):
  → [optionally] tag_create to trigger deployment