Skip to main content
Glama
JigeeshaJain

gh-review-queue-mcp

gh-review-queue-mcp

M8ven Score

An MCP server that answers one question: what should I review next?

It exposes exactly one tool, get_review_queue, which returns a ranked, deduplicated view of your GitHub pull request review queue — reviews requested of you, reviews requested of your teams, and your own pull requests that are waiting on someone else.

One tool is a deliberate constraint. An assistant that has to pick between list_prs, search_prs, and get_pr_status spends its first turn choosing; an assistant with one tool that returns an already-prioritized list can just answer.


What it actually does

When the tool is called, four things happen in order.

1. Identify you and your teams

The server issues a GraphQL query for viewer { login } plus the teams you belong to (organizations.teams(role: MEMBER)). The team slugs matter because GitHub's search API has no "requested of any of my teams" qualifier — you have to name each team explicitly. This is the only reason the token needs the read:org scope.

GitHub has no single query for "everything needing my attention", so the server runs several searches and combines them. All of them go out in one GraphQL document using aliases, so it is one HTTP round trip regardless of how many teams you're on:

Alias

Search

Becomes reason

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

Search strings are passed as GraphQL variables, never interpolated into the query document, so a team slug can't reshape the query.

The same query also asks for rateLimit { remaining resetAt }, so every response can report your remaining budget without a second call.

Two notes on the response shape. GitHub's search(type: ISSUE) returns issues as well as pull requests; because the selection set is an inline fragment on PullRequest, issues come back as empty nodes and are dropped during parsing. And statusCheckRollup is read from commits(last: 1) — the CI state of the head commit, not the whole branch history.

3. Merge, dedupe, filter, rank

The same pull request routinely comes back from several searches — a PR where you're a direct reviewer and your team is requested appears in two buckets. They're deduplicated on GraphQL node id, and the reasons accumulate onto one entry, so the response says "this is here for two reasons" instead of listing it twice.

Then your filters are applied, and what survives is scored and sorted.

4. Serialize

The ranked list comes back as structured output — the tool declares a full JSON output schema, so a client gets typed fields, not prose it has to parse.


Related MCP server: github-ops-mcp

How ranking works

Ranking is tiered, not weight-tuned. Each pull request lands in exactly one tier, and the tier is worth vastly more than anything that accumulates inside one:

Tier

Condition

Base

3

Your own PR with failing CI

300

2

Your own PR with changes requested

200

1

A review requested of you directly

100

0

A team request, or your own PR that's simply waiting

0

Within a tier, two smaller signals apply:

  • Age — 2 points per day since the PR was opened, capped at 20. Old review requests surface, but a six-month-old PR can't dominate forever.

  • Small diff — a flat 8-point bonus for diffs of 100 lines or fewer, on the theory that a small review you can finish now beats a large one you'll defer.

The cap is the whole point. The most anything can accumulate inside a tier is 20 + 8 = 28, well under the tier step of 100, so tier dominance holds by construction: a brand-new direct request always outranks an ancient team request, and no future weight tuning can silently flip that. If you add a scoring signal, keep the within-tier total under 100 or that guarantee breaks.

Ties break on most recent activity (updatedAt), so an active discussion outranks a stalled one at the same score.

Every item carries priority_reasons — human-readable strings like ["my PR, CI failing", "3 days old"] — so the ranking can be explained back to you instead of arriving as an unexplained number.


Installation

Requires Python 3.11+ and uv.

git clone <this repo>
cd ReviewQueueMcp
uv sync

Token

The server reads a GitHub personal access token from GITHUB_TOKEN:

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

Scopes needed:

  • repo — read pull requests in private repositories

  • read:org — read your team memberships, for the team-review-requested searches

A classic PAT is simplest. Fine-grained tokens work if granted "Pull requests: read" plus organization member read. Create one at https://github.com/settings/tokens.

GITHUB_GRAPHQL_URL optionally overrides the endpoint for GitHub Enterprise Server.

The token is read per tool call, not at startup — the server starts cleanly without one and returns an actionable error when called, rather than dying during the MCP handshake where the client would only see a broken pipe.


Running it

uv run gh-review-queue-mcp

It speaks MCP over stdio and expects a client on the other end; run directly, it just waits.

With MCP Inspector

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

Open the printed URL, connect, and the tool appears under Tools with its generated input schema.

With Claude Desktop

Add to claude_desktop_config.json — on macOS at ~/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_..."
      }
    }
  }
}

Paths must be absolute — Claude Desktop doesn't launch servers from your shell, so it has no working directory or exported environment to inherit. Restart Claude Desktop after editing. Then ask it "what should I review today?"


Tool reference

get_review_queue

All arguments are optional.

Argument

Type

Default

Meaning

include

array of requested_of_me | requested_of_my_teams | my_pr_awaiting_review

all three

Which reasons to include. An item survives if any of its reasons is included.

exclude_drafts

boolean

true

Drop drafts. They're excluded, not demoted — a draft isn't reviewable yet.

max_age_days

integer

none

