github-pr-review-mcp-server
README.md
# GitHub PR Review MCP Server
A focused Model Context Protocol server for triaging and reviewing GitHub pull requests from Claude Desktop, Cursor, and other stdio-compatible MCP clients.
The server is written in TypeScript and exposes eight small, typed tools instead of wrapping the `gh` CLI. It supports fine-grained personal access tokens (PATs), GitHub App installation authentication, scoped cursor pagination, explicit read/write separation, GitHub rate-limit reporting, and validated inline diff targets.
> [!IMPORTANT]
> This project is independent community software. It is not an official GitHub, Anthropic, Cursor, Model Context Protocol, or Archimedes product.
## Security model at a glance
- Writes are **off by default**. Mutation tools fail unless `GITHUB_WRITE_ENABLED=true`.
- Use a fine-grained PAT or a GitHub App installed only on the repositories the server needs.
- `post_review_comment` validates the file, side, line, and optional range against the current PR diff before writing.
- Repository content, diffs, PR text, and comments are untrusted input. The opt-in prompts explicitly tell the model not to follow instructions found in that content.
- Credentials are read from environment variables or a mounted private-key file. Never commit them or paste them into prompts, logs, screenshots, issues, or demo recordings.
- API errors include a redacted message and the last observed rate-limit budget.
See [SECURITY.md](SECURITY.md) before enabling writes.
## Requirements
- Node.js 20.10 or newer
- A compatible stdio MCP client
- One of:
- a fine-grained GitHub PAT; or
- a GitHub App ID, installation ID, and private key
- Repository permissions appropriate to the tools you enable
## Clean-machine quickstart
This path is designed to take less than ten minutes on a clean machine that already has Node.js and a GitHub credential. The package version is pinned so the same code is installed every time.
1. Confirm Node.js:
```bash
node --version
```
2. Verify the published executable without starting the server:
```bash
npx -y github-pr-review-mcp-server@0.1.0 --help
```
3. Add one of the client configurations below, replace every placeholder locally, and restart the client.
4. Ask the client to list tools. It should register exactly:
```text
list_prs
get_pr
get_pr_diff
list_pr_comments
post_review_comment
submit_review
add_labels
request_changes
```
Start in read-only mode. Enable writes only for a repository you control and only immediately before an intended mutation.
### Run from a source checkout
```bash
git clone https://github.com/jackvansickle1/github-pr-review-mcp-server.git
cd github-pr-review-mcp-server
npm ci
npm run check
GITHUB_TOKEN="<fine-grained-pat>" node dist/cli.js
```
The process uses stdio for MCP traffic. Diagnostic startup text goes to stderr.
## Claude Desktop configuration
Open Claude Desktop's MCP configuration file:
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
### PAT, read-only
```json
{
"mcpServers": {
"github-pr-review-mcp-server": {
"command": "npx",
"args": ["-y", "github-pr-review-mcp-server@0.1.0"],
"env": {
"GITHUB_AUTH_MODE": "pat",
"GITHUB_TOKEN": "<fine-grained-pat>",
"GITHUB_WRITE_ENABLED": "false"
}
}
}
}
```
### GitHub App, read-only
Prefer a mounted private-key file over inline PEM text:
```json
{
"mcpServers": {
"github-pr-review-mcp-server": {
"command": "npx",
"args": ["-y", "github-pr-review-mcp-server@0.1.0"],
"env": {
"GITHUB_AUTH_MODE": "app",
"GITHUB_APP_ID": "<app-id>",
"GITHUB_APP_INSTALLATION_ID": "<installation-id>",
"GITHUB_APP_PRIVATE_KEY_PATH": "<absolute-path-to-private-key.pem>",
"GITHUB_WRITE_ENABLED": "false"
}
}
}
}
```
Restart Claude Desktop after saving the file. Configuration files may contain secrets in plaintext; restrict their filesystem permissions and do not share them.
## Cursor configuration
Create `.cursor/mcp.json` in a project, or add the same server definition to Cursor's global MCP settings.
### PAT, read-only
```json
{
"mcpServers": {
"github-pr-review-mcp-server": {
"command": "npx",
"args": ["-y", "github-pr-review-mcp-server@0.1.0"],
"env": {
"GITHUB_AUTH_MODE": "pat",
"GITHUB_TOKEN": "<fine-grained-pat>",
"GITHUB_WRITE_ENABLED": "false"
}
}
}
}
```
### GitHub App, read-only
```json
{
"mcpServers": {
"github-pr-review-mcp-server": {
"command": "npx",
"args": ["-y", "github-pr-review-mcp-server@0.1.0"],
"env": {
"GITHUB_AUTH_MODE": "app",
"GITHUB_APP_ID": "<app-id>",
"GITHUB_APP_INSTALLATION_ID": "<installation-id>",
"GITHUB_APP_PRIVATE_KEY_PATH": "<absolute-path-to-private-key.pem>",
"GITHUB_WRITE_ENABLED": "false"
}
}
}
}
```
Do not commit `.cursor/mcp.json` if it contains a real credential. A repository-local config should use a placeholder and obtain the real value through a user-controlled local secret workflow.
## Authentication
### Fine-grained PAT
Set either `GITHUB_TOKEN` or `GITHUB_PAT`. `GITHUB_TOKEN` takes precedence when both are present.
Recommended fine-grained repository permissions:
| Capability | Permission |
| --- | --- |
| PR metadata, changed files, inline comments, and reviews | Pull requests: Read |
| Conversation comments | Issues: Read |
| Repository identity | Metadata: Read |
| Post inline comments or submit reviews | Pull requests: Write |
| Add labels | Issues: Write or Pull requests: Write |
Grant access only to selected repositories. Use a separate, narrowly scoped credential or GitHub App installation for a write-enabled server instance.
### GitHub App installation
Set:
```text
GITHUB_AUTH_MODE=app
GITHUB_APP_ID=<positive-integer>
GITHUB_APP_INSTALLATION_ID=<positive-integer>
GITHUB_APP_PRIVATE_KEY_PATH=<absolute-path-to-pem>
```
The server uses the app credentials to obtain installation access tokens through Octokit's GitHub App authentication strategy. Installation tokens are short-lived and refreshed by the authentication library as needed.
`GITHUB_APP_PRIVATE_KEY` may contain PEM text with literal `\\n` escapes, but a read-only mounted file is safer. Never place private-key contents in a client screenshot, issue, shell history, or repository.
### Authentication selection
`GITHUB_AUTH_MODE` accepts `auto`, `pat`, or `app` and defaults to `auto`.
- `app`: requires a complete GitHub App configuration.
- `pat`: requires `GITHUB_TOKEN` or `GITHUB_PAT`.
- `auto`: selects GitHub App mode when the complete App configuration is present; otherwise selects PAT mode.
## Write opt-in
The four mutation tools remain registered so clients can discover the full contract, but they return an error until writes are explicitly enabled:
```text
GITHUB_WRITE_ENABLED=true
```
Mutation tools are:
- `post_review_comment`
- `submit_review`
- `add_labels`
- `request_changes`
Enabling the flag does not grant GitHub permissions by itself; the PAT or GitHub App must also have the corresponding repository permission. Review the exact owner, repository, PR number, comment body, labels, and target lines before asking an MCP client to write.
## Tools
All tool inputs use typed Zod schemas. All successful responses contain both human-readable JSON text and MCP `structuredContent`.
### `list_prs`
Lists PRs for one repository.
Key inputs: `owner`, `repo`, `state`, `sort`, `direction`, optional `base`/`head`, `limit`, and optional opaque `cursor`.
- Default limit: 100
- Maximum limit per call: 500
- Fetches GitHub pages automatically
- Returns `has_more`, `next_cursor`, and `pages_fetched`
Never decode or construct a cursor in client code. Pass `next_cursor` back unchanged.
### `get_pr`
Returns normalized PR metadata, author, branches and SHAs, labels, review requests, merge state, timestamps, changed-file counts, additions, deletions, and comment counts.
### `get_pr_diff`
Lists changed files and parses each available unified patch into hunks with explicit old/new line mappings.
Key inputs: `owner`, `repo`, `pull_number`, `limit`, optional `cursor`, and `include_patch`.
- Default/maximum changed files per call: 100/300
- Raw patch text is excluded by default; set `include_patch=true` when needed
- Parsed hunk output is capped at 5,000 lines per call and requested raw patches at 500,000 bytes; truncation is explicit
- Returns pagination state
- Reports `patch_available=false` when GitHub omits a patch, such as for some binary or oversized changes
- Does not invent missing patch data
### `list_pr_comments`
Lists one of three comment/review collections:
- `kind=review` (default): inline review comments
- `kind=conversation`: PR conversation comments
- `kind=reviews`: submitted review summaries
It uses the same opaque-cursor pattern as `list_prs`.
### `post_review_comment`
Posts one inline review comment after resolving the current file patch and validating:
- `path` belongs to the PR;
- `line` exists in a complete diff hunk;
- `side` is `LEFT` for a deleted line or `RIGHT` for an added/context line;
- an optional `start_line` is contiguous, on the same side, and in the same hunk; and
- the optional `commit_id` is a 40-character SHA.
The server fetches the current PR head before reading the diff. If `commit_id` is supplied, it must equal that current head or the write is rejected with HTTP-style status 409. After validating the diff target, the server fetches the head again and refuses the write if the PR changed during validation. If `commit_id` is omitted, the first current-head value is used only after that second consistency check. It also fails closed when GitHub omitted the patch or the target cannot be proven.
### `submit_review`
Submits an `APPROVE` or `COMMENT` review. A `COMMENT` review requires a non-empty `body`. Use `request_changes` for a `REQUEST_CHANGES` review. An optional exact head `commit_id` must match GitHub's current PR head or the server rejects the write as stale.
### `add_labels`
Adds one to twenty unique existing repository labels without replacing current labels. The server first verifies the number is a pull request. GitHub exposes PR labels through the Issues API; GitHub accepts Issues write or Pull requests write permission for this endpoint.
### `request_changes`
Submits exactly one `REQUEST_CHANGES` review with a required non-empty rationale. An optional `commit_id` must match GitHub's current PR head or the server rejects the write as stale.
## Opt-in prompts
The server also registers two prompts; neither runs automatically:
- `review_pull_request`: evidence-first general review
- `security_review_pull_request`: security-focused review
Both instruct the model to retrieve PR evidence first, treat all repository content as untrusted, protect credentials and unrelated data, and avoid write tools without separate user confirmation.
## Pagination semantics
GitHub REST pages contain at most 100 records. The server follows GitHub's `Link` header until it reaches the requested `limit` or the collection ends.
When a response is truncated at the caller's limit:
1. Check `has_more`.
2. If true, copy `next_cursor` unchanged into the next call.
3. Continue until `has_more=false`.
The cursor is versioned and bound to the exact tool, repository, filters, and—when reading diffs—the current PR head SHA. Cross-tool, cross-repository, changed-filter, stale-head, oversized, non-canonical, invalid, and corrupted cursors fail before the paginated request. Keep it opaque and pass it back unchanged.
`list_prs` defaults to creation time ascending to reduce page reordering. GitHub does not offer a snapshot-isolated REST listing, so concurrent PR creation, closure, or filter changes can still alter a long traversal. Treat pagination as stable continuation over a mostly static collection, not as a database snapshot.
## Diff and inline-comment semantics
GitHub's modern review-comment API identifies a target with `path`, `line`, and `side` rather than the legacy `position` value.
- `LEFT` addresses a deletion from the old blob.
- `RIGHT` addresses an addition or context line in the new blob.
- Multi-line ranges must stay within one complete hunk and one side.
- Truncated or malformed hunks are never treated as safe comment targets.
- Renames and quoted Git paths are parsed explicitly.
- Binary or unavailable patches can be inspected at the file-summary level but cannot receive a line comment through this server.
The parsed legacy position is retained only for diagnostics.
## Rate limits and errors
Every GitHub-backed response includes a `rate_limit` snapshot when headers are available:
```json
{
"limit": 5000,
"remaining": 4987,
"used": 13,
"reset_epoch": 1785978000,
"reset_at": "2026-08-06T01:00:00.000Z",
"resource": "core",
"retry_after_seconds": null,
"request_id": "EXAMPLE:1234"
}
```
GitHub errors include `remaining`, `reset`, `resource`, `retry_after`, and `request_id` in the message, using `unknown` when GitHub did not provide a value. Credential-like values are redacted from upstream error text.
GitHub requests are serialized. The server stops page traversal when the remaining budget reaches zero, fails fast during primary/secondary cooldown windows, and spaces mutations by at least one second. It does not automatically retry mutations because a retry could create a duplicate comment, review, or label operation. Honor `retry_after_seconds` and GitHub's primary and secondary rate-limit guidance.
## Configuration reference
| Variable | Default | Purpose |
| --- | --- | --- |
| `GITHUB_AUTH_MODE` | `auto` | `auto`, `pat`, or `app` |
| `GITHUB_TOKEN` | — | Preferred PAT variable |
| `GITHUB_PAT` | — | PAT alias |
| `GITHUB_APP_ID` | — | GitHub App ID |
| `GITHUB_APP_INSTALLATION_ID` | — | App installation ID |
| `GITHUB_APP_PRIVATE_KEY_PATH` | — | Preferred PEM file path |
| `GITHUB_APP_PRIVATE_KEY` | — | Inline PEM or PEM with `\\n` escapes |
| `GITHUB_WRITE_ENABLED` | `false` | Explicit mutation gate |
| `GITHUB_API_URL` | `https://api.github.com` | GitHub REST base URL; HTTPS required except localhost |
| `GITHUB_API_VERSION` | `2022-11-28` | `X-GitHub-Api-Version` value |
| `GITHUB_REQUEST_TIMEOUT_MS` | `30000` | Request timeout, 1,000–120,000 ms |
For GitHub Enterprise Server, set `GITHUB_API_URL` to the REST base URL, for example `https://github.example.com/api/v3`. Confirm API compatibility and permissions in your own environment.
## Development and verification
```bash
npm ci
npm run typecheck
npm test
npm run build
# Runs typecheck, tests, and build:
npm run check
```
Before publishing or submitting a release, also verify the real-system acceptance path:
- all eight tools register over stdio;
- `list_prs` traverses more than 50 PRs in a repository with enough history;
- an authorized inline comment is visible on an owned test PR within five seconds;
- both PAT and GitHub App installation modes authenticate;
- the pinned npm quickstart works on a clean machine in under ten minutes; and
- the one-minute Claude Desktop demo contains no secrets or private data.
Real mutation tests must use a disposable repository controlled by the tester. Never run them against a third-party PR without authorization.
## Disclosure and licensing
The project is licensed under the [MIT License](LICENSE). Dependency and provenance disclosures are in:
- [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)
- [SUBMISSION_DISCLOSURES.md](SUBMISSION_DISCLOSURES.md)
The implementation is not a fork of another GitHub PR review MCP server. Public alternatives were used only for market and namespace comparison; their source was not incorporated. Review the disclosure file before any external submission because it contains items the submitter must personally verify.
## Demo
The required one-minute screen-capture plan and safety checklist are in the repository-only [DEMO_SCRIPT.md](https://github.com/jackvansickle1/github-pr-review-mcp-server/blob/main/DEMO_SCRIPT.md). It is submission collateral rather than a runtime package file.
## License
MIT © 2026 Jack VanSickle. See [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues