This document serves as the comprehensive guide for developing, testing, and
maintaining the gerrit-mcp-server. It provides practical instructions for all
stages of the development lifecycle.
- Python 3.12+
uv(the build script will install it viapipif absent)
The project uses uv for dependency management. uv.lock is committed to the
repository to ensure reproducible installs.
-
Run the build script:
./build-gerrit.sh
This will create a virtual environment in
.venvand install all dependencies (including dev extras) viauv sync. -
Activate the virtual environment:
source .venv/bin/activate
[!IMPORTANT] ALWAYS use the virtual environment's Python. Never use the system Python.
We use pytest as our testing framework.
- Files:
test_*.py - Functions: Descriptive names like
test_query_changes_returns_results. - Pattern: Arrange, Act, Assert.
- Use
pytestfixtures for setup and dependency injection. - Place shared fixtures in
conftest.py. - Use
unittest.mock.patchas a context manager or fixture.
Run tests from the project root (no venv activation needed):
uv run pytestIf any changes to source are ever made, you must run uv run pytest to validate
that the changes did not break any tests. Ask the user first after any changes
are made.
Use the /astral:ruff skill
if available. Otherwise, to fix and format:
uv run ruff check --fix . && uv run ruff format .To check without modifying:
uv run ruff check . && uv run ruff format --check .
User-facing documentation lives in the docs/ directory. Keep it in sync with
code changes — docs are part of the deliverable, not an afterthought.
docs/configuration.md— thegerrit_config.jsonschema and all authentication methods.docs/available_tools.md— every MCP tool exposed by the server and what it does.docs/extensions.md— the extension hook: registering additional MCP tools viaregister(ctx), theExtensionContextAPI, andrequires_plugin.docs/testing.md— how to run unit, integration, and E2E tests.docs/best_practices.md— tips for using the server effectively.docs/use_cases.md— worked scenarios demonstrating the server.docs/gemini-cli.md— client setup for the Gemini CLI.docs/claude-code.md— client setup for Claude Code (plugin mode).docs/contributing.md/docs/code-of-conduct.md— contribution guidelines and community standards.
- Add or change a tool → update
docs/available_tools.md. - Change config structure or auth → update
docs/configuration.md. - Change how tests are run → update
docs/testing.md. - Change behavior users rely on → check
docs/best_practices.mdanddocs/use_cases.md. - Change the extension hook or
ExtensionContextAPI → updatedocs/extensions.md.
When you add, rename, or remove a file in docs/, cross-check the other
documentation files for references that need updating — in particular
README.md (which maintains its own index of doc links), AGENTS.md, and any
sibling docs that link to the changed file. Stale or broken cross-references
should be fixed in the same change.
Match the existing Markdown conventions: wrap prose at ~80 columns, use fenced
code blocks with language hints, and prefer > [!IMPORTANT]/> [!NOTE]
callouts as used elsewhere in this guide.
Enforce correct formatting by running mdformat before submitting:
uv run mdformat .To check without modifying: uv run mdformat --check .
The server is configured via gerrit_mcp_server/gerrit_config.json.
- Environment Variable:
GERRIT_CONFIG_PATHcan be used to point to a custom config file. - Structure:
See Configuration Guide for full details.
{ "gerrit_hosts": [ { "name": "MyGerrit", "external_url": "https://gerrit.example.com", "authentication": { "type": "http_basic", ... } } ] }
To configure the Gemini CLI to use this server, see the Gemini CLI Setup Guide.
To run the server locally for debugging (no venv activation needed):
uv run gerrit-mcp-server- Logs: The server outputs logs to stderr.
All tools should return a typed TypedDict rather than assembling
[{"type": "text", "text": ...}] content blocks manually. The MCP SDK
auto-generates an output_schema from the return annotation and produces both
structured and unstructured content automatically — structured for clients that
support it, text fallback for those that don't.
- Declare a
TypedDictfor the return type; useOptional[X](notNotRequired) for optional fields, and always include the key in every return path (set toNonewhen there is no value). - Return the dict directly from the function.
- Raise exceptions on error rather than returning text error content blocks.
[!IMPORTANT] Do not use
NotRequiredin tool-resultTypedDicts — it causes the MCP SDK to raise validation errors either when annotations are stringized (from __future__ import annotations) or when any return path omits the key (validated asNoneagainst the declared type). UseOptional[X]and set"key": Noneon every path that has no value.
from typing import List, Optional, TypedDict
class _ParentChange(TypedDict):
change_number: int
subject: str
work_in_progress: bool
class _MyToolResult(TypedDict):
change_id: str
items: List[_ParentChange]
note: Optional[str] # None unless there is something to say
@mcp.tool()
async def my_tool(
change_id: str,
gerrit_base_url: Optional[str] = None,
) -> _MyToolResult:
...
try:
raw = json.loads(await run_curl([url], base_url))
except json.JSONDecodeError as e:
raise ValueError(f"Failed to parse Gerrit response: {e}") from e
return {"change_id": change_id, "items": [...], "note": None}See get_commit_message in gerrit_mcp_server/main.py for a complete example.
This project uses Gerrit for code reviews. The primary development branch is
master.
- Create a new branch:
git checkout -b <your-feature-branch>
- Commit your changes:
git commit -m "Your descriptive commit message" - Push for review: Changes must be pushed to
refs/for/masterto create a CL. Direct pushes tomasterare not permitted.git push origin HEAD:refs/for/master