GitHub MCP Server: Remote, Local, Toolsets and Lockdown Mode
The GitHub MCP Server is GitHub’s official Model Context Protocol server. It lets Claude Code, Codex and Cursor read and act on pull requests, issues, GitHub Actions logs and security alerts. The hosted endpoint https://api.githubcopilot.com/mcp/ works with a personal access token. What matters is scope: expose only the toolsets a task needs, and enable lockdown mode on public repositories.
PR #412 has been red since lunch: 4,000 lines of failing log and six review comments. You would rather hand the loop to your agent, but only if it can read Actions logs and review threads, and only if you trust what it may push and merge.
This page is for developers wiring the server into their agent and tech leads deciding the shared configuration: token, toolsets, and the tools the agent may never call.
What a scoped GitHub MCP setup gives you
Section titled “What a scoped GitHub MCP setup gives you”- A tested install for Claude Code, Codex and Cursor that keeps the token out of the repository.
- A PAT scope table that grants each workflow only the permissions it uses.
- Toolset, exclusion and lockdown settings that expose only what a task needs and filter content from outsiders.
- Prompts that turn a red pull request into a pushed fix and answer review threads, without merging.
- The GitLab equivalents, for teams that host code there.
Should the agent use GitHub MCP or the gh CLI?
Section titled “Should the agent use GitHub MCP or the gh CLI?”All three agents run shell commands, so gh and git need no MCP server. The GitHub MCP Server earns its place when the agent needs typed, paginated access to GitHub state, or when it runs somewhere without a shell. Many teams run both.
| Job | Best route | Why |
|---|---|---|
| Commit, branch, rebase, diff in the working tree | Local git through the agent’s shell | Fastest, no API calls, no token |
| Read one failing CI log | gh run view RUN_ID --log-failed, or get_job_logs with failed_only and tail_lines | gh costs fewer tokens; the MCP tool can trim the log for you |
| Read review threads and reply to each one | GitHub MCP (pull_request_read method get_review_comments, add_reply_to_pull_request_comment, pull_request_review_write method resolve_thread) | Thread IDs and replies are awkward to script with gh |
| Triage 50 issues into a table | GitHub MCP (search_issues, issue_read) | Structured results, pagination handled by the server |
| An agent with no shell (a desktop chat client, a hosted agent) | GitHub MCP | It is the only route |
Structured git history in a client that cannot run git | The reference Git server, uvx mcp-server-git --repository . (PyPI only) | See reference MCP servers |
Both gh and the MCP server spend the same budget when they use the same token: the primary rate limit for authenticated users is 5,000 requests per hour on github.com.
Remote or local: which GitHub MCP server should you run?
Section titled “Remote or local: which GitHub MCP server should you run?”GitHub publishes one codebase in two forms. The remote server is the default choice; the local server is for GitHub Enterprise Server, air-gapped machines and teams that want OAuth without a PAT.
Remote (https://api.githubcopilot.com/mcp/) | Local (Docker ghcr.io/github/github-mcp-server or a release binary) | |
|---|---|---|
| Auth in Claude Code, Codex, Cursor | PAT in an Authorization: Bearer header, per GitHub’s install guides. OAuth works only in hosts that registered a GitHub App or OAuth App | Browser OAuth login built into the official image, no token needed; token kept in memory only. A PAT in GITHUB_PERSONAL_ACCESS_TOKEN takes precedence. github.com only; GitHub Enterprise Server and ghe.com need your own OAuth or GitHub App, or a PAT |
| Toolset scoping | URL path /mcp/x/TOOLSET and /readonly, or X-MCP-Toolsets, X-MCP-Tools, X-MCP-Exclude-Tools, X-MCP-Readonly headers | --toolsets, --tools, --exclude-tools, --read-only flags or GITHUB_TOOLSETS, GITHUB_TOOLS, GITHUB_EXCLUDE_TOOLS, GITHUB_READ_ONLY |
| Lockdown mode | X-MCP-Lockdown: true header | --lockdown-mode or GITHUB_LOCKDOWN_MODE=1 |
| GitHub Enterprise | Enterprise Cloud with data residency at https://copilot-api.SUBDOMAIN.ghe.com/mcp; not Enterprise Server | Both, via --gh-host or GITHUB_HOST (HTTPS enforced) |
| Extra tools | create_pull_request_with_copilot, Copilot Spaces, github_support_docs_search | Not available |
| Needs | Nothing installed | Docker running, or the binary on PATH |
Install the GitHub MCP server in Claude Code, Codex and Cursor
Section titled “Install the GitHub MCP server in Claude Code, Codex and Cursor”The remote server works the same way in all three agents: one URL and a bearer token. Only the config syntax differs.
-
Create a fine-grained personal access token at Settings > Developer settings > Personal access tokens, limited to the repositories the agent works on. Pick permissions from the scope table in the next section.
-
Export it in your shell profile, not in a file inside the repository:
Terminal window # ~/.zshrc or ~/.bashrcexport GITHUB_PAT="github_pat_..." -
Register the server in your agent:
Terminal window # Terminal. Project scope writes .mcp.json; the single quotes keep ${GITHUB_PAT}# unexpanded, so teammates share the file and each supplies their own token.claude mcp add -s project --transport http github https://api.githubcopilot.com/mcp/ \-H 'Authorization: Bearer ${GITHUB_PAT}'Tested on 2.1.283: the resulting
.mcp.jsonholds"Authorization": "Bearer ${GITHUB_PAT}", which Claude Code expands at connect time. Drop-s projectto keep the entry private to you (the defaultlocalscope).Terminal window # Terminal. Writes [mcp_servers.github] into ~/.codex/config.tomlcodex mcp add github --url https://api.githubcopilot.com/mcp/ \--bearer-token-env-var GITHUB_PATTested on 0.157.1. Codex reads the token from the variable each time it connects, so nothing secret lands in
config.toml.In
~/.cursor/mcp.json, the user-global file. Never commit it:{"mcpServers": {"github": {"url": "https://api.githubcopilot.com/mcp/","headers": { "Authorization": "Bearer ${env:GITHUB_PAT}" }}}}This is the shape from GitHub’s Cursor guide, which says the GitHub server “currently requires a Personal Access Token” in Cursor. The
${env:NAME}interpolation comes from Cursor’s MCP docs as quoted in our research (secondary, not tested in a Cursor binary), so confirm in Settings > MCP thatgithublists tools. -
Verify the connection. In Claude Code run
claude mcp get github(it health-checks the server) or/mcpinside a session. In Codex run/mcpin the TUI. In Cursor open the MCP section of the settings and check thatgithublists tools. Then ask the agent:Call get_me and tell me which GitHub user you are authenticated as.
Install through a plugin instead
Section titled “Install through a plugin instead”A plugin bundles the same remote server with no JSON to write. Each expects its own variable name, the most common reason a plugin shows zero tools.
| Agent | Command | Token variable it reads |
|---|---|---|
| Claude Code | claude plugin install github@claude-plugins-official | GITHUB_PERSONAL_ACCESS_TOKEN (exactly this name) |
| Codex | codex plugin add github@openai-curated | GITHUB_PAT_TOKEN |
| Cursor | /add-plugin github | Not documented in our sources; check the plugin’s mcp.json |
The Codex plugin install was not tested end to end on 2026-09-26; if it fails, use codex mcp add above.
Local Docker server with OAuth and no token
Section titled “Local Docker server with OAuth and no token”To skip the PAT entirely, run the local server. The official image carries GitHub’s OAuth app credentials, opens a browser login on first use and needs a fixed callback port published to loopback:
# Claude Code, terminal (GitHub's documented line)claude mcp add github -e GITHUB_OAUTH_CALLBACK_PORT=8085 -- docker run -i --rm \ -p 127.0.0.1:8085:8085 -e GITHUB_OAUTH_CALLBACK_PORT ghcr.io/github/github-mcp-serverThe Codex and Cursor equivalents pass the same docker run arguments as command/args with GITHUB_OAUTH_CALLBACK_PORT = "8085" in env. Without a published port the server falls back to GitHub’s device-code flow and prints a code to enter at github.com/login/device.
GitHub Copilot CLI already has it
Section titled “GitHub Copilot CLI already has it”Copilot CLI ships the GitHub MCP server pre-installed as github-mcp-server, with read-only tools on by default. Check it with /mcp show github-mcp-server, widen it per session with copilot --add-github-mcp-toolset actions, or turn it off with copilot --disable-builtin-mcps. The GitHub Copilot page covers the cloud agent, whose GitHub server uses a token scoped read-only to the current repository.
Which PAT scopes does the GitHub MCP server need?
Section titled “Which PAT scopes does the GitHub MCP server need?”The server can do what the token allows and nothing more, so the token is your real permission boundary. Grant by job:
| Job | Classic PAT scope | Fine-grained PAT permission (selected repos only) |
|---|---|---|
| Read code, issues, PRs, CI logs | repo | Contents, Issues, Pull requests, Actions: read |
| Comment, open PRs, push files | repo | Contents, Issues, Pull requests: read and write |
Change a file under .github/workflows/ | repo + workflow | Workflows: read and write |
| Org teams and members | read:org | Members (organization permission): read |
| Gists, notifications, classic projects | gist, notifications, project | the matching permission |
Two behaviours are worth knowing before you debug a missing tool:
- Classic PATs filter tools at startup. The server reads the token’s scopes from the
X-OAuth-Scopesheader and hides tools that need a scope you did not grant. Arepo-only token shows no gist or notification tools. Check what a token carries withcurl -sI -H "Authorization: Bearer $GITHUB_PAT" https://api.github.com/user | grep -i x-oauth-scopes. - Fine-grained PATs show every tool. The API enforces permissions at call time, so the agent sees a tool, calls it and gets a 403. That is expected; narrow the tool list with toolsets instead.
Limit the GitHub MCP server to the toolsets a task needs
Section titled “Limit the GitHub MCP server to the toolsets a task needs”By default the server loads context, repos, issues, pull_requests and users. A CI fix also needs actions; triage needs nothing that writes. Fewer tools means better tool choice and less room for a mistake.
On the remote server you have two levers:
- URL path, one toolset per URL:
https://api.githubcopilot.com/mcp/x/actions, and/readonlyon the end of any of them (https://api.githubcopilot.com/mcp/x/actions/readonly,https://api.githubcopilot.com/mcp/readonly). - Headers, for combinations:
X-MCP-Toolsets: repos,issues,pull_requests,actions,X-MCP-Toolsfor single tools,X-MCP-Readonly: true.
The path accepts a single toolset only, so a CI-fix setup that combines four uses the header:
claude mcp add -s project --transport http github https://api.githubcopilot.com/mcp/ \ -H 'Authorization: Bearer ${GITHUB_PAT}' \ -H "X-MCP-Toolsets: context,repos,issues,pull_requests,actions" \ -H "X-MCP-Lockdown: true"[mcp_servers.github]url = "https://api.githubcopilot.com/mcp/"bearer_token_env_var = "GITHUB_PAT"http_headers = { "X-MCP-Toolsets" = "context,repos,issues,pull_requests,actions", "X-MCP-Lockdown" = "true" }The key is http_headers. Codex silently ignores a headers key, and the toolsets never reach the server (tested on 0.157.1 with codex mcp get github --json).
{ "mcpServers": { "github": { "url": "https://api.githubcopilot.com/mcp/", "headers": { "Authorization": "Bearer ${env:GITHUB_PAT}", "X-MCP-Toolsets": "context,repos,issues,pull_requests,actions", "X-MCP-Lockdown": "true" } } }}On the local server the same scoping is --toolsets context,repos,issues,pull_requests,actions (or GITHUB_TOOLSETS), plus --read-only. Read-only mode wins over an explicit --tools list: a write tool you name is still skipped.
What the GitHub MCP server costs in context
Section titled “What the GitHub MCP server costs in context”Tool search is on by default in Claude Code and Codex, so the schemas are loaded on demand. claude plugin details github shows about zero always-on tokens, but that command does not count MCP tool schemas (checked on 2.1.283), so measure with /context instead. The cost that bites is tool results: a full Actions log or a 30-item issue list lands in the context window. Run /context in Claude Code before and after a CI-fix run to see it, and tell the agent to call get_job_logs with failed_only: true and tail_lines set, as the prompt below does. For more on trimming results, see reducing MCP token cost.
Turn on lockdown mode for public repositories
Section titled “Turn on lockdown mode for public repositories”An agent that reads issues and comments on a public repository reads text written by strangers, and that text can carry instructions. Lockdown mode is GitHub’s filter for that: for public repositories the server checks whether each item’s author has push access, and withholds content from people who do not. Private repositories are unaffected.
What the agent sees under lockdown:
issue_read(get) andpull_request_read(get, get_diff, get_files, get_commits) return an error when the author lacks push access.- Comments, sub-issues, review comments and reviews are filtered: items by users without push access are dropped.
- Content from
github-actions[bot]andcopilotalways passes, so CI output stays visible.
Switch it on with --lockdown-mode or GITHUB_LOCKDOWN_MODE=1 locally, or X-MCP-Lockdown: true on the remote server. In HTTP mode an operator’s flag is an upper bound: the header can turn lockdown on but not off.
Fix a red pull request from its Actions log
Section titled “Fix a red pull request from its Actions log”This is the full usage example. It works the same in Claude Code, Codex and Cursor once the server is connected with the actions toolset and your local checkout is on the PR branch.
What you should see, with the tool names observed on the live remote server on 2026-09-26:
actions_listreturns the branch’s runs, and the agent picks the newest withconclusion: failure.get_job_logsreturns a few hundred lines of each failed job instead of thousands.- A new workflow run starts after the push, and the agent reports the evidence and stops.
The agent reads GitHub through MCP but edits the local checkout, so every change shows in git diff and passes your local hooks.
From issue to merge: the GitHub MCP workflow
Section titled “From issue to merge: the GitHub MCP workflow”The CI fix above is one step of a loop from issue to merged pull request, with a person at the one gate that matters.
-
Triage. The agent reads open issues with
search_issuesandissue_read, and proposes labels and a priority. With theissuestoolset in read-only mode, it cannot change anything yet. -
Branch and build. You pick an issue. The agent creates a local branch, writes the change and the tests, and opens a pull request with
create_pull_request, linking the issue. -
CI-log-driven fix. When checks fail, the agent runs the prompt above: read the failed log, reproduce, fix, push. It repeats until the required checks pass.
-
Review comments. Reviewers, human or AI review bots, leave comments. The agent reads them with
pull_request_readmethodget_review_comments, which also returns each thread’sthreadId, answers each thread withadd_reply_to_pull_request_comment, pushes the fixes and resolves a thread withpull_request_review_writemethodresolve_threadonly when the change is in. -
Merge. A person approves and merges once branch protection is satisfied. The agent never calls
merge_pull_request(see the guardrail below).
Keep merge out of the agent’s reach
Section titled “Keep merge out of the agent’s reach”The token can merge if the user can, so remove the tool on the client side as well:
In .claude/settings.json. A deny rule on a bare tool name removes the tool from the model’s context:
{ "permissions": { "deny": ["mcp__github__merge_pull_request", "mcp__github__delete_file"] }}# ~/.codex/config.toml, under the existing [mcp_servers.github] tabledisabled_tools = ["merge_pull_request", "delete_file"]Tested on 0.157.1: codex mcp get github --json lists both under disabled_tools.
Cursor has no per-tool deny setting in our sources, so exclude the tools on the server side with the X-MCP-Exclude-Tools header in ~/.cursor/mcp.json:
{ "mcpServers": { "github": { "url": "https://api.githubcopilot.com/mcp/", "headers": { "Authorization": "Bearer ${env:GITHUB_PAT}", "X-MCP-Exclude-Tools": "merge_pull_request,delete_file" } } }}GitHub’s server-configuration guide documents the header and states that exclusions take precedence over toolsets and X-MCP-Tools, so an excluded tool stays hidden even when its toolset is enabled.
The X-MCP-Exclude-Tools header works in any client that sends custom headers, and the local server takes the same list as --exclude-tools or GITHUB_EXCLUDE_TOOLS. Use it as a server-side complement to the Claude Code deny rule and the Codex disabled_tools list: the tool is removed before any client sees it.
Branch protection with required status checks and a required approving review is the backstop: even a misconfigured agent cannot merge past it.
How do you verify the agent’s GitHub work?
Section titled “How do you verify the agent’s GitHub work?”You do not read every line the agent pushes. You check the evidence; the repository enforces the rest.
- Reproduce before fix, pass after fix. The prompt forces the agent to show the failing command and then the same command passing. A fix with no reproduction goes back.
- Required checks are the oracle. Branch protection marks the CI jobs required, so “green” means the same suite that caught the failure now passes. Watch it with
gh pr checks 412 --watch. - One reviewer signs off. A person approves the pull request, reading the agent’s report and the test diff first; see reviewing agent pull requests for what to check.
- Every action is auditable. Comments, replies and pushes appear under the token’s user; a machine-user token for automated runs separates agent actions from yours.
- Rollback is a revert. The agent works in normal commits, so
git revertundoes any change.
GitLab: the official MCP server and zereight/gitlab-mcp
Section titled “GitLab: the official MCP server and zereight/gitlab-mcp”GitLab teams have two options. GitLab’s own server is remote at https://gitlab.com/api/v4/mcp (or https://YOUR_GITLAB/api/v4/mcp for self-managed). It is a beta GitLab Duo feature for Premium and Ultimate, according to secondary sources (search extracts of GitLab’s docs, which could not be fetched on 2026-09-26). For other tiers, the community zereight/gitlab-mcp (2.0k stars, npm @zereight/mcp-gitlab 2.1.66 on 2026-09-26) runs locally with a PAT.
# Official, remote with OAuth (the gitlab plugin in claude-plugins-official uses this URL)claude mcp add --transport http gitlab https://gitlab.com/api/v4/mcp
# Community, local with a PAT exported as GITLAB_PAT in your shell profile.# Pipeline tools are opt-in: GITLAB_TOOLSETS=pipelines (legacy: USE_PIPELINE=true)claude mcp add -s project gitlab -e 'GITLAB_PERSONAL_ACCESS_TOKEN=${GITLAB_PAT}' \ -e GITLAB_API_URL=https://gitlab.com/api/v4 -e GITLAB_TOOLSETS=pipelines \ -- npx -y @zereight/mcp-gitlabTested on 2.1.283: the single quotes keep the reference unexpanded, and .mcp.json holds "${GITLAB_PAT}", not the token.
# ~/.codex/config.toml. Export GITLAB_PERSONAL_ACCESS_TOKEN in your shell profile;# env_vars passes it through from the shell, so the token never lands in this file.# Pipeline tools are opt-in: GITLAB_TOOLSETS=pipelines (legacy: USE_PIPELINE=true)[mcp_servers.gitlab]command = "npx"args = ["-y", "@zereight/mcp-gitlab"]env_vars = ["GITLAB_PERSONAL_ACCESS_TOKEN"]env = { GITLAB_API_URL = "https://gitlab.com/api/v4", GITLAB_TOOLSETS = "pipelines" }Tested on 0.157.1: codex mcp get gitlab --json lists the token under env_vars and no value.
For the official remote server, codex mcp add gitlab --url https://gitlab.com/api/v4/mcp followed by codex mcp login gitlab is the matching syntax, but it was not tested against GitLab’s OAuth.
In the user-global ~/.cursor/mcp.json, which you never commit:
{ "mcpServers": { "gitlab": { "command": "npx", "args": ["-y", "@zereight/mcp-gitlab"], "env": { "GITLAB_PERSONAL_ACCESS_TOKEN": "${env:GITLAB_PAT}", "GITLAB_API_URL": "https://gitlab.com/api/v4", "GITLAB_TOOLSETS": "pipelines" } } }}${env:NAME} interpolation is from Cursor’s docs as quoted in our research, not a local test.
GITLAB_API_URL must be the API root (/api/v4), not the web address. For a read-only agent, set GITLAB_PERMISSION_MODE=readonly; modify allows create and update without delete tools. The GitLab PAT scopes follow the same rule as GitHub’s: read_api and read_repository for reading, api only when the agent must write.
What breaks with GitHub MCP, and how do you recover?
Section titled “What breaks with GitHub MCP, and how do you recover?”| Symptom | Cause | Recovery |
|---|---|---|
Bad credentials or 401 | Expired, revoked or mistyped token; or the variable is not set in the shell that launched the agent | Create a new PAT, export it, restart the agent from that shell. For fine-grained tokens, check the repository list |
| Plugin installed, zero tools | The plugin reads a fixed variable (GITHUB_PERSONAL_ACCESS_TOKEN for Claude Code, GITHUB_PAT_TOKEN for Codex; for Cursor, check the plugin’s mcp.json) | Export the exact name the plugin expects |
| A tool you expect is missing | A classic PAT without the scope hides it, or read-only mode or a toolset filter is on | Check x-oauth-scopes, the URL path and the X-MCP-* headers |
Tool visible, call returns 403 | Fine-grained PATs show every tool; the API refuses the call | Grant the permission, or drop the toolset so the agent stops trying |
search_code finds nothing on your branch | Only the default branch is indexed for code search, and forks only in some cases | Search locally with rg for branch code |
403 or 429 rate-limit errors mid-run | The token’s 5,000 requests per hour is shared by gh, scripts and the MCP server | Paginate with small pages, use gh for bulk reads, give automation its own token |
claude mcp add github https://… connects to nothing | Without --transport http, Claude Code stores the URL as a stdio command | Remove it and add again with --transport http |
| Docker OAuth login never completes | The callback port is not published to loopback | Add -p 127.0.0.1:8085:8085 and GITHUB_OAUTH_CALLBACK_PORT=8085, or use the device-code fallback |
| A contributor’s issue “does not exist” | Lockdown mode hides content from authors without push access | Expected on public repositories. First choice: read the item yourself (gh issue view N). Lockdown cannot be scoped to one repository: a session without --lockdown-mode, GITHUB_LOCKDOWN_MODE or X-MCP-Lockdown stops filtering every public repository in that session. If the agent must read it, start that session read-only (https://api.githubcopilot.com/mcp/readonly, X-MCP-Readonly: true or --read-only locally) with no write tools, then go back to your lockdown config. The Triage role does not help: it has no push access, only Write and above do |
For connection problems that are not specific to GitHub, see MCP connection issues.
Where to go next with GitHub MCP
Section titled “Where to go next with GitHub MCP”- Prerequisite: how MCP works across the three agents.
- Next step: review the pull requests your agent opens, then run the same CI-fix loop unattended with headless agents in CI.