From df5e2c428e27fbe087521b686c5b0e15c2a09952 Mon Sep 17 00:00:00 2001 From: Joo-young Choe Date: Wed, 7 Jan 2026 11:15:45 +0900 Subject: [PATCH] fix: Use /a/ prefix for authenticated REST API endpoints When using http_basic authentication with standard Gerrit instances, the REST API requires the /a/ prefix for authenticated access. Without this prefix: - /changes/ endpoint returns anonymous/public data only - Credentials sent via --user are silently ignored With this fix: - /a/changes/ endpoint properly authenticates requests - All http_basic auth configurations now work correctly This issue was found when using the MCP server with a self-hosted Gerrit instance using http_basic authentication. Queries like "owner:self" failed with "Must be signed-in to use this operator" despite valid credentials being configured. Reference: https://gerrit-review.googlesource.com/Documentation/rest-api.html#authentication --- gerrit_mcp_server/main.py | 40 +++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/gerrit_mcp_server/main.py b/gerrit_mcp_server/main.py index 618875b..ec75949 100644 --- a/gerrit_mcp_server/main.py +++ b/gerrit_mcp_server/main.py @@ -240,7 +240,7 @@ async def query_changes( config = load_gerrit_config() gerrit_hosts = config.get("gerrit_hosts", []) base_url = _normalize_gerrit_url(_get_gerrit_base_url(gerrit_base_url), gerrit_hosts) - url = f"{base_url}/changes/?q={quote(query)}" + url = f"{base_url}/a/changes/?q={quote(query)}" if limit: url += f"&n={limit}" if options: @@ -352,7 +352,7 @@ async def get_change_details( options = base_options query_params = "&".join([f"o={option}" for option in options]) - url = f"{base_url}/changes/{change_id}/detail?{query_params}" + url = f"{base_url}/a/changes/{change_id}/detail?{query_params}" result_json_str = await run_curl([url], base_url) details = json.loads(result_json_str) @@ -411,7 +411,7 @@ async def get_commit_message( config = load_gerrit_config() gerrit_hosts = config.get("gerrit_hosts", []) base_url = _normalize_gerrit_url(_get_gerrit_base_url(gerrit_base_url), gerrit_hosts) - url = f"{base_url}/changes/{change_id}/message" + url = f"{base_url}/a/changes/{change_id}/message" try: result_str = await run_curl([url], base_url) @@ -461,12 +461,12 @@ async def list_change_files( config = load_gerrit_config() gerrit_hosts = config.get("gerrit_hosts", []) base_url = _normalize_gerrit_url(_get_gerrit_base_url(gerrit_base_url), gerrit_hosts) - url = f"{base_url}/changes/{change_id}/revisions/current/files/" + url = f"{base_url}/a/changes/{change_id}/revisions/current/files/" result_json_str = await run_curl([url], base_url) files = json.loads(result_json_str) # We need the revision number for the patch set - detail_url = f"{base_url}/changes/{change_id}/detail" + detail_url = f"{base_url}/a/changes/{change_id}/detail" detail_json_str = await run_curl([detail_url], base_url) details = json.loads(detail_json_str) patch_set = details.get("current_revision_number", "current") @@ -495,7 +495,7 @@ async def get_file_diff( gerrit_hosts = config.get("gerrit_hosts", []) base_url = _normalize_gerrit_url(_get_gerrit_base_url(gerrit_base_url), gerrit_hosts) encoded_file_path = quote(file_path, safe="") - url = f"{base_url}/changes/{change_id}/revisions/current/patch?path={encoded_file_path}" + url = f"{base_url}/a/changes/{change_id}/revisions/current/patch?path={encoded_file_path}" diff_base64 = await run_curl([url], base_url) # The response is a base64 encoded string, we need to decode it. @@ -514,7 +514,7 @@ async def list_change_comments( config = load_gerrit_config() gerrit_hosts = config.get("gerrit_hosts", []) base_url = _normalize_gerrit_url(_get_gerrit_base_url(gerrit_base_url), gerrit_hosts) - url = f"{base_url}/changes/{change_id}/comments" + url = f"{base_url}/a/changes/{change_id}/comments" result_json_str = await run_curl([url], base_url) try: comments_by_file = json.loads(result_json_str) @@ -567,7 +567,7 @@ async def add_reviewer( config = load_gerrit_config() gerrit_hosts = config.get("gerrit_hosts", []) base_url = _normalize_gerrit_url(_get_gerrit_base_url(gerrit_base_url), gerrit_hosts) - url = f"{base_url}/changes/{change_id}/reviewers" + url = f"{base_url}/a/changes/{change_id}/reviewers" payload = {"reviewer": reviewer, "state": state} args = _create_post_args(url, payload) @@ -617,7 +617,7 @@ async def set_ready_for_review( config = load_gerrit_config() gerrit_hosts = config.get("gerrit_hosts", []) base_url = _normalize_gerrit_url(_get_gerrit_base_url(gerrit_base_url), gerrit_hosts) - url = f"{base_url}/changes/{change_id}/ready" + url = f"{base_url}/a/changes/{change_id}/ready" args = _create_post_args(url) try: @@ -650,7 +650,7 @@ async def set_work_in_progress( config = load_gerrit_config() gerrit_hosts = config.get("gerrit_hosts", []) base_url = _normalize_gerrit_url(_get_gerrit_base_url(gerrit_base_url), gerrit_hosts) - url = f"{base_url}/changes/{change_id}/wip" + url = f"{base_url}/a/changes/{change_id}/wip" payload = {"message": message} if message else None args = _create_post_args(url, payload) @@ -684,7 +684,7 @@ async def revert_change( config = load_gerrit_config() gerrit_hosts = config.get("gerrit_hosts", []) base_url = _normalize_gerrit_url(_get_gerrit_base_url(gerrit_base_url), gerrit_hosts) - url = f"{base_url}/changes/{change_id}/revert" + url = f"{base_url}/a/changes/{change_id}/revert" payload = {"message": message} if message else None args = _create_post_args(url, payload) @@ -730,7 +730,7 @@ async def revert_submission( config = load_gerrit_config() gerrit_hosts = config.get("gerrit_hosts", []) base_url = _normalize_gerrit_url(_get_gerrit_base_url(gerrit_base_url), gerrit_hosts) - url = f"{base_url}/changes/{change_id}/revert_submission" + url = f"{base_url}/a/changes/{change_id}/revert_submission" payload = {"message": message} if message else None args = _create_post_args(url, payload) @@ -780,7 +780,7 @@ async def create_change( config = load_gerrit_config() gerrit_hosts = config.get("gerrit_hosts", []) base_url = _normalize_gerrit_url(_get_gerrit_base_url(gerrit_base_url), gerrit_hosts) - url = f"{base_url}/changes/" + url = f"{base_url}/a/changes/" payload = { "project": project, @@ -851,7 +851,7 @@ async def set_topic( config = load_gerrit_config() gerrit_hosts = config.get("gerrit_hosts", []) base_url = _normalize_gerrit_url(_get_gerrit_base_url(gerrit_base_url), gerrit_hosts) - url = f"{base_url}/changes/{change_id}/topic" + url = f"{base_url}/a/changes/{change_id}/topic" payload = json.dumps({"topic": topic}) args = ["-X", "PUT", "-H", "Content-Type: application/json", "--data", payload, url] @@ -916,7 +916,7 @@ async def changes_submitted_together( config = load_gerrit_config() gerrit_hosts = config.get("gerrit_hosts", []) base_url = _normalize_gerrit_url(_get_gerrit_base_url(gerrit_base_url), gerrit_hosts) - url = f"{base_url}/changes/{change_id}/submitted_together" + url = f"{base_url}/a/changes/{change_id}/submitted_together" if options: query_params = "&".join([f"o={option}" for option in options]) @@ -985,7 +985,7 @@ async def suggest_reviewers( config = load_gerrit_config() gerrit_hosts = config.get("gerrit_hosts", []) base_url = _normalize_gerrit_url(_get_gerrit_base_url(gerrit_base_url), gerrit_hosts) - url = f"{base_url}/changes/{change_id}/suggest_reviewers?q={quote(query)}" + url = f"{base_url}/a/changes/{change_id}/suggest_reviewers?q={quote(query)}" if limit: url += f"&n={limit}" @@ -1042,7 +1042,7 @@ async def abandon_change( config = load_gerrit_config() gerrit_hosts = config.get("gerrit_hosts", []) base_url = _normalize_gerrit_url(_get_gerrit_base_url(gerrit_base_url), gerrit_hosts) - url = f"{base_url}/changes/{change_id}/abandon" + url = f"{base_url}/a/changes/{change_id}/abandon" payload = {"message": message} if message else None args = _create_post_args(url, payload) @@ -1088,7 +1088,7 @@ async def get_most_recent_cl( gerrit_hosts = config.get("gerrit_hosts", []) base_url = _normalize_gerrit_url(_get_gerrit_base_url(gerrit_base_url), gerrit_hosts) query = f"owner:{user}" - url = f"{base_url}/changes/?q={quote(query)}&n=1" + url = f"{base_url}/a/changes/?q={quote(query)}&n=1" result_json_str = await run_curl([url], base_url) changes = json.loads(result_json_str) @@ -1113,7 +1113,7 @@ async def get_bugs_from_cl( config = load_gerrit_config() gerrit_hosts = config.get("gerrit_hosts", []) base_url = _normalize_gerrit_url(_get_gerrit_base_url(gerrit_base_url), gerrit_hosts) - url = f"{base_url}/changes/{change_id}/revisions/current/commit" + url = f"{base_url}/a/changes/{change_id}/revisions/current/commit" result_json_str = await run_curl([url], base_url) if not result_json_str: return [ @@ -1163,7 +1163,7 @@ async def post_review_comment( config = load_gerrit_config() gerrit_hosts = config.get("gerrit_hosts", []) base_url = _normalize_gerrit_url(_get_gerrit_base_url(gerrit_base_url), gerrit_hosts) - url = f"{base_url}/changes/{change_id}/revisions/current/review" + url = f"{base_url}/a/changes/{change_id}/revisions/current/review" payload = { "comments": {