Skip to main content
Glama
JigeeshaJain

gh-review-queue-mcp

by JigeeshaJain

gh-review-queue-mcp

MCP-сервер, который отвечает на один вопрос: что мне ревьюить следующим?

Он предоставляет ровно один инструмент, get_review_queue, который возвращает ранжированное и дедуплицированное представление вашей очереди ревью пул-реквестов GitHub — ревью, запрошенные у вас, ревью, запрошенные у ваших команд, и ваши собственные пул-реквесты, ожидающие кого-то другого.

Один инструмент — это осознанное ограничение. Ассистент, которому приходится выбирать между list_prs, search_prs и get_pr_status, тратит свой первый ход на выбор; ассистент с одним инструментом, возвращающим уже приоритизированный список, может просто ответить.


Что это на самом деле делает

Когда инструмент вызывается, по порядку происходят четыре вещи.

1. Определение вас и ваших команд

Сервер отправляет GraphQL-запрос для viewer { login } плюс команды, в которых вы состоите (organizations.teams(role: MEMBER)). Слаги команд важны, потому что в поисковом API GitHub нет квалификатора «запрошено у любой из моих команд» — каждую команду нужно называть явно. Это единственная причина, по которой токену требуется область read:org.

2. Разветвление в один пакетный поиск

В GitHub нет единого запроса для «всего, что требует моего внимания», поэтому сервер выполняет несколько поисков и объединяет их. Все они отправляются в одном GraphQL-документе с использованием алиасов, так что это один HTTP-запрос, независимо от того, в скольких командах вы состоите:

Алиас

Поиск

Становится причиной

requested_of_me

is:pr is:open archived:false review-requested:@me

requested_of_me

my_pr_awaiting_review

is:pr is:open archived:false author:@me

my_pr_awaiting_review

team_0, team_1, …

is:pr is:open archived:false team-review-requested:<org>/<team>

requested_of_my_teams

Поисковые строки передаются как GraphQL-переменные и никогда не интерполируются в документ запроса, поэтому слаг команды не может изменить запрос.

Тот же запрос также запрашивает rateLimit { remaining resetAt }, так что каждый ответ может сообщить ваш оставшийся бюджет без дополнительного вызова.

Два замечания о форме ответа. search(type: ISSUE) в GitHub возвращает и issues, и пул-реквесты; поскольку набор полей — это инлайн-фрагмент на PullRequest, issues возвращаются как пустые узлы и отбрасываются при разборе. А statusCheckRollup читается из commits(last: 1) — состояние CI головного коммита, а не всей истории ветки.

3. Объединение, дедупликация, фильтрация, ранжирование

Один и тот же пул-реквест часто приходит из нескольких поисков — PR, где вы являетесь непосредственным ревьюером и где запрошена ваша команда, появляется в двух корзинах. Они дедуплицируются по node id GraphQL, а причины накапливаются в одной записи, так что ответ говорит «это здесь по двум причинам», а не перечисляет его дважды.

Затем применяются ваши фильтры, а то, что осталось, получает оценку и сортируется.

4. Сериализация

Ранжированный список возвращается в виде структурированного вывода — инструмент объявляет полную JSON-схему вывода, так что клиент получает типизированные поля, а не текст, который нужно разбирать.


Related MCP server: github-ops-mcp

Как работает ранжирование

Ранжирование многоуровневое, а не настраиваемое весами. Каждый пул-реквест попадает ровно на один уровень, и уровень значит намного больше, чем всё, что накапливается внутри него:

Уровень

Условие

База

3

Ваш собственный PR с упавшим CI

300

2

Ваш собственный PR с запрошенными изменениями

200

1

Ревью, запрошенное у вас напрямую

100

0

Запрос от команды или ваш PR, который просто ждёт

0

Внутри уровня действуют два меньших сигнала:

  • Возраст — 2 очка в день с момента открытия PR, максимум 20. Старые запросы на ревью всплывают, но шестимесячный PR не может доминировать вечно.

  • Небольшой диф — плоский бонус в 8 очков за дифы размером 100 строк или меньше, исходя из того, что небольшое ревью, которое можно закончить сейчас, лучше большого, которое вы отложите.

