gh-review-queue-mcp
Provides a prioritized, deduplicated review queue for GitHub pull requests, combining review requests directed at you, requests for your teams, and your own PRs awaiting review, with CI status and filtering options.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@gh-review-queue-mcpWhat's at the top of my review queue?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
gh-review-queue-mcp
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.
2. Fan out into one batched search
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 |
|
|
|
|
|
|
|
|
|
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 syncToken
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 repositoriesread: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-mcpIt 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-mcpOpen 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 |
| array of | all three | Which reasons to include. An item survives if any of its reasons is included. |
| boolean |
| Drop drafts. They're excluded, not demoted — a draft isn't reviewable yet. |
| integer | none | Drop PRs opened more than this many days ago. Inclusive at the boundary. |
| array of | none | Restrict to these repositories. Exact match. |
| integer 1–100 |
| Maximum items returned. |
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 |
| done |
3 |
| 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.
This server cannot be installed
Maintenance
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
- FlicenseAqualityDmaintenanceA minimal MCP server that exposes a focused set of GitHub PR review tools to AI agents, enabling PR listing, detail retrieval, comment viewing, and thread management.5
- AlicenseAqualityBmaintenanceAn MCP server that provides operational tooling over the GitHub API — issue triage, PR review monitoring, repo health audits, and team access reviews.111MIT
- FlicenseAqualityBmaintenanceAn MCP server exposing AI-powered GitHub PR review as tools.5
- AlicenseAqualityCmaintenanceA production-ready MCP server for triaging and reviewing GitHub pull requests via the GitHub REST API, providing typed tools to list, inspect, comment, add labels, and submit reviews.821MIT
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…
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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