01Why auth choice matters
Every GitHub MCP failure we've helped a reader debug traces back to one of four auth mistakes:
- Wrong token type for the workflow. A classic PAT works but exposes access to every org the user belongs to, forever, with no rotation. A fine-grained PAT would have been safer but slightly harder to set up. The user picked the fast path in a hurry; the token ended up in a leaked config file six months later; a former employee's still-valid credential got flagged in an audit.
- Missing SAML SSO authorization. The token works fine against personal repos but returns misleading 404s on any org resource. Users spend hours suspecting the MCP, the AI provider, or GitHub outages before finding the "Configure SSO" button hiding in the token settings page. This is the single most common enterprise auth failure.
- Over-broad scopes. Users grant
repofull-access (a classic PAT scope) because that's what the first Stack Overflow answer said. The AI now has write access to every private repo in every org — including the ones with production secrets in.env.examplefiles. - Silent token expiration. A working setup breaks weeks later when the PAT expires. The MCP returns 401 errors; the user thinks something's wrong with the MCP; they burn an afternoon debugging before realizing the token needs to be renewed and nothing sent them a reminder.
All four are preventable with the setup in this guide. The core rule: pick the auth type that fits your situation, grant the minimum scope, plan for rotation from day one.
Everything below works identically whether you're using the GitHub MCP with Claude Desktop, Claude Code, Cursor, Cline, or any other MCP-compliant client. The auth token is the same; only the config-file location and env-var syntax vary. We use Claude Desktop paths in examples; adjust for your client.
02Prerequisites
Before you start:
- A GitHub account with access to the repos you want the AI to reach. Personal, GitHub Enterprise Cloud, and GitHub Enterprise Server all work.
- An MCP-compliant AI client already installed. Claude Desktop, Claude Code, Cursor, Cline, or Continue.
- A secret store for the token. Environment variables in a shell profile, 1Password CLI, your OS keychain, or HashiCorp Vault — anywhere except a config file that gets checked into git or synced to Dropbox.
- Admin rights on your GitHub org if you're setting up a GitHub App (Section 9). For a personal PAT, no admin rights required beyond your own account.
Time investment: about 10 minutes for a fine-grained PAT setup, closer to 25 minutes for a GitHub App. Setup once, use for months.
03Choose your auth type
Four options. Pick one and skip the rest:
| Auth type | Best for | Rate limit | Scope granularity | Rotation |
|---|---|---|---|---|
| Fine-grained PAT | Individuals, single org, prototyping, personal projects | 5,000 / hr | Per-repo, per-permission | Forced (max 366 days) |
| Classic PAT | Legacy compatibility only — when a tool requires it | 5,000 / hr | Coarse (repo, workflow, admin) | Optional (no expiry by default) |
| GitHub App | Teams, production workflows, shared infrastructure | 5,000 / hr per installation | Per-permission, per-installation | Long-lived app + short-lived install tokens |
| OAuth App | Multi-tenant SaaS that acts on user behalf | 5,000 / hr per user token | OAuth scopes (same as classic PAT) | Refresh tokens for long-lived access |
The short version:
- If you're one person setting up AI for your own use: fine-grained PAT. Jump to Section 4.
- If you're a team setting up shared or production AI infrastructure: GitHub App. Jump to Section 9.
- If you're building a product that authenticates end users to their GitHub: OAuth App (not covered in depth here — see GitHub's OAuth Apps documentation).
- Classic PATs: use only if a tool specifically doesn't support fine-grained tokens yet. In 2026 this is rare.
For 90% of individual and small-team AI use cases, a fine-grained PAT is the right answer. It's fast to set up, scopeable to specific repos, forces expiration, and its permission model matches what AI workflows actually need. Reach for a GitHub App only when you're clearly outgrowing the PAT model — multiple users, production reliability requirements, or an org policy that mandates apps over PATs.
04Create a fine-grained personal access token
The recommended path for individual use. Ten-minute setup.
Step-by-step
- Sign in to GitHub. Click your avatar (top right) → Settings.
- In the left sidebar, scroll to the bottom and click Developer settings.
- Click Personal access tokens → Fine-grained tokens. (Not "Tokens (classic)" — that's the older type.)
- Click Generate new token.
- Fill in the token settings:
- Token name: something descriptive like
claude-mcp-personal-2026. You'll thank yourself later when reviewing tokens. - Expiration: 90 days is a good balance. Never pick "No expiration" — the maximum is 366 days but shorter is safer.
- Description: "Used by Claude Desktop MCP for reading personal repos" — helps team members and future-you.
- Resource owner: your personal account for personal repos, or a specific org for org repos. A single fine-grained PAT can only cover one owner — that's a deliberate safety trade-off. For multiple orgs, create multiple tokens.
- Repository access: "Only select repositories" is the safest option. Pick the specific repos the AI needs. Avoid "All repositories" unless you truly want AI access to every repo you own.
- Token name: something descriptive like
- Continue to Section 5 for scope selection before hitting Generate.
Selecting "All repositories" doesn't just cover repos you have now — it covers repos you'll create later, repos your org adds you to, and repos you fork. That silent scope expansion is how tokens end up with access their owner didn't intend. "Only select repositories" is worth the extra 30 seconds of clicking.
05Select AI-safe scopes
On the same "Generate new token" page, scroll to Permissions. This is where most auth setups get wrong — either too broad ("give it everything, we'll fix it later" — you won't) or too narrow (the AI hits permission denied mid-workflow and the user files a ticket).
Minimum viable read-only scope set
Under Repository permissions, set these:
| Permission | Access | Why |
|---|---|---|
| Metadata | Read (auto) | Always required; granted automatically when you enable any other permission. |
| Contents | Read | Read files, browse trees, view blobs. The core of what AI does with code. |
| Pull requests | Read | Read PR titles, descriptions, diffs, reviews. Needed for PR-context workflows. |
| Issues | Read | Read issues and comments. Needed for issue-linked PR workflows. |
| Actions | Read (optional) | Read workflow runs and job logs. Add only if the AI investigates CI failures. |
Add write access only when you'll actually use it
If your AI workflow includes creating PRs or commenting on issues, extend these two:
| Permission | Access | When to add |
|---|---|---|
| Pull requests | Read and write | AI creates PRs, adds review comments, or updates PR descriptions. |
| Issues | Read and write | AI creates or comments on issues. |
| Contents | Read and write | AI creates commits directly (uncommon — usually PRs are safer). |
Never grant these to AI tokens
Administration: lets the token delete repos, transfer ownership, change collaborator access. There is no reasonable AI workflow that needs this.
Secrets: lets the token read and write repository secrets (API keys, deployment credentials). Catastrophic if leaked.
Deployments: lets the token trigger production deployments. AI-triggered deploys should go through explicit human-approved pipelines, not a token.
Actions: Write: lets the token modify workflow files and trigger arbitrary workflow runs, which can execute arbitrary code with the workflow's own secrets. Only grant if you have a very specific reason.
Environments: lets the token modify protected environment configurations.
After setting permissions, hit Generate token. GitHub shows the token exactly once — a string starting with github_pat_. Copy it immediately into your secret store. If you lose it, revoke and generate a new one; there's no recovery.
06Configure the GitHub MCP server
Add the GitHub MCP to your client config, reading the token from an environment variable rather than embedding it in the file.
Store the token in a secret store first
For most people, environment variables in the shell profile are simplest and safe enough:
# macOS / Linux (~/.zshrc or ~/.bashrc)
export GITHUB_MCP_TOKEN="github_pat_11ABC...your_token_here..."
# Reload the shell after saving
source ~/.zshrc
# Verify it's set (should print the first few chars, not the whole thing)
echo "${GITHUB_MCP_TOKEN:0:15}..."
For teams: use 1Password CLI (op read "op://vault/GitHub MCP Token/credential"), AWS Secrets Manager, or HashiCorp Vault. The pattern is the same — keep the token out of any file that might get committed, shared, or backed up in plaintext.
Add the MCP to Claude Desktop
Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or the equivalent path on your platform:
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_TOKEN}"
}
}
}
}
The ${GITHUB_MCP_TOKEN} substitution reads from the environment variable you set above. Claude Desktop resolves this at MCP startup — the token never touches the config file.
For Cursor and other clients, the config file location differs but the pattern is the same: an env object with GITHUB_PERSONAL_ACCESS_TOKEN resolved from an environment variable. Cursor: ~/.cursor/mcp.json. Cline: ~/.cline/mcp_settings.json. Check each tool's docs for the exact path.
Restart your MCP client fully after saving the config. The token is loaded at MCP server startup; a running client doesn't pick up config changes.
07The SAML SSO fix (enterprise only)
If your organization enforces SAML single sign-on, a token that works for personal repos will return misleading 404 errors on any org resource until you explicitly authorize it for SSO. This is the single most common enterprise auth failure. It's not documented on the token creation page. The error message doesn't hint at it.
How to authorize a token for SSO
- Go to Settings → Developer settings → Personal access tokens → Fine-grained tokens (or Tokens (classic) if using classic).
- Find your token in the list. Click its name.
- Scroll to the Single sign-on (SSO) section near the top of the token page.
- For each SSO-protected org listed, click Configure SSO → Authorize.
- You'll be redirected to your SSO provider (Okta, Auth0, Azure AD, etc.) to complete the flow. After a successful auth, GitHub marks the token as authorized for that org.
When SSO authorization is missing, GitHub's API returns 404 Not Found for the org's resources — the same error you'd get if a repo didn't exist. This is deliberate (avoids leaking whether a repo exists to unauthorized users) but it's confusing during setup. If your token works for personal repos but returns 404 on org repos, check SSO authorization first before suspecting anything else.
SSO authorization expires
Authorization is not permanent. Depending on your org's SAML session policies, you may need to re-authorize every 30 to 90 days — even if the token itself hasn't expired. When you see 404s reappear on previously-working org resources, the SSO authorization has lapsed; go back to the token page and click Configure SSO → Reauthorize.
08Verify everything works
Before trusting the setup, prove it works. Each check has an expected outcome; if any produces a different result, revisit the earlier steps.
Command-line smoke test
Test the token independently of the MCP with curl. This isolates auth problems from MCP problems.
# Confirm the token is valid and returns your username
curl -H "Authorization: Bearer $GITHUB_MCP_TOKEN" \
https://api.github.com/user
# Check the rate limit (should show 5000, not 60)
curl -H "Authorization: Bearer $GITHUB_MCP_TOKEN" \
https://api.github.com/rate_limit
# Verify access to a specific repo (adjust owner/repo)
curl -H "Authorization: Bearer $GITHUB_MCP_TOKEN" \
https://api.github.com/repos/your-org/your-repo
# Confirm you CAN'T do things the scope doesn't allow
# (should return 403 if Contents:Write not granted)
curl -X PUT -H "Authorization: Bearer $GITHUB_MCP_TOKEN" \
-d '{"message":"test","content":"aGVsbG8="}' \
https://api.github.com/repos/your-org/your-repo/contents/test.txt
MCP verification checklist
With the MCP client running, ask the AI to run each of these. The results tell you whether the auth chain is working end-to-end.
- List repos succeeds. "List my recent repositories." Expected: a list of repos matching your PAT's scoped access. If empty, your fine-grained PAT's repository selection is wrong.
- Read a file succeeds. "Read README.md from repo X." Expected: the file contents. If 404, the repo is outside your token's scope or (for orgs) SSO is not authorized.
- Write action fails when it should. "Create an issue titled 'test' in repo X." If your PAT only has read scopes, expected: 403 permission denied. If your PAT has write, it succeeds — delete the test issue after.
- Rate limit shows authenticated. Check
X-RateLimit-Limitin any response header (or the/rate_limitendpoint above). Should be 5,000 — not 60. If you see 60, the token isn't being sent. - Org access works if SSO required. "List issues in org-repo Y." Expected: a list of issues. If 404, complete the SSO authorization from Section 7.
09GitHub App setup for teams
Skip this section if you're happy with a fine-grained PAT. GitHub Apps are the better choice for teams because they decouple authentication from any individual person's account, scale rate limits per installation, and can be centrally administered.
Create the app
- In your org: Settings → Developer settings → GitHub Apps → New GitHub App. (Or in your personal account for personal-scope apps.)
- Fill in the app details:
- Name: something descriptive like
acme-ai-assistant. - Homepage URL: any URL that exists (your team's docs page works).
- Webhook: uncheck the "Active" box; MCP doesn't need webhooks.
- Name: something descriptive like
- Under Repository permissions, set the same scopes as Section 5 (Contents: Read, Pull requests: Read, Issues: Read, and so on).
- Under Where can this GitHub App be installed? choose "Only on this account".
- Click Create GitHub App.
Generate a private key and install the app
- On the app's settings page, scroll to Private keys and click Generate a private key. Save the downloaded
.pemfile to your secret store — you'll never see this content again if you lose it. - Note the app's App ID from the top of the settings page.
- Click Install App in the left sidebar. Install to your org and pick which repos it has access to (again: "Only select repositories" is safer than "All").
- After install, note the Installation ID from the URL (
installations/<id>/).
Configure the MCP with GitHub App credentials
Not every MCP server supports GitHub App auth natively yet — check your server's docs. When supported, the config usually looks like:
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_APP_ID": "123456",
"GITHUB_APP_INSTALLATION_ID": "78901234",
"GITHUB_APP_PRIVATE_KEY_PATH": "${HOME}/.secrets/github-app.pem"
}
}
}
}
The MCP server generates short-lived installation tokens (1-hour lifetime) from your app credentials automatically; you never handle the tokens directly.
One GitHub App can be installed on multiple orgs, each with its own installation ID. Register the MCP once per installation with distinct names (github_orga, github_orgb) — same app credentials, different installation IDs. This is much cleaner than juggling multiple PATs.
10Rate limits by auth type
GitHub's REST API rate limits vary meaningfully by auth type. For interactive AI use they rarely bite; for automation they can matter.
| Auth mode | REST API | Search API | GraphQL |
|---|---|---|---|
| Unauthenticated | 60 / hour / IP | 10 / minute | Not available |
| Fine-grained or classic PAT | 5,000 / hour | 30 / minute | 5,000 points / hour |
| GitHub App (installation token) | 5,000 / hour per installation | 30 / minute | 5,000 points / hour |
| GitHub Enterprise Cloud (metered) | 15,000 / hour | 30 / minute | Higher tier available |
Every GitHub API response includes rate-limit headers you can check:
# Check the rate limit status
curl -sI -H "Authorization: Bearer $GITHUB_MCP_TOKEN" \
https://api.github.com/user | grep -i x-ratelimit
# Expected output:
# x-ratelimit-limit: 5000
# x-ratelimit-remaining: 4998
# x-ratelimit-reset: 1755440000 (Unix timestamp)
# x-ratelimit-used: 2
# x-ratelimit-resource: core
If you see x-ratelimit-limit: 60, your token isn't being applied — the request is being treated as unauthenticated. Recheck that the MCP is actually reading the env var and sending the Authorization header.
GitHub also enforces secondary rate limits that aren't publicly documented in exact numbers — they trigger on bursts of many concurrent requests or many requests targeting the same resource. If you see 403 secondary rate limit exceeded, back off for a few minutes and add delays between requests. For heavy automation, respect the Retry-After header when present.
11Common errors and their fixes
Every error we've seen readers hit, with the actual fix.
The token is wrong, expired, or malformed. Check: is the token spelled correctly (they start with github_pat_ for fine-grained, ghp_ for classic)? Has it expired? Was it revoked? Test with curl as shown in Section 8. If the token itself works but the MCP fails, the env var isn't being loaded — restart your MCP client after any env changes.
The token is valid but doesn't have the required scope for the operation, OR the SSO authorization is missing. First check: does the requested action match your granted scopes? A write to Contents requires Contents: Read and write. If scopes look right, complete the SSO authorization from Section 7.
Almost always missing SAML SSO authorization. Go to your token's settings page and click Configure SSO for the org. Also possible: your fine-grained PAT's owner is set to your personal account rather than the org, so it can't see org repos at all. Check the token's resource owner setting.
The IP mention is the tell — you're hitting the unauthenticated rate limit (60/hour), which means the token isn't being sent. Check that the MCP's env config points to the actual env var and that the var is set. Restart the MCP client after fixing.
Different from the primary rate limit — triggered by bursts of concurrent requests or repeated hits on the same resource. Back off for 5–10 minutes; the MCP should retry automatically. For heavy automation, add explicit delays between requests and respect the Retry-After header when present.
Usually a branch protection rule blocking the action (direct pushes to main, requiring signed commits, requiring PRs). Not an auth problem — adjust either the workflow to go through a PR or the branch protection to allow the action. If the AI is creating PRs anyway, this error indicates it tried a direct push first; check your Skill's logic.
Public repos work without auth (against the anon rate limit); private repos require the token. If public repos succeed but private ones return 401, either your token isn't being sent, or your fine-grained PAT doesn't have those private repos in its selected list.
Some orgs enforce a policy requiring fine-grained tokens (or GitHub Apps) for org resources. Migrate: create a fine-grained PAT following Section 4, update the MCP config with the new token, revoke the classic PAT. No feature loss for standard AI workflows.
12Rotation checklist
Rotation is where "safely set up" becomes "safely maintained." Zero-downtime rotation is straightforward if you do the steps in the right order.
Two weeks before expiry
- Get a reminder. Set a calendar event 7 days before the token's expiration date. GitHub sends an email 7 days before, but calendar reminders are more reliable.
- Review whether the token is still needed. If the AI workflow it powered is no longer in use, this is the moment to revoke rather than renew.
- Note any scope changes. If the AI has taken on new responsibilities that need new scopes (or shed old ones that no longer do), plan the scope update as part of the rotation.
Rotation day
- Generate the new token first. Same scopes and permissions as the old one (or whatever changes you planned). Give it a new name including the year (
claude-mcp-personal-2027) so you can distinguish generations at a glance. - Update the secret store. Replace the value in your env-var config, 1Password entry, or Vault path. Don't remove the old value yet.
- Restart the MCP client. The MCP server picks up the new token at restart.
- Verify the new token works. Run the smoke tests from Section 8. Confirm the rate limit shows 5000 (proving the new token is being sent), confirm org resources still return correctly (SSO authorization may need to be re-done for the new token).
- Revoke the old token. GitHub Settings → Developer settings → Personal access tokens → the old token → Revoke. Once revoked, any process still using it starts failing immediately — which is how you discover forgotten workflows still depending on it.
- Update your calendar reminder. Set the next rotation reminder for 7 days before the new token's expiration.
Notice the order: new token generated → secret store updated → MCP restarted → verified → old token revoked. If you revoke first and then generate, there's a window where AI workflows fail. Doing it in the order above means zero downtime and no risk of locking yourself out.
13Frequently asked questions
For most individuals and prototypes: fine-grained PAT. Scoped to specific repos, forced expiration, granular permissions. For teams, shared infrastructure, or anything production: GitHub App. Higher rate limits per installation, admin-controlled, no dependency on any individual person's account. Classic PATs are only worth using when a specific tool doesn't support fine-grained tokens yet (rare in 2026).
See the table in Section 3 for the head-to-head comparison.
Your organization enforces SAML SSO and the token has not been authorized for it. Go to Settings → Developer settings → Personal access tokens, find the token, click Configure SSO, and authorize each SSO-protected org.
This is the single most common auth failure in enterprise setups and produces a misleading 404 rather than a clear auth error. See Section 7 for the full walkthrough.
For read-only workflows: Contents: Read, Metadata: Read (automatic), Pull requests: Read, Issues: Read. For AI that will create PRs and comment on issues: add Pull requests: Write and Issues: Write.
Never grant: Administration, Secrets, Deployments, Actions: Write. These give the AI destructive or credential-adjacent permissions with no realistic upside for most workflows. Full scope table in Section 5.
Fine-grained PATs are scoped to one owner (a personal account or one org), so multi-org requires multiple tokens. Options:
- Run one MCP instance per org with distinct names (
github_orga,github_orgb). - Use a GitHub App installed on each org — single app credential grants access to all installations.
- Fall back to a classic PAT that has organization access enabled (least safe, avoid).
The GitHub App approach scales best for teams. See Section 9.
Unauthenticated: 60 requests per hour per IP. Authenticated with a PAT: 5,000 per hour. GitHub App installation token: 5,000 per hour per installation, so multiple installations multiply the ceiling. GitHub Enterprise Cloud with metered billing: 15,000 per hour.
The Search API has its own tighter limit (30 requests per minute) regardless of auth type. Watch the X-RateLimit-Remaining response header to know where you stand in real time. Full table in Section 10.
Every 90 days for individual PATs (the maximum fine-grained expiration is now 366 days but shorter is safer). For GitHub Apps, rotate the app private key every 6 months.
Set a calendar reminder 7 days before expiry. The rotation process: create the new token, update your secret store, restart the MCP client, verify it works, then revoke the old token. Doing the steps in that order means zero downtime and no risk of getting locked out. Full checklist in Section 12.
Hit a GitHub API or MCP error during setup?
Our sister sites cover error messages and diagnostic recipes across the AI and Git ecosystem.