Ограничение — и есть суть. Максимум, что может накопиться внутри уровня, — 20 + 8 = 28, что значительно меньше шага уровня в 100, так что доминирование уровня выполняется по построению: новый прямой запрос всегда превосходит древний командный запрос, и никакая будущая подстройка весов не может это незаметно изменить. Если вы добавляете сигнал оценки, держите внутриуровневую сумму ниже 100, иначе эта гарантия нарушится.

Связи разрешаются по последней активности (updatedAt), так что активное обсуждение опережает зависшее при том же балле.

Каждый элемент несёт priority_reasons — человекочитаемые строки вроде ["мой PR, CI упал", "3 дня"], — чтобы ранжирование можно было объяснить, а не получить как необъяснимое число.


Установка

Требуются Python 3.11+ и uv.

git clone <this repo>
cd ReviewQueueMcp
uv sync

Токен

Сервер читает персональный токен доступа GitHub из GITHUB_TOKEN:

cp .env.example .env      # then edit it
export GITHUB_TOKEN=ghp_...

Требуемые области доступа:

  • repo — чтение пул-реквестов в частных репозиториях

  • read:org — чтение вашего членства в командах для поиска по командным запросам

Классический PAT — проще всего. Тонкозернистые токены работают, если выдано «Pull requests: read» плюс чтение участников организации. Создайте его на https://github.com/settings/tokens.

GITHUB_GRAPHQL_URL опционально переопределяет конечную точку для GitHub Enterprise Server.

Токен читается при каждом вызове инструмента, а не при запуске — сервер запускается без него и возвращает понятную ошибку при вызове, а не падает во время MCP-рукопожатия, где клиент увидел бы только разорванный канал.


Запуск

uv run gh-review-queue-mcp

Он говорит по MCP через stdio и ожидает клиента на другом конце; запущенный напрямую, он просто ждёт.

С MCP Inspector

npx @modelcontextprotocol/inspector uv --directory /absolute/path/to/ReviewQueueMcp run gh-review-queue-mcp

Откройте напечатанный URL, подключитесь, и инструмент появится в разделе Tools со своей сгенерированной входной схемой.

С Claude Desktop

Добавьте в claude_desktop_config.json — в macOS по пути ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "gh-review-queue": {
      "command": "uv",
      "args": [
        "--directory",
        "/absolute/path/to/ReviewQueueMcp",
        "run",
        "gh-review-queue-mcp"
      ],
      "env": {
        "GITHUB_TOKEN": "ghp_..."
      }
    }
  }
}

Пути должны быть абсолютными — Claude Desktop не запускает серверы из вашей оболочки, поэтому у него нет рабочего каталога или экспортированного окружения для наследования. После редактирования перезапустите Claude Desktop. Затем спросите его: «что мне сегодня ревьюить?»


Справочник инструмента

get_review_queue

Все аргументы необязательны.

Аргумент

Тип

По умолчанию

Значение

include

массив requested_of_me | requested_of_my_teams | my_pr_awaiting_review

все три

Какие причины включать. Элемент сохраняется, если включена любая из его причин.

exclude_drafts

логический

true

Отбрасывать черновики. Они исключаются, а не понижаются — черновик ещё нельзя ревьюить.

max_age_days

целое число

нет

Отбрасывать PR, открытые более этого количества дней назад. Включительно по границе.

repos

массив owner/name

нет

Ограничить этими репозиториями. Точное совпадение.

limit

целое число 1–100

25

Максимум возвращаемых элементов. total_matching по-прежнему сообщает полное количество.

Ответ:

{
  "viewer": "octocat",
  "generated_at": "2026-08-20T12:00:00Z",
  "returned": 5,
  "total_matching": 5,
  "rate_limit_remaining": 4712,
  "warnings": [],
  "items": [
    {
      "repository": "acme/payments-api",
      "number": 4830,
      "title": "Add idempotency keys",
      "url": "https://github.com/acme/payments-api/pull/4830",
      "author": "octocat",
      "reasons": ["my_pr_awaiting_review"],
      "priority_score": 306.0,
      "priority_reasons": ["my PR, CI failing", "3 days old"],
      "age_days": 3.0,
      "diff_size": 374,
      "changed_files": 12,
      "is_draft": false,
      "review_decision": "REVIEW_REQUIRED",
      "ci_status": "FAILURE"
    }
  ]
}

