Files
Gitea-Tools/README.md
T

15 KiB
Raw Blame History

Gitea Tools

A collection of Python scripts and an MCP server to automate interactions with Gitea instances.

Supported Instances

Remote Host Org / Repo
dadeschools gitea.dadeschools.net Contractor / Timesheet
prgs gitea.prgs.cc Scaled-Tech-Consulting / Timesheet

Authentication

Authentication is configured via environment variables or a local .env file in the repository root (uses python-dotenv).

Create a .env file in the project root:

# Option A: Gitea Personal Access Tokens (Recommended)
GITEA_TOKEN_DADESCHOOLS="your_token_here"
GITEA_TOKEN_PRGS="your_token_here"

# Option B: Gitea Username & Password (fallback)
GITEA_USER_DADESCHOOLS="username"
GITEA_PASS_DADESCHOOLS="password"
GITEA_USER_PRGS="username"
GITEA_PASS_PRGS="password"

# Optional: Fallback to macOS Keychain (via git credential fill)
# GITEA_USE_KEYCHAIN=1

The Gitea-Tools MCP server exposes all functionality as structured tool calls. Any MCP-compatible agent (Antigravity, Claude Code, etc.) can call these tools natively.

Available Tools

Tool Description
gitea_create_issue Create an issue with title, body, remote
gitea_create_pr Open a pull request with title, head, base
gitea_edit_pr Edit details of an existing pull request
gitea_list_prs List pull requests with state/remote
gitea_view_pr Get full details of a single pull request
gitea_merge_pr Gated merge: merge/squash/rebase only after identity+profile+eligibility gates pass, explicit confirmation="MERGE PR <n>", optional head-SHA and changed-files pinning (no self-merge, no force)
gitea_review_pr Legacy wrapper for gitea_submit_pr_review (merging disabled)
gitea_delete_branch Delete a remote branch
gitea_close_issue Close an issue by number
gitea_list_issues List issues with state/label filters
gitea_view_issue Get full details of a single issue
gitea_whoami Read-only: identify the authenticated Gitea account (safe metadata only)
gitea_get_profile Read-only: describe the active runtime execution profile (safe metadata only)
gitea_check_pr_eligibility Read-only: check if the current identity/profile may review/approve/request_changes/merge a PR
gitea_submit_pr_review Gated review mutation: comment/approve/request_changes, only after identity+profile+eligibility gates pass (no merge, no self-approval)
gitea_mark_issue Claim/release an issue (start/done)
gitea_list_labels List all available labels in a repository
gitea_create_label Create a new label with custom color
gitea_set_issue_labels Replace all labels on an issue
gitea_get_file Retrieve file content and SHA metadata
gitea_commit_files Commit changes to multiple files atomically
gitea_mirror_refs Mirror branches + tags between instances

Setup

1. Install dependencies

cd /Users/jasonwalker/Development/Gitea-Tools
python3 -m venv venv          # skip if venv already exists
source venv/bin/activate
pip install "mcp[cli]"

2. Configure your AI client

The MCP server uses stdio transport — each client starts it as a subprocess. Add the config below to your client, then restart it.

Antigravity (Google)

Add to ~/.gemini/antigravity-ide/mcp_config.json inside "mcpServers":

"gitea-tools": {
  "command": "/Users/jasonwalker/Development/Gitea-Tools/venv/bin/python3",
  "args": ["/Users/jasonwalker/Development/Gitea-Tools/mcp_server.py"],
  "env": {}
}

Restart Antigravity to load the server. Tools appear as lazy-loaded MCP tools (call via call_mcp_tool with ServerName: "gitea-tools").

Claude Code (Anthropic)

Add to ~/.claude.json (global) or .mcp.json in the project root:

{
  "mcpServers": {
    "gitea-tools": {
      "command": "/Users/jasonwalker/Development/Gitea-Tools/venv/bin/python3",
      "args": ["/Users/jasonwalker/Development/Gitea-Tools/mcp_server.py"]
    }
  }
}

Restart Claude Code. Tools appear as mcp__gitea-tools__gitea_create_issue, etc.

Any MCP-compatible client

The server is a standard MCP stdio server. Point your client at:

  • Command: /Users/jasonwalker/Development/Gitea-Tools/venv/bin/python3
  • Args: ["/Users/jasonwalker/Development/Gitea-Tools/mcp_server.py"]
  • Transport: stdio

No environment variables needed — auth is handled via macOS keychain.

Runtime profiles (multiple env-configured entries)

The same server can run as separate MCP entries, each authenticating as its own Gitea token and carrying its own profile name. This keeps roles task-scoped: the profile is the role, not the LLM. Point each entry at a different gitignored env file.

