|
| 1 | +# go-gerrit-mcp |
| 2 | + |
| 3 | +An MCP (Model Context Protocol) server exposing Gerrit code review operations as capability-gated tools. It lets an |
| 4 | +AI agent search and read changes, publish review comments, vote, and drive change-state transitions — with a safety |
| 5 | +posture that makes every write capability an explicit operator opt-in. |
| 6 | + |
| 7 | +- **Transport:** stdio |
| 8 | +- **Target platform:** Gerrit 3.13+, authenticated via HTTP credentials |
| 9 | +- **Output:** llmxml — semantically tagged text meant to be read by a model (see [Output format](#output-format)) |
| 10 | + |
| 11 | +## Safety posture |
| 12 | + |
| 13 | +Two defaults encode it: |
| 14 | + |
| 15 | +- **Zero configuration exposes the `read` group only.** Write capability never appears unless enabled via `--groups`. |
| 16 | +- **The own-changes restriction is on by default.** Even with write groups enabled, trail-leaving operations — |
| 17 | + comments, votes, state changes, anything other humans see — are refused on changes the authenticated account does |
| 18 | + not own, until the operator explicitly passes `--own-changes-only=false`. |
| 19 | + |
| 20 | +An agent leaving unwanted trail on colleagues' changes is an externally visible failure; a missing capability is a |
| 21 | +locally discoverable inconvenience. The defaults are chosen accordingly. Widen deliberately: |
| 22 | + |
| 23 | +```sh |
| 24 | +# agent may comment and vote on anyone's changes, but only within two projects |
| 25 | +go-gerrit-mcp --groups read,comment,transition --own-changes-only=false --projects core,infra |
| 26 | +``` |
| 27 | + |
| 28 | +## Install |
| 29 | + |
| 30 | +**Binary release** — download the archive for your platform from |
| 31 | +[GitHub Releases](https://github.com/GaijinEntertainment/go-gerrit-mcp/releases), unpack, and put `go-gerrit-mcp` on |
| 32 | +your `PATH`. |
| 33 | + |
| 34 | +**Docker:** |
| 35 | + |
| 36 | +```sh |
| 37 | +docker pull ghcr.io/gaijinentertainment/go-gerrit-mcp:latest |
| 38 | +``` |
| 39 | + |
| 40 | +**go install:** |
| 41 | + |
| 42 | +```sh |
| 43 | +go install dev.gaijin.team/go/go-gerrit-mcp/cmd/go-gerrit-mcp@latest |
| 44 | +``` |
| 45 | + |
| 46 | +## Quick start |
| 47 | + |
| 48 | +The server reads connection identity from environment variables only — credentials never travel through flags: |
| 49 | + |
| 50 | +| Variable | Meaning | |
| 51 | +| ----------------- | -------------------------------------------------- | |
| 52 | +| `GERRIT_URL` | Base URL of the Gerrit instance | |
| 53 | +| `GERRIT_USERNAME` | Account username for HTTP Basic authentication | |
| 54 | +| `GERRIT_TOKEN` | HTTP credential paired with the username | |
| 55 | + |
| 56 | +Generate the credential in Gerrit under **Settings → HTTP Credentials** (an HTTP password, or an auth token on |
| 57 | +instances that issue them). All three variables are required; the server exits with an error naming the missing ones. |
| 58 | + |
| 59 | +### Claude Code |
| 60 | + |
| 61 | +```sh |
| 62 | +claude mcp add gerrit \ |
| 63 | + --env GERRIT_URL=https://gerrit.example.com \ |
| 64 | + --env GERRIT_USERNAME=your-username \ |
| 65 | + --env GERRIT_TOKEN=your-http-credential \ |
| 66 | + -- go-gerrit-mcp |
| 67 | +``` |
| 68 | + |
| 69 | +### Any MCP client (JSON configuration) |
| 70 | + |
| 71 | +```json |
| 72 | +{ |
| 73 | + "mcpServers": { |
| 74 | + "gerrit": { |
| 75 | + "command": "go-gerrit-mcp", |
| 76 | + "args": ["--groups", "read"], |
| 77 | + "env": { |
| 78 | + "GERRIT_URL": "https://gerrit.example.com", |
| 79 | + "GERRIT_USERNAME": "your-username", |
| 80 | + "GERRIT_TOKEN": "your-http-credential" |
| 81 | + } |
| 82 | + } |
| 83 | + } |
| 84 | +} |
| 85 | +``` |
| 86 | + |
| 87 | +### Docker |
| 88 | + |
| 89 | +```json |
| 90 | +{ |
| 91 | + "mcpServers": { |
| 92 | + "gerrit": { |
| 93 | + "command": "docker", |
| 94 | + "args": [ |
| 95 | + "run", "-i", "--rm", |
| 96 | + "-e", "GERRIT_URL", "-e", "GERRIT_USERNAME", "-e", "GERRIT_TOKEN", |
| 97 | + "ghcr.io/gaijinentertainment/go-gerrit-mcp:latest", |
| 98 | + "--groups", "read" |
| 99 | + ], |
| 100 | + "env": { |
| 101 | + "GERRIT_URL": "https://gerrit.example.com", |
| 102 | + "GERRIT_USERNAME": "your-username", |
| 103 | + "GERRIT_TOKEN": "your-http-credential" |
| 104 | + } |
| 105 | + } |
| 106 | + } |
| 107 | +} |
| 108 | +``` |
| 109 | + |
| 110 | +## Capability groups |
| 111 | + |
| 112 | +Capability is selected at startup via `--groups` as a comma-separated list. Groups are independent and combinable — |
| 113 | +there is no ladder; each write-capable group bundles the minimal change-read subset it needs to function on its own, |
| 114 | +and enabled groups union. |
| 115 | + |
| 116 | +| Group | Tools | |
| 117 | +| ------------ | ---------------------------------------------------------------------------------------- | |
| 118 | +| `read` | `search_changes`, `get_change`, `list_change_files`, `get_file_diff`, `get_change_comments` | |
| 119 | +| `comment` | `get_change`, `get_change_comments`, `post_comments` | |
| 120 | +| `transition` | `get_change`, `set_vote`, `transition_change` | |
| 121 | + |
| 122 | +### Tools |
| 123 | + |
| 124 | +- `search_changes` — query changes with Gerrit's change query syntax, paginated. |
| 125 | +- `get_change` — one change in review-relevant detail: status, owner, labels with votes, current revision, messages. |
| 126 | +- `list_change_files` — files touched by a revision, with per-file change stats. |
| 127 | +- `get_file_diff` — the diff of one file in a revision. |
| 128 | +- `get_change_comments` — comment threads on a change, with resolution state and comment ids. |
| 129 | +- `post_comments` — publish a review in one call: optional top-level message plus inline, range, file-level, and |
| 130 | + reply comments; replies anchor to comment ids from `get_change_comments`; `resolved` toggles the thread state. |
| 131 | +- `set_vote` — set a label vote (e.g. `Code-Review`) with an optional message; value `0` clears an own vote. |
| 132 | +- `transition_change` — move a change's state: `submit`, `abandon`, `restore`, `wip`, or `ready`, with an optional |
| 133 | + message (submit accepts none). Gerrit's refusal — a blocked submit, a restore of a merged change — is reported |
| 134 | + verbatim. |
| 135 | + |
| 136 | +## Configuration reference |
| 137 | + |
| 138 | +Behavior is configured by CLI flags, each mirrored by an environment variable so one configuration style works for |
| 139 | +binary and Docker invocations alike. Precedence: **flag wins over its mirror, the mirror wins over the default.** |
| 140 | + |
| 141 | +| Flag | Mirror | Default | Meaning | |
| 142 | +| -------------------- | ----------------------------- | ------- | -------------------------------------------------------------- | |
| 143 | +| `--groups` | `GERRIT_MCP_GROUPS` | `read` | Comma-separated capability groups: `read`, `comment`, `transition` | |
| 144 | +| `--projects` | `GERRIT_MCP_PROJECTS` | (empty) | Project allowlist confining every operation, reads included | |
| 145 | +| `--own-changes-only` | `GERRIT_MCP_OWN_CHANGES_ONLY` | `true` | Refuse trail-leaving operations on changes not owned by the authenticated account | |
| 146 | +| `--include-tools` | `GERRIT_MCP_INCLUDE_TOOLS` | (empty) | Keep only the listed tools from the group-resolved set | |
| 147 | +| `--exclude-tools` | `GERRIT_MCP_EXCLUDE_TOOLS` | (empty) | Remove the listed tools from the group-resolved set | |
| 148 | + |
| 149 | +Notes: |
| 150 | + |
| 151 | +- `--own-changes-only` takes an explicit boolean value: `--own-changes-only=false`. A bare flag without a value is a |
| 152 | + configuration error. |
| 153 | +- **Project scoping** (`--projects`) is enforced server-side: a project clause is injected into every change query |
| 154 | + regardless of what the agent composed, and direct operations on out-of-scope changes are refused. |
| 155 | +- **Tool filters** only narrow. `--exclude-tools` removes tools from what the groups resolved; `--include-tools` |
| 156 | + keeps only the listed subset of it. A tool outside the enabled groups can never be activated by a filter, and |
| 157 | + exclude wins over include. Filter entries naming no known tool fail startup, so misconfigurations surface |
| 158 | + immediately. |
| 159 | +- Configuration errors are aggregated: the server reports every problem at once, then exits non-zero. |
| 160 | + |
| 161 | +## Output format |
| 162 | + |
| 163 | +Every tool responds in **llmxml**: an LLM-digestible subset of XML — line-structured, semantically tagged text meant |
| 164 | +to be read by a model. Attributes carry metadata, element bodies carry content: |
| 165 | + |
| 166 | +``` |
| 167 | +<changes query="status:open owner:self" start="0" count="1" more="false"> |
| 168 | +<change number="12345" project="core" branch="main" status="NEW" owner="Jane Doe (jdoe)" updated="2026-07-10T20:48:32Z">fix scanner initialization</change> |
| 169 | +</changes> |
| 170 | +``` |
| 171 | + |
| 172 | +There is no XML declaration, no namespaces, and no schema; nothing parses it back. If you need machine-readable |
| 173 | +Gerrit data, use Gerrit's REST API directly — this format is for model consumption. |
| 174 | + |
| 175 | +## License |
| 176 | + |
| 177 | +[MIT](LICENSE) |
0 commit comments