11# go-gerrit-mcp
22
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.
3+ An MCP (Model Context Protocol) server that exposes Gerrit code review operations as capability-gated tools. An AI
4+ agent can search and read changes, publish review comments, vote, and drive change-state transitions. Every write
5+ capability is an explicit operator opt-in.
66
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 ) )
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 ) )
1010
1111## Safety posture
1212
1313Two defaults encode it:
1414
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 ` .
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 (comments,
17+ votes, state changes, anything other humans see) are refused on changes the authenticated account does not own,
18+ until the operator explicitly passes ` --own-changes-only=false ` .
1919
2020An agent leaving unwanted trail on colleagues' changes is an externally visible failure; a missing capability is a
2121locally discoverable inconvenience. The defaults are chosen accordingly. Widen deliberately:
@@ -27,7 +27,7 @@ go-gerrit-mcp --groups read,comment,transition --own-changes-only=false --projec
2727
2828## Install
2929
30- ** Binary release** — download the binary for your platform from
30+ Binary release: download the binary for your platform from
3131[ GitHub Releases] ( https://github.com/GaijinEntertainment/go-gerrit-mcp/releases ) , make it executable, and put it on
3232your ` PATH ` :
3333
@@ -38,21 +38,21 @@ curl -Lo /usr/local/bin/go-gerrit-mcp \
3838chmod +x /usr/local/bin/go-gerrit-mcp
3939```
4040
41- ** Docker:**
41+ Docker:
4242
4343``` sh
4444docker pull ghcr.io/gaijinentertainment/go-gerrit-mcp:latest
4545```
4646
47- ** go install:**
47+ go install:
4848
4949``` sh
5050go install dev.gaijin.team/go/go-gerrit-mcp/cmd/go-gerrit-mcp@latest
5151```
5252
5353## Quick start
5454
55- The server reads connection identity from environment variables only — credentials never travel through flags:
55+ The server reads connection identity from environment variables only; credentials never travel through flags:
5656
5757| Variable | Meaning |
5858| ----------------- | -------------------------------------------------- |
@@ -116,12 +116,12 @@ claude mcp add gerrit \
116116
117117## Per-project configuration in Claude Code
118118
119- Everything the server needs is read from the environment — the identity variables above plus a ` GERRIT_MCP_* `
120- mirror for every flag (see [ Configuration reference] ( #configuration-reference ) ) — and MCP server processes inherit
121- the session environment. In Claude Code that turns one user-level registration into per-project configuration,
122- down to different Gerrit instances with different credentials per repository.
119+ Everything the server needs is read from the environment: the identity variables above plus a ` GERRIT_MCP_* ` mirror
120+ for every flag (see [ Configuration reference] ( #configuration-reference ) ). MCP server processes inherit the session
121+ environment, so in Claude Code one user-level registration turns into per-project configuration, down to different
122+ Gerrit instances with different credentials per repository.
123123
124- ** ` ~/.claude.json ` ** — the registration; no ` env ` block:
124+ ` ~/.claude.json ` holds the registration, with no ` env ` block:
125125
126126``` json
127127{
@@ -133,7 +133,7 @@ down to different Gerrit instances with different credentials per repository.
133133}
134134```
135135
136- ** ` <project>/.claude/settings.local.json ` ** — the project's environment; every ` env ` entry reaches the server
136+ ` <project>/.claude/settings.local.json ` carries the project's environment; every ` env ` entry reaches the server
137137process:
138138
139139``` json
@@ -148,22 +148,22 @@ process:
148148}
149149```
150150
151- Values shared by most projects can sit one layer down in ` ~/.claude/settings.json ` — settings files merge with
152- ` .claude/settings.local.json ` over ` .claude/settings.json ` over ` ~/.claude/settings.json ` — so a project declares
153- only its deltas. Anything no layer sets falls back to the server's own defaults: read-only, own changes.
151+ Values shared by most projects can sit one layer down in ` ~/.claude/settings.json ` . Settings files merge, with
152+ ` .claude/settings.local.json ` over ` .claude/settings.json ` over ` ~/.claude/settings.json ` , so a project declares only
153+ its deltas. Anything no layer sets falls back to the server's own defaults: read-only, own changes.
154154
155- The registration's ` env ` block stays empty for a reason: a variable named there shadows every settings layer, and
156- references are not a way around that — they never resolve from settings files. ` ${VAR} ` expands only from the
157- shell environment that launched ` claude ` , and anything unresolved reaches the server as a literal string.
155+ The registration's ` env ` block stays empty for a reason. A variable named there shadows every settings layer, and
156+ references are no workaround: ` ${VAR} ` expands only from the shell environment that launched ` claude ` , never from
157+ settings files , and anything unresolved reaches the server as a literal string.
158158
159159Other MCP clients inherit their launch environment the same way, so per-directory tooling such as
160160[ direnv] ( https://direnv.net/ ) achieves the identical split without client support.
161161
162162## Capability groups
163163
164- Capability is selected at startup via ` --groups ` as a comma-separated list. Groups are independent and combinable —
165- there is no ladder; each write-capable group bundles the minimal change-read subset it needs to function on its own,
166- and enabled groups union.
164+ Capability is selected at startup via ` --groups ` as a comma-separated list. Groups are independent and combinable,
165+ with no privilege ladder: each write-capable group bundles the minimal change-read subset it needs to work on its
166+ own, and enabled groups union.
167167
168168| Group | Tools |
169169| ------------ | ---------------------------------------------------------------------------------------- |
@@ -173,63 +173,63 @@ and enabled groups union.
173173
174174### Tools
175175
176- - ` search_changes ` — query changes with Gerrit's change query syntax, paginated.
177- - ` get_change ` — one change in review-relevant detail: status, owner, labels with votes, current revision, messages.
178- - ` list_change_files ` — files touched by a revision, with per-file change stats.
179- - ` get_file_diff ` — the diff of one file in a revision.
180- - ` get_change_comments ` — comment threads on a change, with resolution state and comment ids. Returns unresolved
176+ - ` search_changes ` : query changes with Gerrit's change query syntax, paginated.
177+ - ` get_change ` : one change in review-relevant detail: status, owner, labels with votes, current revision, messages.
178+ - ` list_change_files ` : files touched by a revision, with per-file change stats.
179+ - ` get_file_diff ` : the diff of one file in a revision.
180+ - ` get_change_comments ` : comment threads on a change, with resolution state and comment ids. Returns unresolved
181181 threads only by default; ` status=all ` fetches the full history, ` status=resolved ` the settled threads.
182- - ` post_comments ` — publish a review in one call: optional top-level message plus inline, range, file-level, and
183- reply comments; replies anchor to comment ids from ` get_change_comments ` ; ` resolved ` toggles the thread state.
184- - ` set_vote ` — set a label vote (e.g. ` Code-Review ` ) with an optional message; value ` 0 ` clears an own vote.
185- - ` transition_change ` — move a change's state: ` submit ` , ` abandon ` , ` restore ` , ` wip ` , or ` ready ` , with an optional
186- message (submit accepts none). Gerrit's refusal — a blocked submit, a restore of a merged change — is reported
182+ - ` post_comments ` : publish a review in one call: optional top-level message plus inline, range, file-level, and
183+ reply comments. Replies anchor to comment ids from ` get_change_comments ` ; ` resolved ` toggles the thread state.
184+ - ` set_vote ` : set a label vote (e.g. ` Code-Review ` ) with an optional message; value ` 0 ` clears an own vote.
185+ - ` transition_change ` : move a change's state: ` submit ` , ` abandon ` , ` restore ` , ` wip ` , or ` ready ` , with an optional
186+ message (submit accepts none). Gerrit's refusal ( a blocked submit, a restore of a merged change) is reported
187187 verbatim.
188188
189189## Review notifications
190190
191- An opt-in push channel for review activity. The agent subscribes to a change ( ` subscribe_change ` ), and from then on
192- new change messages, votes, inline comment threads, and status transitions arrive in the session by themselves as
193- ` review_activity ` blocks — the same llmxml vocabulary the read tools emit, activity carried whole, so nothing needs
194- fetching afterwards. ` unsubscribe_change ` ends a subscription early; a merged or abandoned change ends its own with
195- a final notification saying so, and a change that becomes unreadable (deleted, or no longer visible to the account)
196- does the same naming the reason.
191+ An opt-in push channel for review activity. The agent subscribes to a change with ` subscribe_change ` ; from then on,
192+ new change messages, votes, inline comment threads, and status transitions arrive in the session on their own as
193+ ` review_activity ` blocks. The payload uses the same llmxml vocabulary the read tools emit and carries the activity
194+ whole, so nothing needs fetching afterwards. ` unsubscribe_change ` ends a subscription early. A merged or abandoned
195+ change ends its own subscription with a final notification that says so, and a change that becomes unreadable
196+ (deleted, or no longer visible to the account) does the same, with the reason spelled out .
197197
198198Subscriptions are per-session and in-memory: they leave no trace on the Gerrit instance, end with the session, and
199- after a server restart the agent subscribes again. With the feature off — the default — the server is byte-identical
200- to its pre-feature self: no extra tools, no extra capability, no background polling.
199+ after a server restart the agent subscribes again. With the feature off ( the default) the server is byte-identical
200+ to its pre-feature self, with no extra tools or capabilities and no background polling.
201201
202202Enabling takes both sides:
203203
204- 1 . ** Server** — ` --review-notifications=true ` (or its mirror). The server registers both subscription tools and
205- polls Gerrit every ` --review-notifications-poll-interval ` (default ` 60s ` ): one batched query per tick over all
206- subscribed changes, detail fetches only for changes that actually moved.
207- 2 . ** Client** — delivery rides the Claude Code ** channels** contract (research preview, Claude Code ≥ 2.1.80).
204+ 1 . Server side: pass ` --review-notifications=true ` (or its mirror). The server registers both subscription tools
205+ and polls Gerrit every ` --review-notifications-poll-interval ` (default ` 60s ` ): one batched query per tick over
206+ all subscribed changes, with detail fetches only for changes that actually moved.
207+ 2 . Client side: delivery uses the Claude Code channels contract (research preview, Claude Code 2.1.80 or newer ).
208208 For a server registered plainly under ` mcpServers ` , launch with
209- ` claude --dangerously-load-development-channels server:<name> ` , where ` <name> ` is the registration key — it
209+ ` claude --dangerously-load-development-channels server:<name> ` , where ` <name> ` is the registration key; it
210210 becomes the ` source ` attribute of the injected ` <channel> ` blocks. Allowlisted channel plugins load with
211211 ` claude --channels ` instead.
212212
213213Research-preview caveats: organization policy can disable channels entirely; the flag syntax may change between
214- Claude Code releases; and a client without channel support silently drops the events — the server then degrades to
215- exactly its pre- feature behavior , with no errors on either side.
214+ Claude Code releases; and a client without channel support silently drops the events, in which case the server
215+ behaves exactly as if the feature were off , with no errors on either side.
216216
217- Noise control is operator configuration, not server heuristics — nothing is filtered by message tag, because a CI
218- verdict is often exactly the awaited outcome:
217+ Noise control is operator configuration. The server applies no heuristics of its own and filters nothing by message
218+ tag, because a bot's verdict is often exactly the outcome the agent is waiting for :
219219
220220- the authenticated account's own activity is skipped by default (` --review-notifications-include-own ` keeps it);
221221- ` --review-notifications-exclude-accounts ` silences accounts by username or numeric ID;
222222- ` --review-notifications-exclude-patterns ` drops events whose message or comment text matches a regular
223- expression; an invalid pattern fails startup naming it.
223+ expression; an invalid pattern fails startup with an error naming it.
224224
225- Every flag has a ` GERRIT_MCP_* ` mirror riding the same settings layering as the rest of the configuration (see
226- [ Per-project configuration] ( #per-project-configuration-in-claude-code ) ), so a project can enable notifications and
227- pick its exclusions in its own settings file.
225+ Every flag has a ` GERRIT_MCP_* ` mirror that follows the same settings layering as the rest of the configuration
226+ (see [ Per-project configuration] ( #per-project-configuration-in-claude-code ) ), so a project can enable notifications
227+ and pick exclusions in its own settings file.
228228
229229## Configuration reference
230230
231231Behavior is configured by CLI flags, each mirrored by an environment variable so one configuration style works for
232- binary and Docker invocations alike. Precedence: ** flag wins over its mirror, the mirror wins over the default.**
232+ binary and Docker invocations alike. Precedence: the flag wins over its mirror, and the mirror wins over the default.
233233
234234| Flag | Mirror | Default | Meaning |
235235| -------------------- | ----------------------------- | ------- | -------------------------------------------------------------- |
@@ -248,18 +248,17 @@ Notes:
248248
249249- ` --own-changes-only ` takes an explicit boolean value: ` --own-changes-only=false ` . A bare flag without a value is a
250250 configuration error.
251- - ** Project scoping** (` --projects ` ) is enforced server-side: a project clause is injected into every change query
251+ - Project scoping (` --projects ` ) is enforced server-side: a project clause is injected into every change query
252252 regardless of what the agent composed, and direct operations on out-of-scope changes are refused.
253- - ** Tool filters** only narrow. ` --exclude-tools ` removes tools from what the groups resolved; ` --include-tools `
254- keeps only the listed subset of it. A tool outside the enabled groups can never be activated by a filter, and
255- exclude wins over include. Filter entries naming no known tool fail startup, so misconfigurations surface
256- immediately.
253+ - Tool filters only narrow. ` --exclude-tools ` removes tools from what the groups resolved; ` --include-tools ` keeps
254+ only the listed subset of it. A tool outside the enabled groups can never be activated by a filter, and exclude
255+ wins over include. Filter entries naming no known tool fail startup, so misconfigurations surface immediately.
257256- Configuration errors are aggregated: the server reports every problem at once, then exits non-zero.
258257
259258## Output format
260259
261- Every tool responds in ** llmxml** : an LLM-digestible subset of XML — line-structured, semantically tagged text meant
262- to be read by a model. Attributes carry metadata, element bodies carry content:
260+ Every tool responds in llmxml, an LLM-digestible subset of XML: line-structured, semantically tagged text meant to
261+ be read by a model. Attributes carry metadata; element bodies carry content:
263262
264263```
265264<changes query="status:open owner:self" start="0" count="1" more="false">
@@ -268,7 +267,7 @@ to be read by a model. Attributes carry metadata, element bodies carry content:
268267```
269268
270269There is no XML declaration, no namespaces, and no schema; nothing parses it back. If you need machine-readable
271- Gerrit data, use Gerrit's REST API directly — this format is for model consumption.
270+ Gerrit data, use Gerrit's REST API directly; this format is for model consumption.
272271
273272## License
274273
0 commit comments