returned и total_matching различают «вот 25» и «их много» — без этого ограниченный ответ неотличим от полного.

warnings несёт частичные ошибки GraphQL. GitHub может вернуть полезные данные вместе с ошибками (одна организация нечитаема, один поиск падает); вместо того чтобы выбрасывать всю очередь, они понижаются до предупреждений, а остальные результаты возвращаются.


Архитектура

Четыре модуля в src/gh_review_queue/, и границы несут нагрузку:

server.py    MCP wiring. Parse arguments -> call client -> domain layer -> serialize.
   |         Deliberately thin; its docstring sets a ~120-line budget.
   v
github.py    The only module that touches the network. Builds GraphQL, handles HTTP
   |         and GraphQL errors, returns domain objects. Never ranks or filters.
   v
queue.py     Pure functions: merge -> apply_filters -> rank/score, via build_queue.
   |         Input is a snapshot and a clock. Nothing else.
   v
models.py    Frozen pydantic value objects. The only place GitHub's nested GraphQL
             shape is flattened. No network types.

Выигрыш — в queue.py: поскольку он принимает QueueSnapshot и datetime и больше ничего, каждое правило ранжирования тестируется на простых данных и без моков, сети и подмены часов. Именно поэтому проведено разделение и почему импорт httpx никогда не должен туда попадать.

Деградация вместо отказа

Неизвестные значения перечислений от GitHub — новый reviewDecision, новое состояние сводки CI — преобразуются в None, а не вызывают исключение. Состояние, добавленное на стороне GitHub, не должно ломать всю вашу очередь. Тот же инстинкт проходит через весь слой разбора: отсутствующие авторы становятся ghost (собственное соглашение GitHub для удалённых аккаунтов), результаты поиска, не являющиеся PR, отбрасываются, а отсутствующие временные метки — единственный действительно невосстановимый случай, который вызывает исключение.


Разработка

uv run pytest                       # all tests
uv run pytest tests/test_queue.py   # one file
uv run pytest -k "rank or score"    # by name
uv run ruff check .                 # lint
uv run ruff format .                # format
uv run mypy                         # typecheck (strict)

Запускайте mypy без аргументов — он берёт цели из [tool.mypy] files в pyproject.toml, поэтому передача пути проверяет меньше, чем задумано.

Подход к тестированию

Тесты работают на tests/fixtures/queue_response.json — одном захваченном GraphQL-ответе, созданном так, чтобы содержать неудобные случаи: PR, появляющийся в двух корзинах, черновик, очень устаревший PR, PR зрителя с упавшим CI и нулевая сводка статуса.

test_rank_orders_the_fixture_the_way_a_reviewer_would_read_it проверяет точные оценки относительно фиксированных часов. Это канарейка для изменений оценки — если она падает, решите, действительно ли новый порядок лучше, прежде чем обновлять числа.


Статус

Фаза

Объём

Состояние

1

Каркас, упаковка, инструментарий

готово

2

models.py, queue.py, доменные тесты

готово

3

github.py GraphQL-клиент, настоящий server.py

готово

4

Клиентские и серверные тесты

не начато

5

Документация

этот файл

Фаза 3 проверена в конце концов — реальное MCP-рукопожатие через stdio, обнаружение инструмента и вызов инструмента, — но tests/test_server.py по-прежнему заглушка. Пути ошибок клиента (401, 403, частичные сбои GraphQL, недостижимый хост) написаны, но ещё не покрыты автоматическими тестами.

F
license - not found
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

  • A Model Context Protocol (MCP) application for automated GitHub PR analysis and issue management.…

  • An MCP server that gives your AI access to the source code and docs of all public github repos

  • A MCP server built for developers enabling Git based project management with project and personal…

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/JigeeshaJain/ReviewQueueMcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server