{
  "mcpServers": {
    "gitea-tools-reviewer": {
      "command": "/Users/jasonwalker/Development/Gitea-Tools/venv/bin/python3",
      "args": ["/Users/jasonwalker/Development/Gitea-Tools/mcp_server.py"],
      "env": {
        "GITEA_PROFILE_NAME": "gitea-reviewer",
        "GITEA_ALLOWED_OPERATIONS": "read,review,approve"
      }
    },
    "gitea-tools-merger": {
      "command": "/Users/jasonwalker/Development/Gitea-Tools/venv/bin/python3",
      "args": ["/Users/jasonwalker/Development/Gitea-Tools/mcp_server.py"],
      "env": {
        "GITEA_PROFILE_NAME": "gitea-merger",
        "GITEA_ALLOWED_OPERATIONS": "read,merge"
      }
    }
  }
}

Recognized environment fields (see .env.example for placeholders):

Variable Purpose
GITEA_TOKEN API token for this runtime. Read only by the auth layer; never returned, logged, or committed.
GITEA_PROFILE_NAME Non-secret label for the running profile (e.g. gitea-reviewer). Surfaced by gitea_whoami.
GITEA_ALLOWED_OPERATIONS Optional, comma-separated operation categories (descriptive metadata only for now).
GITEA_FORBIDDEN_OPERATIONS Optional, comma-separated categories this profile must not perform (descriptive).
GITEA_AUDIT_LABEL Optional short label for this runtime, for audit purposes.
GITEA_TOKEN_SOURCE Optional name of the token source (e.g. an env var name). A name only — never the token value.
GITEA_BASE_URL Optional informational base URL.
GITEA_AUDIT_LOG Optional path to an audit log file. When set, mutating actions append one redacted JSON record each (profile + authenticated user + outcome). Unset ⇒ auditing off (no records, no extra API calls).
GITEA_MCP_CONFIG Optional path to a JSON file defining multiple named runtime profiles. Unset ⇒ pure env behaviour.
GITEA_MCP_PROFILE Name of the profile (from GITEA_MCP_CONFIG) to activate for this runtime.

