38c96d5815
Add a read-only MCP tool that reports the active runtime execution profile so an LLM can inspect what the current process is configured to do before deciding whether to attempt an action later. - gitea_get_profile: returns profile_name, allowed/forbidden operation categories, audit_label, token_source_name (a NAME, never a value), base_url, remote, resolved server, and — optionally — the verified authenticated username. Identity resolution fails soft and marks identity_status (verified/unknown/unavailable/not_resolved); the profile config is always returned. Never mutates Gitea. - gitea_auth.get_profile(): extended with forbidden_operations, audit_label, token_source_name from env (non-secret metadata). - .env.example / README: document the new optional metadata vars and the discovery tool. - tests: metadata parsing, verified/unavailable/unknown identity paths, skip-identity, and secret-redaction. Read-only. No token exposure. No multi-token switching. No PR eligibility, review, or merge workflow. No Jenkins/Ops/GlitchTip/ Release/deploy behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
281 lines
10 KiB
Markdown
281 lines
10 KiB
Markdown
# 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` | Merge a pull request (merge, squash, or rebase) |
|
||
| `gitea_review_pr` | Submit a review on a pull request and optionally merge it |
|
||
| `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_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.
|
||
|
||
<details>
|
||
<summary><strong>Antigravity (Google)</strong></summary>
|
||
|
||
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"`).
|
||
</details>
|
||
|
||
<details>
|
||
<summary><strong>Claude Code (Anthropic)</strong></summary>
|
||
|
||
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.
|
||
</details>
|
||
|
||
<details>
|
||
<summary><strong>Any MCP-compatible client</strong></summary>
|
||
|
||
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.
|
||
</details>
|
||
|
||
<details>
|
||
<summary><strong>Runtime profiles (multiple env-configured entries)</strong></summary>
|
||
|
||
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. |
|
||
|
||
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.
|
||
</details>
|
||
|
||
<details>
|
||
<summary><strong>Codex / non-MCP tools</strong></summary>
|
||
|
||
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"
|
||
```
|
||
</details>
|
||
|
||
## 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 and sign-off on a pull request (with optional merge) |
|
||
| `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, then automatically merge it
|
||
./review_pr.py --pr-number 12 --event APPROVE --body "Approved" --merge
|
||
|
||
# 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.
|