No hardcoded tokens. No op run wrappers. Just gh auth login and a small bridge script.
The GitHub MCP server needs a GITHUB_TOKEN or GITHUB_PERSONAL_ACCESS_TOKEN to authenticate. The naive approach is to hardcode it in your MCP config — but that's a plaintext secret sitting in a dotfile, one accidental commit away from leaking.
Use the GitHub CLI (gh) as your credential store. It keeps your token securely in the system keychain. A small Node.js bridge script fetches the token at runtime and passes it to the MCP server — nothing ever touches a config file.
# GitHub CLI
brew install gh
# Authenticate (stores token in system keychain)
gh auth loginWhen prompted, choose:
- GitHub.com
- HTTPS
- Login with a web browser
If you need org access, make sure to include the right scopes:
gh auth refresh --scopes repo,read:org,gistmkdir -p ~/.config/claude
cat > ~/.config/claude/github-mcp.mjs << 'EOF'
#!/usr/bin/env node
import { execSync, spawn } from "node:child_process";
try {
execSync("gh auth status", { stdio: "ignore" });
} catch {
console.error(
"Error: Not authenticated with GitHub CLI. Run 'gh auth login' first.",
);
process.exit(1);
}
const token = execSync("gh auth token", { encoding: "utf-8" }).trim();
const child = spawn("npx", ["-y", "@modelcontextprotocol/server-github"], {
env: { ...process.env, GITHUB_TOKEN: token },
stdio: "inherit",
shell: true,
});
child.on("exit", (code) => process.exit(code ?? 0));
EOF
chmod +x ~/.config/claude/github-mcp.mjsWhat this does:
- Checks
ghis authenticated — exits with a clear error if not - Fetches the token from
gh's secure keychain storage - Passes it as an env var to the GitHub MCP server process
- Proxies the exit code cleanly
Use the full path — ~ is not expanded by Claude's MCP runner:
claude mcp add github node /Users/YOUR_USERNAME/.config/claude/github-mcp.mjsReplace YOUR_USERNAME with your actual username, or use $HOME:
claude mcp add github node $HOME/.config/claude/github-mcp.mjsclaude mcp list
# github: node /Users/you/.config/claude/github-mcp.mjs - ✓ ConnectedThen launch Claude and test:
list my github repositories
list repositories for organisation YOUR_ORG_NAME
show open PRs in owner/repo
gh auth login
│
│ token stored in system keychain
│
▼
github-mcp.mjs (runs on claude startup)
│
│ gh auth token → fetches from keychain
│
▼
@modelcontextprotocol/server-github
│
│ GITHUB_TOKEN injected as env var
│
▼
Claude MCP tools
(list repos, create issues, review PRs, etc.)
✗ Failed to connect
- Make sure you used the full path, not
~ - Run
node /full/path/to/github-mcp.mjsdirectly to see the error
gh auth status shows no scopes
gh auth refresh --scopes repo,read:org,gistCan't list org repos
- Your PAT may need org approval — ask an org admin to approve the OAuth app under GitHub → Org Settings → Third-party Access
MCP server not appearing in Claude debug logs
tail -f ~/.claude/debug/*.txt | grep -i github- ❌ Hardcoded tokens in
~/.claude.json - ❌
op runwrappers - ❌
.envfiles with secrets - ❌ Custom shell aliases
Just gh auth login once, and plain claude works every time.