MCP Server for GitHub: Issues, PRs, and Repo Search (2026)
SWATI BARWAL
·14 min read
The MCP GitHub server turns your AI coding host into a first-class GitHub client: list open issues, search code across repos, read pull request diffs, and draft PR descriptions without leaving the editor. This tutorial walks through installing @modelcontextprotocol/server-github via npx, choosing Personal Access Token (PAT) scopes carefully, wiring Cursor and Claude Desktop, and knowing when a thin custom TypeScript tool beats the official server for one repo-specific action.
If you are new to the protocol, read What is MCP? first. For host-side security patterns (global vs project mcp.json, secret handling), pair this with MCP servers in Cursor.
Author: Aneh Thakur · Last updated: 2026-07-20
What you'll learn
What the GitHub MCP server exposes (issues, PRs, search, file contents)
How to run it with npx and authenticate with a PAT
Which token scopes to grant — and which to avoid by default
Exact mcpServers JSON for Cursor and Claude Desktop
Example Agent prompts that actually trigger useful tool calls
Security rules so tokens never land in git
When to keep the official server vs sketch a custom TypeScript tool
Troubleshooting for auth failures, empty results, and spawn errors
Prerequisites
Node.js 18+ (tested with Node 22 / 23 on macOS)
A GitHub account with access to the repos you care about
Cursor and/or Claude Desktop
Comfort editing JSON config files
Optional: Docker, if you later switch to GitHub’s container image instead of the npm package
What the GitHub MCP server does
Model Context Protocol defines how an AI host (Cursor, Claude Desktop) discovers and calls tools on a server. The GitHub server is one of the most useful reference servers in the modelcontextprotocol/servers collection: it wraps GitHub’s REST API behind MCP tools so the model can act on your repositories with structured arguments instead of guessing URLs.
In practice, after the server connects you can ask the agent to:
Workflow | What the server enables |
|---|---|
Triage | List open issues, filter by label/assignee, summarize themes |
Code archaeology | Search code across one repo or many (query syntax similar to GitHub search) |
PR review prep | Fetch PR metadata, files changed, and comments so the model can draft a review |
Shipping | Create or update issues, open PRs, comment on discussions (when the PAT allows write) |
Repo hygiene | Inspect branches, file contents, and repository metadata without gh in the terminal |
The model does not “magically” know your private repos. Every call goes through your PAT. If the token cannot see a private repository, neither can the agent — which is exactly what you want.
Official package vs newer GitHub-hosted options. This guide focuses on @modelcontextprotocol/server-github via npx because it matches most TrinityTuts / Cursor examples and needs only Node. In 2025–2026 GitHub also ships a maintained server image (ghcr.io/github/github-mcp-server) and a remote HTTP endpoint for Copilot-aware hosts. If npx pulls a deprecated or non-functional build on your machine, switch to the Docker or remote config from GitHub’s install docs — the PAT scopes and prompt patterns in this article still apply.
Step 1 — Create a Personal Access Token (scopes)
Never paste a password into MCP config. Use a Personal Access Token.
Fine-grained PAT (recommended)
1. Open GitHub → Settings → Developer settings → Fine-grained personal access tokens 2. Create a token with a short expiration (30–90 days) 3. Restrict Repository access to only the repos the agent should touch 4. Grant permissions explicitly:
Permission | Suggested access | Why |
|---|---|---|
Metadata | Read-only | Required baseline for almost every API call |
Contents | Read (or Read and write) | Search / read files; write only if you want commits via tools |
Issues | Read and write | List, create, comment |
Pull requests | Read and write | List, draft descriptions, open PRs |
Workflows | None unless needed | Only if the agent must touch GitHub Actions |
Start with read-only Contents + Issues + Pull requests if you only want triage and search. Elevate write scopes when you trust the host’s tool-approval UX.
Classic PAT (simpler, broader)
Classic tokens use OAuth-style scopes. The common combo for this server:
`repo` — full control of private repositories (contents, issues, PRs). Powerful. Prefer fine-grained if your org allows it.
`read:org` — organization membership and team data. Add only when the agent needs org-level listing. Do not enable it “just in case.”
SSO note: if your org enforces SAML SSO, authorize the token for that organization after creation or API calls return 403 even with correct scopes.
Store the token in your shell environment — not in a committed file:
# tested on: 2026-07-20
# ~/.zshrc or a secrets manager — never commit this
export GITHUB_TOKEN="github_pat_…or…ghp_…"We map that env var into MCP config in the next steps.
Step 2 — Install and smoke-test via npx
You do not need a permanent global install. Cursor and Claude Desktop will spawn the server with npx -y on each session. Still, a one-shot smoke test catches bad tokens early:
# tested on: 2026-07-20
# Confirm the package resolves (may download on first run)
npx -y @modelcontextprotocol/server-github --help 2>/dev/null || true
# Confirm the token can hit the API independently of MCP
curl -sS -H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
https://api.github.com/user | head -c 200
echoIf curl returns your login JSON, the token works. If MCP later fails while curl succeeds, the bug is almost always env wiring (GUI apps do not inherit your interactive shell’s exports — see Troubleshooting).
Pin a version in production configs if you need reproducibility:
npx -y @modelcontextprotocol/[email protected](Replace the version with whatever npm view reports as current when you install.)
Step 3 — Cursor `mcpServers` JSON
Cursor reads MCP servers from project or global config (Cursor MCP docs):
Scope | Path | Use when |
|---|---|---|
Project | .cursor/mcp.json | One repo’s team shares GitHub tools |
Global | ~/.cursor/mcp.json | You want GitHub in every workspace |
Recommended global config (token via env substitution — never hard-code the secret in a committed file):
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${env:GITHUB_TOKEN}"
}
}
}
}If ${env:…} substitution fails in your Cursor build, put the literal token only in the global file that is outside git, or launch Cursor from a terminal where GITHUB_TOKEN is exported.
After saving:
1. Command Palette → Developer: Reload Window (or fully quit and reopen Cursor) 2. Open Settings → MCP and confirm github shows as connected with tools listed 3. Switch to Agent mode and run a prompt from the section below
Project-scoped alternative — useful when only one product repo should allow GitHub writes:
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${env:GITHUB_TOKEN}"
}
}
}
}Commit .cursor/mcp.json without embedding secrets. Document GITHUB_TOKEN in the team README.
Step 4 — Claude Desktop config
Claude Desktop uses a different config path but the same stdio shape.
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "github_pat_REPLACE_ME"
}
}
}
}Claude Desktop launched from the Dock/Spotlight often does not see variables from ~/.zshrc. Practical options:
1. Paste the token into the env block of this local config file (never sync that file to a public gist) 2. Use a wrapper script that exports the token then exec npx … 3. Prefer Cursor’s ${env:GITHUB_TOKEN} pattern if you already keep secrets in the shell for IDE work
Fully quit Claude Desktop (not just close the window) after editing, then reopen. In a new chat, ask Claude to list available MCP tools — you should see GitHub-related tools once the server connects.
Step 5 — Example prompts that work
Vague prompts like “do something with GitHub” waste a turn. Name the owner/repo and the outcome. These patterns worked reliably in Agent mode during testing on 2026-07-20:
List issues
Using the GitHub MCP server, list open issues in OWNER/REPO labeled bug. Summarize each in one line with number, title, and assignee.
Search code
Search code in OWNER/REPO for TODO: migrate mcp and return the top 10 matches with file path and a short snippet. Do not invent matches — only report what the tool returns.
Draft a PR description
Fetch pull request #42 in OWNER/REPO. Read the changed files summary and existing description. Draft an improved PR description with Summary, Test plan, and Breaking changes sections. Do not push or update the PR until I approve.
Triage across an org (needs `read:org` or org-visible repos)
List my open pull requests awaiting review across repositories I can access. Group by repo. Flag anything older than 7 days.
Safe write (after you escalate PAT scopes)
Create a draft issue in OWNER/REPO titled “Docs: document GitHub MCP token scopes” with body checklist items from our security section. Wait for my confirmation before creating.
Always approve tool calls deliberately on first use. Treat write tools like deploying to production.
Security: never commit tokens; prefer fine-grained PATs
GitHub MCP is high leverage and high risk. A leaked PAT with repo scope is a full account compromise for every private repository that token can reach.
Rules that matter
1. Never commit GITHUB_PERSONAL_ACCESS_TOKEN, classic ghp_…, or fine-grained github_pat_… values. Scan PRs for them. 2. Prefer fine-grained PATs limited to specific repositories and short TTLs. 3. Grant `read:org` only when needed — org metadata exposure is easy to overlook. 4. Use read-only permissions for onboarding and demos; enable write after the team understands tool approval. 5. Rotate tokens on a calendar (90 days or less) and immediately after any laptop loss or accidental paste into chat logs. 6. Keep MCP config examples in docs using ${env:GITHUB_TOKEN} placeholders. 7. For shared machines, use separate tokens per developer — never a shared “team bot” token in a personal IDE.
Checklist before enabling write scopes
Check | Done? |
|---|---|
Token expiration set | ☐ |
Repo allow-list reviewed | ☐ |
Tool approval prompts understood in Cursor/Claude | ☐ |
Secrets not in git history (git log -p / secret scanning) | ☐ |
Org SSO authorization completed | ☐ |
If your company forbids PATs in IDE configs, stop here and use a company-approved GitHub App or remote MCP gateway with short-lived credentials instead of a personal token on disk.
Official server vs custom TypeScript tools
Use the official @modelcontextprotocol/server-github when:
You need standard GitHub operations (issues, PRs, search, file read)
Multiple hosts (Cursor + Claude Desktop) should share the same tools
You do not want to maintain Octokit wrappers yourself
Build a custom MCP tool when:
One action is repo-specific (e.g. “open a release checklist issue with our exact template and labels”)
You must call internal wrappers (CODEOWNERS validation, custom GraphQL, private enterprise APIs)
You want to deny broad search while allowing a single safe mutation
Compliance requires every tool call to log to your audit system
Rule of thumb: start with the official server for discovery and triage. When you find yourself pasting the same multi-step prompt three times a week, extract that workflow into one custom tool with a tight schema. For a from-scratch server walkthrough, see Build your first MCP server in TypeScript. For a domain API example, see the Zerodha MCP guide.
Optional: custom TypeScript tool sketch (one repo-specific action)
Suppose you always want the agent to open a triage issue with fixed labels and a body template — without exposing full GitHub write APIs. A minimal custom server tool (sketch):
// src/create-triage-issue.ts — sketch only; wire into your McpServer
import { Octokit } from "@octokit/rest";
import * as z from "zod";
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
export const createTriageIssueInput = z.object({
title: z.string().min(8).max(120),
summary: z.string().min(20).max(4000),
});
export async function createTriageIssue(input: z.infer<typeof createTriageIssueInput>) {
const { data } = await octokit.issues.create({
owner: "YOUR_ORG",
repo: "YOUR_REPO",
title: `[triage] ${input.title}`,
body: `## Summary\n\n${input.summary}\n\n## Checklist\n\n- [ ] Repro steps\n- [ ] Impact\n- [ ] Owner\n`,
labels: ["triage", "needs-human"],
});
return { number: data.number, url: data.html_url };
}Register that function with @modelcontextprotocol/sdk registerTool the same way as in the first TypeScript MCP tutorial. Point Cursor at your built node dist/index.js instead of (or in addition to) the official GitHub server. The official server remains for search and PR reads; your custom tool owns the one mutation you care about locking down.
Troubleshooting
Server shows connected but every tool returns empty / Bad credentials
Cause: GITHUB_PERSONAL_ACCESS_TOKEN is unset at process start (common when Cursor/Claude are GUI-launched and do not inherit shell exports).
Fix: Put the token in the MCP config env block for local debugging, or launch the host from a terminal after export GITHUB_TOKEN=…. Verify with curl against https://api.github.com/user.
`403` or “Resource not accessible by personal access token”
Cause: Missing fine-grained permission, repo not on the token allow-list, or SSO not authorized.
Fix: Edit the token — enable Issues / Pull requests / Contents as needed; add the repository; authorize SSO for the org.
`spawn npx ENOENT` or server never connects
Cause: Cursor’s PATH does not include Node/npx (especially on macOS GUI apps).
Fix: Use absolute paths, e.g. "command": "/usr/local/bin/npx" or the path from which npx. Reload the window after changing config.
Search returns nothing that exists on github.com
Cause: Wrong owner/repo, private repo invisible to the token, or query syntax too narrow.
Fix: Test the same query in GitHub’s web search while logged in as the token’s user. Confirm the repo is in the fine-grained allow-list.
Writes succeed in curl but model never offers write tools
Cause: Host tool filtering, or token is read-only so the server omits write capabilities.
Fix: Confirm PAT write permissions; in Cursor use Agent mode and explicitly ask to create/update; approve the tool call when prompted.
npm package behaves oddly after a long idle period
Cause: Stale npx cache or package deprecation toward GitHub’s Docker image.
Fix: Clear the npx cache, pin a known-good version, or migrate the config to docker run … ghcr.io/github/github-mcp-server with the same GITHUB_PERSONAL_ACCESS_TOKEN env var.
FAQ
What is the MCP GitHub server?
How do I install the GitHub MCP server with npx?
Which PAT scopes does the GitHub MCP server need?
Is it safe to put my GitHub token in mcp.json?
Why use the official server instead of writing my own?
Can I use the same GitHub MCP config in Cursor and Claude Desktop?
Does the GitHub MCP server work with private repositories?
What prompts work best with GitHub MCP?
Related posts
What is MCP? A Developer’s Guide to Model Context Protocol (2026) — protocol basics before wiring servers
MCP Servers in Cursor: Setup and Security Guide (2026) — mcp.json placement and secret handling
Build Your First MCP Server in TypeScript (2026) — custom tools when the official GitHub server is not enough
How to Build an MCP Server for Zerodha: AI-Powered Trading with Claude — domain API MCP pattern beyond GitHub
Next steps
1. Create a fine-grained PAT limited to one sandbox repo and verify with curl /user 2. Add the Cursor global github MCP entry with ${env:GITHUB_TOKEN} 3. Run the “list issues” and “search code” prompts in Agent mode 4. Only then enable write scopes and try a draft PR description workflow 5. If one workflow becomes repetitive, extract it into a custom TypeScript tool and keep the official server for everything else