Notes:

  • This provides one token + one profile per process. It does not implement multi-token switching inside a single runtime, nor any approve/merge/eligibility gating — those are later roadmap items (#14#18).
  • Profile name and allowed operations are metadata only; the token value is never part of any tool output. gitea_whoami returns the profile name, and gitea_get_profile returns the full non-secret profile metadata so a workflow can inspect which runtime it is talking to before deciding to act.
  • See docs/gitea-execution-profiles.md for the full profile model, and docs/llm-workflow-runbooks.md for the task-scoped, profile-based runbooks (create/review/merge/close, thin launchers, migration, fail-closed rules).
  • Audit logging (#18): mutating actions emit a durable, redacted JSON audit record — timestamp, action, result (allowed/blocked/failed/succeeded), profile name + audit label, authenticated username, target repo/issue/PR, branch and head SHA where applicable — when GITEA_AUDIT_LOG is set. Auditing is off by default and never adds API calls or breaks the action when off. See gitea_audit.py.

Canonical runtime profiles (#19). Define every Gitea profile once, in a canonical JSON file, and keep each LLM launcher (Claude / Gemini / Codex) a thin pointer at it — no duplicated GITEA_USER_* / GITEA_PASS_* blocks and no raw tokens in client configs. See gitea-mcp.example.json, loaded by gitea_config.py.

Canonical profile file (e.g. ~/.config/gitea-tools/profiles.json):

{
  "version": 1,
  "profiles": {
    "prgs": {
      "base_url": "https://gitea.prgs.cc",
      "username": "jcwalker3",
      "auth": { "type": "keychain", "id": "prgs-gitea-token" },
      "default_owner": "Scaled-Tech-Consulting",
      "execution_profile": "personal-prgs"
    },
    "mdcps": {
      "base_url": "https://gitea.dadeschools.net",
      "username": "913443",
      "auth": { "type": "env", "name": "GITEA_TOKEN_MDCPS" },
      "execution_profile": "mdcps"
    }
  }
}

Thin LLM launcher (Claude / Gemini / Codex) — only two env vars, no secrets:

"gitea-tools": {
  "command": "/Users/jasonwalker/Development/Gitea-Tools/venv/bin/python3",
  "args": ["/Users/jasonwalker/Development/Gitea-Tools/mcp_server.py"],
  "env": {
    "GITEA_MCP_CONFIG": "/Users/jasonwalker/.config/gitea-tools/profiles.json",
    "GITEA_MCP_PROFILE": "prgs"
  }
}
  • Secrets by reference only: a profile's auth names where the token lives — { "type": "keychain", "id": "..." } (macOS keychain) or { "type": "env", "name": "..." } (env var). Inline token/password keys are rejected. The value is resolved on demand and never stored in, returned by, or logged as profile metadata.
  • Precedence: explicit process env vars (GITEA_PROFILE_NAME, GITEA_BASE_URL, GITEA_TOKEN, …) override the JSON profile; the JSON profile only fills what the environment leaves unset.
  • Backwards compatible / fail-safe: with GITEA_MCP_CONFIG unset, behaviour is exactly the legacy env-only mode. A missing file, invalid JSON, unsupported version, unknown/unset selected profile, or unresolvable secret reference raises a clear startup error that never prints file contents, tokens, or passwords. Parsing makes no network calls.

Migrating from duplicated GITEA_PASS_* blocks. Move each instance's credentials into one canonical profile entry (referencing a keychain id or env var for the secret), then delete the GITEA_USER_* / GITEA_PASS_* / GITEA_SITE_* blocks from every LLM mcp_config.json, leaving only GITEA_MCP_CONFIG + GITEA_MCP_PROFILE. Existing env-only setups keep working unchanged until migrated.

Interactive setup — no hand-editing JSON. Run the menu to create/edit/ validate profiles, store a token in the macOS keychain (never echoed or written to any config), test a profile's authentication, print the authenticated user, check reviewer eligibility for a PR, and generate ready-to-paste launcher snippets for Claude / Gemini / Codex:

./scripts/gitea-config-menu

The generated launcher snippets contain only command, args, GITEA_MCP_CONFIG, and GITEA_MCP_PROFILE — never a token or password.

Codex / non-MCP tools

OpenAI Codex and other tools that don't support MCP can use the CLI scripts directly. See the CLI Scripts section below.

# Example: Codex can shell out to the scripts
python3 /Users/jasonwalker/Development/Gitea-Tools/create_issue.py \
  --remote prgs --title "Bug report" --body "Details here"

CLI Scripts

The MCP tools can also be used as standalone CLI scripts:

Script Description
create_issue.py Create an issue (--remote, --title, --body, --body-file)
create_pr.py Open a Pull Request (--remote, --title, --head, --base)
edit_pr.py Edit a Pull Request (--title, --body, --body-file, etc.)
review_pr.py Review/sign-off on a pull request (--merge is disabled — fails closed; merge only via gated gitea_merge_pr)
close_issue.py Close a specific issue
mark_issue.py Claim/release an issue via status:in-progress label
manage_labels.py Create label set and apply label mappings (--dry to preview)
mirror_refs.sh Mirror branches + tags between dadeschools ⇄ prgs

Quick Examples

# Create an issue
./create_issue.py --title "Fix PDF output" --body "Blank on Safari"

# Create an issue on the prgs instance
./create_issue.py --remote prgs --title "Add tests" --body-file description.md

# Create a PR
./create_pr.py --title "feat: add validation" --head feat/validation --body "Closes #12"

# Edit a PR's description or title
./edit_pr.py 155 --body "Updated description wording"

# Review and approve a PR (review only — CLI merge is disabled; use the
# gated gitea_merge_pr MCP workflow to merge)
./review_pr.py --pr-number 12 --event APPROVE --body "Approved"

# Close issue #5
./close_issue.py 5

# Claim an issue before working on it
./mark_issue.py 10 start

# Release when done
./mark_issue.py 10 done

# Mirror refs (dry-run by default)
./mirror_refs.sh

# Actually push the refs
./mirror_refs.sh --apply

Use --help on any Python script or shell script for full usage details.

Architecture

gitea_auth.py    ← shared auth & API helpers (get_credentials, api_request)
mcp_server.py    ← MCP server (FastMCP, stdio transport)
create_issue.py  ← CLI: create issues
create_pr.py     ← CLI: create PRs
edit_pr.py       ← CLI: edit PRs
review_pr.py     ← CLI: review PRs
manage_labels.py ← CLI: label management
close_issue.py   ← CLI: close issues
mark_issue.py    ← CLI: claim/release issues
mirror_refs.sh   ← CLI: ref mirroring

Tests

# Run with the venv (includes MCP SDK)
source venv/bin/activate
python3 -m pytest tests/ -v
Test file Covers
test_mcp_server.py All 7 MCP tools: create, list, view, close, mark, PR, mirror
test_create_issue.py CLI arg parsing, remote resolution, payload, auth, errors
test_create_pr.py CLI arg parsing, remote resolution, payload, auth, errors
test_credentials.py get_credentials(), get_auth_header(), repo_api_url()
test_manage_labels.py Label create/skip, dry run, mapping, constant validation
test_python_cli.py close_issue.py + mark_issue.py CLI validation
test_mirror_refs.py Flags, safety defaults, local integration tests

All tests mock network and keychain access — no real API calls are made.