Skip to content

aaronsomo/github-issues-mcp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

github-issues-mcp

An MCP server that exposes GitHub issues and pull requests to Claude Desktop and any other MCP-compatible client — built for LLM-driven triage, summarization, and labeling workflows.

  • Read-only by default. Write tools (add_comment, add_label) exist but are only registered when you opt in with --allow-writes.
  • Projected for models, not dashboards. GitHub's ~40-field issue payload is cut down to the 8 fields a model actually uses for triage. Less noise, far fewer tokens.
  • Cursor pagination. List results are capped (default 20, max 50 per call) and continue via an opaque next_cursor — the model can page without doing page math.
  • Errors a model can act on. Every failure comes back as structured JSON with a stable code and a hint written for the LLM: what went wrong and what to do differently.

See DESIGN.md for the reasoning behind these choices.

One-minute install

Requires Node.js ≥ 18.

git clone https://github.com/aaronsomo/github-issues-mcp.git
cd github-issues-mcp
npm install && npm run build

The build produces dist/index.js — a stdio MCP server that works with any MCP client. Pick yours below.

Claude Desktop

Add the server to claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json, Windows: %APPDATA%\Claude\claude_desktop_config.json):

{
  "mcpServers": {
    "github-issues": {
      "command": "node",
      "args": ["/ABSOLUTE/PATH/TO/github-issues-mcp/dist/index.js"],
      "env": {
        "GITHUB_TOKEN": "ghp_your_token_here"
      }
    }
  }
}

Restart Claude Desktop. You should see the six read tools under the 🔌 MCP indicator. Try:

"List the open issues labeled bug in facebook/react and summarize the three most recently updated."

To enable writes, add the flag to args — without it the write tools are never registered, so the model cannot call them:

"args": ["/ABSOLUTE/PATH/TO/github-issues-mcp/dist/index.js", "--allow-writes"]

Claude Code

Register the server with claude mcp add. Everything after -- is the command Claude Code runs, so server flags like --allow-writes go there:

# read-only
claude mcp add github-issues \
  -e GITHUB_TOKEN=ghp_your_token_here \
  -- node /ABSOLUTE/PATH/TO/github-issues-mcp/dist/index.js

# with writes (note --allow-writes after the `--`)
claude mcp add github-issues \
  -e GITHUB_TOKEN=ghp_your_token_here \
  -- node /ABSOLUTE/PATH/TO/github-issues-mcp/dist/index.js --allow-writes

Then verify and use it:

claude mcp list          # confirms github-issues is connected

Inside a session, run /mcp to see the registered tools, then just ask:

"Triage the open issues in vercel/next.js — group the bug reports by area and flag anything updated in the last day."

Scope. claude mcp add defaults to local (this project only). Add -s user to make it available in every project, or -s project to write a checked-in .mcp.json so collaborators get it automatically:

claude mcp add github-issues -s user -e GITHUB_TOKEN=ghp_… -- node /ABSOLUTE/PATH/.../dist/index.js

A -s project registration produces a .mcp.json in the repo root with the same shape as the Claude Desktop snippet above (command / args / env). Don't commit a real token there — use an env var indirection ("GITHUB_TOKEN": "${GITHUB_TOKEN}") and set it in your shell. The first time a collaborator opens the project, Claude Code shows the server as "⏸ Pending approval" until they approve it (claude mcp list reflects this); that's expected for checked-in servers.

Writes & permissions. The write tools carry MCP readOnlyHint: false annotations, so Claude Code surfaces its normal approval prompt before add_comment / add_label run — the --allow-writes gate (which decides whether the tools exist at all) and Claude Code's per-call consent stack together rather than conflict.

To remove or inspect the server later: claude mcp remove github-issues and claude mcp get github-issues.

Token & minimum OAuth scopes

Set GITHUB_TOKEN (or GH_TOKEN). Without a token the server still works for public repositories, but unauthenticated GitHub API limits are low (60 requests/hour) — a token raises that to 5,000/hour.

Usage Fine-grained PAT permission Classic PAT scope
Read public repos none (or any token for the rate limit) none
Read private repos Issues: Read + Pull requests: Read repo
--allow-writes (comment / label) Issues: Read and write public_repo (public) / repo (private)

public_repo is a write scope — don't grant it for read-only use; reading public repos needs no scope at all.

Use the smallest scope that covers your use — there is no reason to hand a read-only triage assistant a write-capable token.

Tools

Read (always available)

Tool What it returns
list_issues Issue summaries for a repo (PRs filtered out) — filter by state, labels, assignee, since
get_issue One issue in detail: body, assignees, milestone, lifecycle dates
list_issue_comments The discussion thread of an issue or PR
list_pull_requests PR summaries — state reports open/closed/merged
get_pull_request One PR in detail: branches, merge status, diff stats
search_issues Full GitHub search syntax across issues and PRs (repo:, label:, is:, ...)

Each list_issues summary row is exactly eight fields:

{
  "number": 4096,
  "title": "Crash when window is resized during render",
  "state": "open",
  "author": "octocat",
  "labels": ["bug", "p1"],
  "comments": 7,
  "updated_at": "2026-06-01T12:00:00Z",
  "url": "https://github.com/facebook/react/issues/4096"
}

list_pull_requests rows have the same shape but carry draft (boolean) in place of comments, and state reports open/closed/merged. search_issues rows add a ninth field, type (issue or pull_request), so the model can tell them apart in a mixed result set.

List results include next_cursor when more pages exist — pass it back verbatim to continue.

Write (only with --allow-writes)

Tool What it does
add_comment Posts a comment on an issue or PR
add_label Adds labels to an issue or PR (existing labels are kept; labels must already exist in the repo)

Both writes are additive — nothing in this server can close, edit, or delete anything.

Errors

Failures return structured, model-readable JSON, e.g.:

{
  "error": {
    "code": "rate_limited",
    "status": 403,
    "message": "API rate limit exceeded",
    "hint": "GitHub rate limit hit. The rate limit resets at 2026-06-10T21:30:00.000Z. Authenticated requests get a much higher limit — set GITHUB_TOKEN if it is missing.",
    "retry_after_seconds": 1200
  }
}

Codes: unauthorized, forbidden, rate_limited, not_found, issues_disabled, validation_failed, invalid_cursor, invalid_response, network_error, timeout, cancelled, server_error, http_error, internal_error.

One exception to the structured shape: arguments that fail the tool's input schema (e.g. limit above 50, or a malformed owner/repo) are rejected by the MCP SDK before the handler runs, so they come back as the SDK's own -32602 plain-text error rather than the {error:{...}} payload. Every error originating inside the server uses the structured form.

Development

npm test            # vitest suite (no network — fetch is injected)
npm run typecheck   # strict TS, no emit
npm run build       # compile to dist/

License

MIT

About

MCP server exposing GitHub issues & PRs to Claude Desktop, Claude Code, and other MCP clients — LLM-ergonomic field projection, cursor pagination, and safe-by-default write gating.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors