The local (stdio) GitHub MCP Server can log you in with OAuth instead of a Personal Access Token (PAT). On first use it walks you through GitHub's authorization flow in your browser and keeps the resulting token in memory only — nothing is written to disk.
Official released binaries and the ghcr.io/github/github-mcp-server image ship
with a registered GitHub OAuth application baked in, so on github.com you can
start the server with no token and no client ID at all. To target a different
host (GitHub Enterprise Server or ghe.com), or to use your own application,
pass --oauth-client-id (see Bring your own app).
OAuth login applies to the stdio server only. The remote server and the
httpcommand have their own authentication; see Remote Server.
- How it works
- Quick start
- Configuration reference
- Scope filtering
- Running in Docker
- Headless and device-code fallback
- URL elicitation and the security advisory
- Bring your own app
- GitHub Enterprise Server and ghe.com
- Building from source with baked-in credentials
The server prefers the authorization code flow with PKCE: it starts a loopback callback server on your machine, opens GitHub's authorization page, and exchanges the returned code for a token. GitHub requires a client secret at the token endpoint (for both OAuth Apps and GitHub Apps), so the exchange sends it together with the PKCE verifier. Because this is a public, distributed client, that secret is baked into the binary and is not truly confidential — PKCE is what secures the flow: it binds the authorization code to this one login attempt, so a code intercepted on the loopback redirect can't be redeemed anywhere else.
To present the authorization URL, the server uses the most secure channel your MCP client offers, in order:
- Open your browser automatically (native runs).
- URL elicitation — the client prompts you with the link out of band, so the URL never enters the model's context. Requires a client that supports MCP elicitation (e.g. VS Code 1.101+).
- A message in the first tool response — a last resort for clients without elicitation. This includes a security advisory.
If the authorization-code flow can't be used — for example, a container with no published callback port — the server falls back to the device-code flow.
GitHub App tokens that expire are refreshed transparently using the refresh token, so long-running sessions keep working without re-authorizing.
Native binary (recommended). Best experience: a random loopback port is used and your browser opens automatically. On github.com with an official build, no flags are needed:
github-mcp-server stdioWith your own application:
github-mcp-server stdio --oauth-client-id <YOUR_CLIENT_ID>VS Code (.vscode/mcp.json), using your own app:
{
"servers": {
"github": {
"command": "/path/to/github-mcp-server",
"args": ["stdio", "--oauth-client-id", "<YOUR_CLIENT_ID>"]
}
}
}For Docker, see Running in Docker — containers need a fixed callback port.
OAuth login is configured with these stdio flags (each has an environment
variable equivalent). Flags apply only to the stdio command.
| Flag | Environment variable | Description |
|---|---|---|
--oauth-client-id |
GITHUB_OAUTH_CLIENT_ID |
OAuth App or GitHub App client ID. Enables OAuth login when no token is set. Defaults to the baked-in app on github.com for official builds. |
--oauth-client-secret |
GITHUB_OAUTH_CLIENT_SECRET |
Client secret, if your app requires one. For distributed clients this is a public, non-confidential credential. |
--oauth-scopes |
GITHUB_OAUTH_SCOPES |
Comma-separated scopes to request. Also filters tools to those scopes. Defaults to the full supported set. |
--oauth-callback-port |
GITHUB_OAUTH_CALLBACK_PORT |
Fixed local port for the callback server. Defaults to a random port; set a fixed port when mapping it through Docker. |
A static token still takes precedence: if GITHUB_PERSONAL_ACCESS_TOKEN is set,
the server uses it and skips OAuth entirely.
The scopes you request determine which tools are exposed. Requesting the full
supported set (the default) hides no tools. Narrowing --oauth-scopes both
narrows the token's grant and filters out tools that would need a scope you
didn't request, so the tool list reflects what the token can actually do.
For example, requesting only repo,read:org hides tools that require gist,
workflow, notifications, and so on.
A container can't reach a random loopback port on your host, so Docker OAuth needs a fixed callback port that you publish into the container. Use port 8085 to match the official app's registered callback URL.
docker run -i --rm \
-p 127.0.0.1:8085:8085 \
-e GITHUB_OAUTH_CALLBACK_PORT=8085 \
ghcr.io/github/github-mcp-serverVS Code (.vscode/mcp.json):
{
"servers": {
"github": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-p", "127.0.0.1:8085:8085",
"-e", "GITHUB_OAUTH_CALLBACK_PORT",
"ghcr.io/github/github-mcp-server"
],
"env": { "GITHUB_OAUTH_CALLBACK_PORT": "8085" }
}
}
}Because the container can't open your host browser, the authorization URL
arrives via URL elicitation or the
tool-response message. After you authorize, your browser hits
localhost:8085, which Docker forwards into the container's callback.
If you bring your own app for Docker, register its callback URL as exactly
http://localhost:8085/callback.
Two safety properties to be aware of with a fixed port:
- Publish to loopback only (
-p 127.0.0.1:8085:8085, not-p 8085:8085). Inside a container the callback necessarily listens on all interfaces, so a plain publish would expose the authorization code to your network. The server logs a warning reminding you of this when it binds inside a container.- A busy port is fatal, by design. With a fixed port, if the server can't bind it (another process already holds it), it stops with an error rather than silently falling back to the device flow. A port you didn't get could belong to another user's process positioned to receive the redirect, so the server refuses to continue. Free the port or choose a different
--oauth-callback-port.
When there's no usable browser or callback — a remote shell, CI, or a container started without a published port — the server uses GitHub's device-code flow. You'll get a short code and a verification URL to open on any device:
Visit https://github.com/login/device and enter the code WDJB-MJHT to authorize
the GitHub MCP Server.
The server polls GitHub until you finish authorizing, then continues. No callback port is involved, so this works anywhere.
URL elicitation lets your MCP client present the authorization URL to you directly, keeping it out of the model's context — the model never sees the link or any code embedded in it. This is the most secure way to hand off the authorization step.
If your client doesn't support elicitation, the server falls back to placing the URL in a tool response and appends a short advisory:
Note: your MCP client does not appear to support secure URL elicitation. For improved security, consider asking your agent, CLI, or IDE to add it (for example, by opening an issue).
If you see this, your authorization still works — but consider asking your client vendor to add elicitation support.
You need your own application when targeting a non-github.com host, or when you'd rather not use the baked-in app. Either application type works:
- Create an OAuth App — simplest to set up. Grants the scopes you request.
- Register a GitHub App — finer-grained, per-resource permissions and short-lived tokens that refresh automatically. Enable Device Flow in the app settings if you want the headless fallback.
When registering, set the authorization callback URL:
- Native runs use a random loopback port. For loopback redirects GitHub does
not require the callback port to match, so registering
http://localhost/callbackis sufficient. - Docker / fixed port must match exactly: register
http://localhost:8085/callback(or whichever port you publish).
Then pass the client ID (and secret, only if your app requires one):
github-mcp-server stdio \
--oauth-client-id <YOUR_CLIENT_ID> \
--oauth-client-secret <YOUR_CLIENT_SECRET>The baked-in app is registered on github.com only, so it is not used when you
set a custom host. GitHub Enterprise Server and ghe.com (Enterprise Cloud with
data residency) users must bring their own app registered on that host and
pass --oauth-client-id.
Set the host with --gh-host / GITHUB_HOST; the server derives the OAuth
authorization, token, and device endpoints from it, so login is directed at your
instance's authorization server rather than github.com:
github-mcp-server stdio \
--gh-host https://github.example.com \
--oauth-client-id <YOUR_CLIENT_ID>- For GitHub Enterprise Server, prefix the host with
https://. - For
ghe.com, usehttps://YOURSUBDOMAIN.ghe.com.
Register the app's callback URL on the same host (e.g.
http://localhost/callback for native runs, or http://localhost:8085/callback
for Docker).
Official builds embed the default OAuth client via linker flags at build time, so
they are not present in the source tree. To produce your own build with embedded
credentials, set them with -ldflags:
go build -ldflags "\
-X github.com/github/github-mcp-server/internal/buildinfo.OAuthClientID=<CLIENT_ID> \
-X github.com/github/github-mcp-server/internal/buildinfo.OAuthClientSecret=<CLIENT_SECRET>" \
./cmd/github-mcp-serverWithout these, a source build simply has no baked-in app and expects
--oauth-client-id (or a PAT) at runtime.