Drop PRs opened more than this many days ago. Inclusive at the boundary.

repos

array of owner/name

none

Restrict to these repositories. Exact match.

limit

integer 1–100

25

Maximum items returned. total_matching still reports the full count.

Response:

{
  "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 vs total_matching distinguishes "here are 25" from "there are numerous" — without it, a limited response is indistinguishable from a complete one.

warnings carries GraphQL partial failures. GitHub can return usable data alongside errors (one org unreadable, one search failing); rather than throwing away the whole queue, those degrade to warnings and the rest of the results still come back.


Architecture

Four modules under src/gh_review_queue/, and the boundaries are load-bearing:

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.

The payoff is queue.py: because it takes a QueueSnapshot and a datetime and nothing else, every ranking rule is tested with plain data and no mocks, no network, and no clock patching. That's the reason for the split, and why an httpx import must never reach it.

Degrading instead of failing

Unknown enum values from GitHub — a new reviewDecision, a new CI rollup state — are mapped to None rather than raising. A state added on GitHub's side should never break your whole queue. The same instinct runs through the parsing layer: missing authors become ghost (GitHub's own convention for deleted accounts), non-PR search results are dropped, and absent timestamps are the one genuinely unrecoverable case that does raise.


Development

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)

Run mypy bare — it takes its targets from [tool.mypy] files in pyproject.toml, so passing a path checks less than intended.

Testing approach

Tests run off tests/fixtures/queue_response.json, one captured GraphQL response built to contain the awkward cases: a PR that appears in two buckets, a draft, a very stale PR, a failing-CI PR of the viewer's, and a null status rollup.

test_rank_orders_the_fixture_the_way_a_reviewer_would_read_it asserts exact scores against a fixed clock. It's the canary for scoring changes — if it fails, decide whether the new ordering is genuinely better before updating the numbers.


Status

Phase

Scope

State

1

Scaffold, packaging, tooling

done

2

models.py, queue.py, domain tests

done

3

github.py GraphQL client, real server.py

done

4

Client and server tests

not started

5

Documentation

this file

Phase 3 is verified end to end — a real MCP stdio handshake, tool discovery, and a tool call — but tests/test_server.py is still a placeholder. The client's error paths (401, 403, partial GraphQL failures, unreachable host) are written but not yet covered by automated tests.

License

This project is licensed under the Apache License 2.0. See the LICENSE file for details.

Available Tools

1 tool
get_review_queueA

Return the viewer's GitHub pull request review queue, ranked by what needs attention first: their own pull requests with failing CI, then their own with changes requested, then reviews requested of them directly, then reviews requested of their teams. Within a tier, older and smaller pull requests rank higher. Every item carries priority_reasons explaining its position, and total_matching reports how many matched before the limit.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMaximum items to return.
reposNoRestrict to these repositories, as 'owner/name'.
includeNoWhich reasons to include. Defaults to all three.
max_age_daysNoDrop pull requests opened more than this many days ago.
exclude_draftsNoDrop draft pull requests. Defaults to true.

Output Schema

ParametersJSON Schema
NameRequiredDescription
itemsYes
viewerYes
returnedYes
warningsNo
generated_atYes
total_matchingYes
rate_limit_remainingNo

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the burden of disclosure. It reveals the ranking tiers, tie-breaking rules, and the fact that results include priority_reasons and total_matching. It does not discuss auth, errors, or side effects, but the operation is clearly read-oriented and described in useful detail.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core purpose and ranking intent, then economically conveys the tier order and output signals in two structurally clear runs. Every clause earns its place and no filler exists.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description is complete enough for reliable invocation. It covers behavior, output information, ordering, and scoping semantics, the output schema and full parameter documentation handle the remaining return-value details, and there are no required parameters or sibling tools to complicate selection.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description does not elaborate on the individual parameters such as limit, repos, include, max_age_days, or exclude_drafts, but it does not need to because those parameters are already well-documented in the input schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: "Return the viewer's GitHub pull request review queue," and goes further by specifying the exact ranking logic. It is immediately clear what this tool does and how it differs from a generic list-pull-requests tool.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There are no siblings to contrast against, so the explicit when/when-not language is less necessary. The description makes the intended use clear: retrieve a prioritized review queue with tiered attention ordering, which is sufficient context for an agent to select it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. 1 tool updatev0.1.0
    • First observedget_review_queue

TDQS

A4.4/5.0
Disambiguation5/5

The set contains only one tool, so there is no possibility of overlap or selecting the wrong tool. Its purpose is clearly and specifically described.

Naming Consistency5/5

The single tool name follows the conventional verb_noun pattern with a clear action and resource. There are no other tool names to create inconsistency.

Tool Count4/5

One tool is small, but the server is narrow by design: it exists specifically to fetch a GitHub review queue. The tool is substantial rather than trivial, so the count is slightly lean but still appropriate for the server's scope.

Completeness5/5

The tool covers the full review queue surface described: own PRs, requested changes, direct review requests, and team review requests, along with ranking reasons and match counts. There are no obvious read-model gaps within this narrow domain.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

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