Skip to content

Commit a9aa0ce

Browse files
docs(oauth): document stdio OAuth login; make PAT optional in install config
Add a dedicated Local Server OAuth Login guide (docs/oauth-login.md) covering the PKCE/device flows, display channels and the URL-elicitation security advisory, scope-based tool filtering, the fixed-port Docker recipe and its loopback/port-safety behavior, bringing your own OAuth or GitHub App, and the GitHub Enterprise Server / ghe.com requirement to register an app on that host (custom --gh-host directs login at that instance's authorization server). Reflect that the local server now logs in with OAuth by default on github.com: - README: make the stdio Docker install badges OAuth-first (fixed callback port 8085 published to loopback), drop the PAT prompt, and reframe the PAT as an optional alternative with a pointer to the new guide. - server.json: make GITHUB_PERSONAL_ACCESS_TOKEN optional and publish the OAuth callback port so the registry default works without a token. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 3709f58 commit a9aa0ce

3 files changed

Lines changed: 284 additions & 6 deletions

File tree

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,14 +176,15 @@ GitHub Enterprise Server does not support remote server hosting. Please refer to
176176

177177
## Local GitHub MCP Server
178178

179-
[![Install with Docker in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=github&inputs=%5B%7B%22id%22%3A%22github_token%22%2C%22type%22%3A%22promptString%22%2C%22description%22%3A%22GitHub%20Personal%20Access%20Token%22%2C%22password%22%3Atrue%7D%5D&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22-e%22%2C%22GITHUB_PERSONAL_ACCESS_TOKEN%22%2C%22ghcr.io%2Fgithub%2Fgithub-mcp-server%22%5D%2C%22env%22%3A%7B%22GITHUB_PERSONAL_ACCESS_TOKEN%22%3A%22%24%7Binput%3Agithub_token%7D%22%7D%7D) [![Install with Docker in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=github&inputs=%5B%7B%22id%22%3A%22github_token%22%2C%22type%22%3A%22promptString%22%2C%22description%22%3A%22GitHub%20Personal%20Access%20Token%22%2C%22password%22%3Atrue%7D%5D&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22-e%22%2C%22GITHUB_PERSONAL_ACCESS_TOKEN%22%2C%22ghcr.io%2Fgithub%2Fgithub-mcp-server%22%5D%2C%22env%22%3A%7B%22GITHUB_PERSONAL_ACCESS_TOKEN%22%3A%22%24%7Binput%3Agithub_token%7D%22%7D%7D&quality=insiders) [![Install with Docker in Visual Studio](https://img.shields.io/badge/Visual_Studio-Install_Server-C16FDE?style=flat-square&logo=visualstudio&logoColor=white)](https://aka.ms/vs/mcp-install?%7B%22name%22%3A%22github%22%2C%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22-e%22%2C%22GITHUB_PERSONAL_ACCESS_TOKEN%22%2C%22ghcr.io%2Fgithub%2Fgithub-mcp-server%22%5D%7D)
179+
[![Install with Docker in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22-p%22%2C%22127.0.0.1%3A8085%3A8085%22%2C%22-e%22%2C%22GITHUB_OAUTH_CALLBACK_PORT%22%2C%22ghcr.io%2Fgithub%2Fgithub-mcp-server%22%5D%2C%22env%22%3A%7B%22GITHUB_OAUTH_CALLBACK_PORT%22%3A%228085%22%7D%7D) [![Install with Docker in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%7B%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22-p%22%2C%22127.0.0.1%3A8085%3A8085%22%2C%22-e%22%2C%22GITHUB_OAUTH_CALLBACK_PORT%22%2C%22ghcr.io%2Fgithub%2Fgithub-mcp-server%22%5D%2C%22env%22%3A%7B%22GITHUB_OAUTH_CALLBACK_PORT%22%3A%228085%22%7D%7D&quality=insiders) [![Install with Docker in Visual Studio](https://img.shields.io/badge/Visual_Studio-Install_Server-C16FDE?style=flat-square&logo=visualstudio&logoColor=white)](https://aka.ms/vs/mcp-install?%7B%22name%22%3A%22github%22%2C%22command%22%3A%22docker%22%2C%22args%22%3A%5B%22run%22%2C%22-i%22%2C%22--rm%22%2C%22-p%22%2C%22127.0.0.1%3A8085%3A8085%22%2C%22-e%22%2C%22GITHUB_OAUTH_CALLBACK_PORT%3D8085%22%2C%22ghcr.io%2Fgithub%2Fgithub-mcp-server%22%5D%7D)
180180

181181
### Prerequisites
182182

183183
1. To run the server in a container, you will need to have [Docker](https://www.docker.com/) installed.
184184
2. Once Docker is installed, you will also need to ensure Docker is running. The Docker image is available at `ghcr.io/github/github-mcp-server`. The image is public; if you get errors on pull, you may have an expired token and need to `docker logout ghcr.io`.
185-
3. Lastly you will need to [Create a GitHub Personal Access Token](https://github.com/settings/personal-access-tokens/new).
186-
The MCP server can use many of the GitHub APIs, so enable the permissions that you feel comfortable granting your AI tools (to learn more about access tokens, please check out the [documentation](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)).
185+
3. **Authentication.** On github.com you don't need to create anything up front — the one-click buttons above log you in with OAuth on first use (a browser-based flow; the token is kept in memory only). The Docker buttons publish a fixed callback port (`127.0.0.1:8085`) so the container's login callback is reachable. See **[Local Server OAuth Login](docs/oauth-login.md)** for how it works, headless/device-code fallback, and bringing your own OAuth or GitHub App (required for GitHub Enterprise Server and `ghe.com`).
186+
187+
Prefer a token? You can still authenticate with a [GitHub Personal Access Token](https://github.com/settings/personal-access-tokens/new) by setting `GITHUB_PERSONAL_ACCESS_TOKEN` instead (it takes precedence over OAuth). The MCP server can use many of the GitHub APIs, so enable the permissions that you feel comfortable granting your AI tools (to learn more about access tokens, please check out the [documentation](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)).
187188

188189
<details><summary><b>Handling PATs Securely</b></summary>
189190

@@ -281,6 +282,8 @@ Install in GitHub Copilot on other IDEs (JetBrains, Visual Studio, Eclipse, etc.
281282

282283
Add the following JSON block to your IDE's MCP settings.
283284

285+
> The examples below authenticate with a Personal Access Token. To log in with OAuth instead (no token to create or store), see **[Local Server OAuth Login](docs/oauth-login.md)** — in Docker it needs a fixed callback port, as the one-click buttons above show.
286+
284287
```json
285288
{
286289
"mcp": {

docs/oauth-login.md

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
# Local Server OAuth Login (stdio)
2+
3+
The local (stdio) GitHub MCP Server can log you in with OAuth instead of a
4+
Personal Access Token (PAT). On first use it walks you through GitHub's
5+
authorization flow in your browser and keeps the resulting token **in memory
6+
only** — nothing is written to disk.
7+
8+
Official released binaries and the `ghcr.io/github/github-mcp-server` image ship
9+
with a registered GitHub OAuth application baked in, so on **github.com** you can
10+
start the server with no token and no client ID at all. To target a different
11+
host (GitHub Enterprise Server or `ghe.com`), or to use your own application,
12+
pass `--oauth-client-id` (see [Bring your own app](#bring-your-own-app)).
13+
14+
> OAuth login applies to the **stdio** server only. The remote server and the
15+
> `http` command have their own authentication; see
16+
> [Remote Server](remote-server.md).
17+
18+
## Contents
19+
20+
- [How it works](#how-it-works)
21+
- [Quick start](#quick-start)
22+
- [Configuration reference](#configuration-reference)
23+
- [Scope filtering](#scope-filtering)
24+
- [Running in Docker](#running-in-docker)
25+
- [Headless and device-code fallback](#headless-and-device-code-fallback)
26+
- [URL elicitation and the security advisory](#url-elicitation-and-the-security-advisory)
27+
- [Bring your own app](#bring-your-own-app)
28+
- [GitHub Enterprise Server and ghe.com](#github-enterprise-server-and-ghecom)
29+
- [Building from source with baked-in credentials](#building-from-source-with-baked-in-credentials)
30+
31+
## How it works
32+
33+
The server prefers the **authorization code flow with PKCE**: it starts a
34+
loopback callback server on your machine, opens GitHub's authorization page, and
35+
exchanges the returned code for a token. PKCE means the client secret is not
36+
required to complete the exchange, which is why a public, distributed client can
37+
ship without a confidential secret.
38+
39+
To present the authorization URL, the server uses the most secure channel your
40+
MCP client offers, in order:
41+
42+
1. **Open your browser automatically** (native runs).
43+
2. **URL elicitation** — the client prompts you with the link out of band, so the
44+
URL never enters the model's context. Requires a client that supports MCP
45+
elicitation (e.g. VS Code 1.101+).
46+
3. **A message in the first tool response** — a last resort for clients without
47+
elicitation. This includes a [security advisory](#url-elicitation-and-the-security-advisory).
48+
49+
If the authorization-code flow can't be used — for example, a container with no
50+
published callback port — the server falls back to the
51+
[device-code flow](#headless-and-device-code-fallback).
52+
53+
GitHub App tokens that expire are refreshed transparently using the refresh
54+
token, so long-running sessions keep working without re-authorizing.
55+
56+
## Quick start
57+
58+
**Native binary (recommended).** Best experience: a random loopback port is
59+
used and your browser opens automatically. On github.com with an official build,
60+
no flags are needed:
61+
62+
```bash
63+
github-mcp-server stdio
64+
```
65+
66+
With your own application:
67+
68+
```bash
69+
github-mcp-server stdio --oauth-client-id <YOUR_CLIENT_ID>
70+
```
71+
72+
VS Code (`.vscode/mcp.json`), using your own app:
73+
74+
```json
75+
{
76+
"servers": {
77+
"github": {
78+
"command": "/path/to/github-mcp-server",
79+
"args": ["stdio", "--oauth-client-id", "<YOUR_CLIENT_ID>"]
80+
}
81+
}
82+
}
83+
```
84+
85+
For Docker, see [Running in Docker](#running-in-docker) — containers need a fixed
86+
callback port.
87+
88+
## Configuration reference
89+
90+
OAuth login is configured with these stdio flags (each has an environment
91+
variable equivalent). Flags apply only to the `stdio` command.
92+
93+
| Flag | Environment variable | Description |
94+
|------|----------------------|-------------|
95+
| `--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. |
96+
| `--oauth-client-secret` | `GITHUB_OAUTH_CLIENT_SECRET` | Client secret, **if your app requires one**. For distributed clients this is a public, non-confidential credential. |
97+
| `--oauth-scopes` | `GITHUB_OAUTH_SCOPES` | Comma-separated scopes to request. Also [filters tools](#scope-filtering) to those scopes. Defaults to the full supported set. |
98+
| `--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. |
99+
100+
A static token still takes precedence: if `GITHUB_PERSONAL_ACCESS_TOKEN` is set,
101+
the server uses it and skips OAuth entirely.
102+
103+
## Scope filtering
104+
105+
The scopes you request determine which tools are exposed. Requesting the full
106+
supported set (the default) hides no tools. Narrowing `--oauth-scopes` both
107+
narrows the token's grant **and** filters out tools that would need a scope you
108+
didn't request, so the tool list reflects what the token can actually do.
109+
110+
For example, requesting only `repo,read:org` hides tools that require `gist`,
111+
`workflow`, `notifications`, and so on.
112+
113+
## Running in Docker
114+
115+
A container can't reach a random loopback port on your host, so Docker OAuth
116+
needs a **fixed** callback port that you publish into the container. Use port
117+
**8085** to match the official app's registered callback URL.
118+
119+
```bash
120+
docker run -i --rm \
121+
-p 127.0.0.1:8085:8085 \
122+
-e GITHUB_OAUTH_CALLBACK_PORT=8085 \
123+
ghcr.io/github/github-mcp-server
124+
```
125+
126+
VS Code (`.vscode/mcp.json`):
127+
128+
```json
129+
{
130+
"servers": {
131+
"github": {
132+
"command": "docker",
133+
"args": [
134+
"run", "-i", "--rm",
135+
"-p", "127.0.0.1:8085:8085",
136+
"-e", "GITHUB_OAUTH_CALLBACK_PORT",
137+
"ghcr.io/github/github-mcp-server"
138+
],
139+
"env": { "GITHUB_OAUTH_CALLBACK_PORT": "8085" }
140+
}
141+
}
142+
}
143+
```
144+
145+
Because the container can't open your host browser, the authorization URL
146+
arrives via [URL elicitation](#url-elicitation-and-the-security-advisory) or the
147+
tool-response message. After you authorize, your browser hits
148+
`localhost:8085`, which Docker forwards into the container's callback.
149+
150+
If you bring your own app for Docker, register its callback URL as exactly
151+
`http://localhost:8085/callback`.
152+
153+
> **Two safety properties to be aware of with a fixed port:**
154+
>
155+
> - **Publish to loopback only** (`-p 127.0.0.1:8085:8085`, not `-p 8085:8085`).
156+
> Inside a container the callback necessarily listens on all interfaces, so a
157+
> plain publish would expose the authorization code to your network. The
158+
> server logs a warning reminding you of this when it binds inside a container.
159+
> - **A busy port is fatal, by design.** With a fixed port, if the server can't
160+
> bind it (another process already holds it), it **stops with an error** rather
161+
> than silently falling back to the device flow. A port you didn't get could
162+
> belong to another user's process positioned to receive the redirect, so the
163+
> server refuses to continue. Free the port or choose a different
164+
> `--oauth-callback-port`.
165+
166+
## Headless and device-code fallback
167+
168+
When there's no usable browser or callback — a remote shell, CI, or a container
169+
started without a published port — the server uses GitHub's **device-code
170+
flow**. You'll get a short code and a verification URL to open on any device:
171+
172+
```
173+
Visit https://github.com/login/device and enter the code WDJB-MJHT to authorize
174+
the GitHub MCP Server.
175+
```
176+
177+
The server polls GitHub until you finish authorizing, then continues. No
178+
callback port is involved, so this works anywhere.
179+
180+
## URL elicitation and the security advisory
181+
182+
URL elicitation lets your MCP client present the authorization URL to you
183+
directly, keeping it **out of the model's context** — the model never sees the
184+
link or any code embedded in it. This is the most secure way to hand off the
185+
authorization step.
186+
187+
If your client doesn't support elicitation, the server falls back to placing the
188+
URL in a tool response and appends a short advisory:
189+
190+
> Note: your MCP client does not appear to support secure URL elicitation. For
191+
> improved security, consider asking your agent, CLI, or IDE to add it (for
192+
> example, by opening an issue).
193+
194+
If you see this, your authorization still works — but consider asking your client
195+
vendor to add elicitation support.
196+
197+
## Bring your own app
198+
199+
You need your own application when targeting a non-github.com host, or when you'd
200+
rather not use the baked-in app. Either application type works:
201+
202+
- **[Create an OAuth App](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app)**
203+
simplest to set up. Grants the scopes you request.
204+
- **[Register a GitHub App](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app)**
205+
finer-grained, per-resource permissions and short-lived tokens that refresh
206+
automatically. Enable **Device Flow** in the app settings if you want the
207+
[headless fallback](#headless-and-device-code-fallback).
208+
209+
When registering, set the authorization callback URL:
210+
211+
- **Native runs** use a random loopback port. For loopback redirects GitHub does
212+
not require the callback port to match, so registering
213+
`http://localhost/callback` is sufficient.
214+
- **Docker / fixed port** must match exactly: register
215+
`http://localhost:8085/callback` (or whichever port you publish).
216+
217+
Then pass the client ID (and secret, only if your app requires one):
218+
219+
```bash
220+
github-mcp-server stdio \
221+
--oauth-client-id <YOUR_CLIENT_ID> \
222+
--oauth-client-secret <YOUR_CLIENT_SECRET>
223+
```
224+
225+
## GitHub Enterprise Server and ghe.com
226+
227+
The baked-in app is registered on github.com only, so it is **not** used when you
228+
set a custom host. GitHub Enterprise Server and `ghe.com` (Enterprise Cloud with
229+
data residency) users must **bring their own app** registered on that host and
230+
pass `--oauth-client-id`.
231+
232+
Set the host with `--gh-host` / `GITHUB_HOST`; the server derives the OAuth
233+
authorization, token, and device endpoints from it, so login is directed at your
234+
instance's authorization server rather than github.com:
235+
236+
```bash
237+
github-mcp-server stdio \
238+
--gh-host https://github.example.com \
239+
--oauth-client-id <YOUR_CLIENT_ID>
240+
```
241+
242+
- For GitHub Enterprise Server, prefix the host with `https://`.
243+
- For `ghe.com`, use `https://YOURSUBDOMAIN.ghe.com`.
244+
245+
Register the app's callback URL on the same host (e.g.
246+
`http://localhost/callback` for native runs, or `http://localhost:8085/callback`
247+
for Docker).
248+
249+
## Building from source with baked-in credentials
250+
251+
Official builds embed the default OAuth client via linker flags at build time, so
252+
they are not present in the source tree. To produce your own build with embedded
253+
credentials, set them with `-ldflags`:
254+
255+
```bash
256+
go build -ldflags "\
257+
-X github.com/github/github-mcp-server/internal/buildinfo.OAuthClientID=<CLIENT_ID> \
258+
-X github.com/github/github-mcp-server/internal/buildinfo.OAuthClientSecret=<CLIENT_SECRET>" \
259+
./cmd/github-mcp-server
260+
```
261+
262+
Without these, a source build simply has no baked-in app and expects
263+
`--oauth-client-id` (or a PAT) at runtime.

server.json

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,27 @@
1616
"type": "stdio"
1717
},
1818
"runtimeArguments": [
19+
{
20+
"type": "named",
21+
"name": "-p",
22+
"description": "Publish the OAuth callback port to loopback so the in-container login callback is reachable",
23+
"value": "127.0.0.1:8085:8085"
24+
},
25+
{
26+
"type": "named",
27+
"name": "-e",
28+
"description": "Fixed OAuth callback port, matching the published port above",
29+
"value": "GITHUB_OAUTH_CALLBACK_PORT=8085"
30+
},
1931
{
2032
"type": "named",
2133
"name": "-e",
22-
"description": "Set an environment variable in the runtime",
34+
"description": "Optional GitHub Personal Access Token. Omit to log in with OAuth on first use.",
2335
"value": "GITHUB_PERSONAL_ACCESS_TOKEN={token}",
24-
"isRequired": true,
36+
"isRequired": false,
2537
"variables": {
2638
"token": {
27-
"isRequired": true,
39+
"isRequired": false,
2840
"isSecret": true,
2941
"format": "string"
3042
}

0 commit comments

Comments
 (0)