# 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: ```bash # 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 ``` ## MCP Server (Recommended) 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 "`, 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 ```bash 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"`: ```json "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: ```json { "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. ```json { "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`](.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). | 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`](docs/gitea-execution-profiles.md) for the full profile model. - **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`](gitea_audit.py).
Codex / non-MCP tools OpenAI Codex and other tools that don't support MCP can use the CLI scripts directly. See the [CLI Scripts](#cli-scripts) section below. ```bash # 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 ```bash # 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 ```bash # 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.