Skip to content

Commit c126802

Browse files
feat: add build-time OAuth credentials for seamless authentication
- Add internal/buildinfo package for build-injected OAuth credentials - Update goreleaser and Dockerfile to inject credentials via ldflags - Update main.go to use baked-in credentials as fallback (PAT > explicit OAuth > baked-in) - Update GitHub Actions workflows to pass secrets to builds - Update documentation with new authentication flow
1 parent 6044f36 commit c126802

File tree

7 files changed

+142
-18
lines changed

7 files changed

+142
-18
lines changed

.github/workflows/docker-publish.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ jobs:
112112
platforms: linux/amd64,linux/arm64
113113
build-args: |
114114
VERSION=${{ github.ref_name }}
115+
OAUTH_CLIENT_ID=${{ secrets.OAUTH_CLIENT_ID }}
116+
OAUTH_CLIENT_SECRET=${{ secrets.OAUTH_CLIENT_SECRET }}
115117
116118
# Sign the resulting Docker image digest except on PRs.
117119
# This will only write to the public Rekor transparency log when the Docker

.github/workflows/goreleaser.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ jobs:
3535
workdir: .
3636
env:
3737
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
38+
OAUTH_CLIENT_ID: ${{ secrets.OAUTH_CLIENT_ID }}
39+
OAUTH_CLIENT_SECRET: ${{ secrets.OAUTH_CLIENT_SECRET }}
3840

3941
- name: Generate signed build provenance attestations for workflow artifacts
4042
uses: actions/attest-build-provenance@v3

.goreleaser.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ builds:
99
- env:
1010
- CGO_ENABLED=0
1111
ldflags:
12-
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}
12+
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X github.com/github/github-mcp-server/internal/buildinfo.OAuthClientID={{ .Env.OAUTH_CLIENT_ID }} -X github.com/github/github-mcp-server/internal/buildinfo.OAuthClientSecret={{ .Env.OAUTH_CLIENT_SECRET }}
1313
goos:
1414
- linux
1515
- windows

Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
FROM golang:1.25.4-alpine AS build
22
ARG VERSION="dev"
3+
ARG OAUTH_CLIENT_ID=""
4+
ARG OAUTH_CLIENT_SECRET=""
35

46
# Set the working directory
57
WORKDIR /build
@@ -13,7 +15,7 @@ RUN --mount=type=cache,target=/var/cache/apk \
1315
RUN --mount=type=cache,target=/go/pkg/mod \
1416
--mount=type=cache,target=/root/.cache/go-build \
1517
--mount=type=bind,target=. \
16-
CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=${VERSION} -X main.commit=$(git rev-parse HEAD) -X main.date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
18+
CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=${VERSION} -X main.commit=$(git rev-parse HEAD) -X main.date=$(date -u +%Y-%m-%dT%H:%M:%SZ) -X github.com/github/github-mcp-server/internal/buildinfo.OAuthClientID=${OAUTH_CLIENT_ID} -X github.com/github/github-mcp-server/internal/buildinfo.OAuthClientSecret=${OAUTH_CLIENT_SECRET}" \
1719
-o /bin/github-mcp-server cmd/github-mcp-server/main.go
1820

1921
# Make a stage to run the app

cmd/github-mcp-server/main.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"strings"
99
"time"
1010

11+
"github.com/github/github-mcp-server/internal/buildinfo"
1112
"github.com/github/github-mcp-server/internal/ghmcp"
1213
"github.com/github/github-mcp-server/internal/oauth"
1314
"github.com/github/github-mcp-server/pkg/github"
@@ -72,9 +73,10 @@ var (
7273
var oauthScopes []string
7374
var prebuiltInventory *inventory.Inventory
7475

75-
// If no token provided, setup OAuth manager if configured
76+
// If no token provided, setup OAuth manager
77+
// Priority: 1. Explicit OAuth config, 2. Build-time credentials, 3. None
7678
if token == "" {
77-
oauthClientID := viper.GetString("oauth_client_id")
79+
oauthClientID, oauthClientSecret := resolveOAuthCredentials()
7880
if oauthClientID != "" {
7981
// Get translation helper for inventory building
8082
t, _ := translations.TranslationHelper()
@@ -87,7 +89,7 @@ var (
8789
// Create OAuth manager for lazy authentication
8890
oauthCfg := oauth.GetGitHubOAuthConfig(
8991
oauthClientID,
90-
viper.GetString("oauth_client_secret"),
92+
oauthClientSecret,
9193
oauthScopes,
9294
viper.GetString("host"),
9395
viper.GetInt("oauth_callback_port"),
@@ -274,3 +276,25 @@ func collectRequiredScopes(inv *inventory.Inventory) []string {
274276

275277
return scopes
276278
}
279+
280+
// resolveOAuthCredentials returns OAuth client credentials using the following priority:
281+
// 1. Explicit configuration via flags/environment (--oauth-client-id, GITHUB_OAUTH_CLIENT_ID)
282+
// 2. Build-time baked credentials (for official releases)
283+
//
284+
// This allows developers to override with their own OAuth app while providing
285+
// a seamless "just works" experience for end users of official builds.
286+
func resolveOAuthCredentials() (clientID, clientSecret string) {
287+
// Priority 1: Explicit user configuration
288+
clientID = viper.GetString("oauth_client_id")
289+
if clientID != "" {
290+
return clientID, viper.GetString("oauth_client_secret")
291+
}
292+
293+
// Priority 2: Build-time baked credentials
294+
if buildinfo.HasOAuthCredentials() {
295+
return buildinfo.OAuthClientID, buildinfo.OAuthClientSecret
296+
}
297+
298+
// No OAuth credentials available
299+
return "", ""
300+
}

docs/oauth-authentication.md

Lines changed: 76 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,80 @@ The GitHub MCP Server supports OAuth authentication for stdio mode, enabling int
66

77
OAuth authentication allows users to authenticate with GitHub through their browser without pre-configuring a token. This is useful for:
88

9+
- **End users** who want authentication to "just work" without configuration
910
- **Interactive sessions** where users want to authenticate on-demand
1011
- **Docker deployments** where tokens shouldn't be baked into images
1112
- **Multi-user scenarios** where each user authenticates individually
1213

13-
## Configuration
14+
## How It Works
1415

15-
### Required Environment Variables
16+
Official releases of the GitHub MCP Server include built-in OAuth credentials, providing a seamless authentication experience. When you run the server without a PAT configured, it will automatically prompt for OAuth authentication when a tool requires it.
17+
18+
### Authentication Priority
19+
20+
The server uses the following priority for authentication:
21+
22+
1. **Personal Access Token** (GITHUB_PERSONAL_ACCESS_TOKEN) - Highest priority, explicit user choice
23+
2. **Explicit OAuth configuration** (--oauth-client-id flag/env) - Developer/power user override
24+
3. **Built-in OAuth credentials** - Default for official releases, "just works"
25+
4. **No authentication** - Warning displayed, tools will fail when called
26+
27+
## Quick Start
28+
29+
For most users, simply run the server without any configuration:
30+
31+
```bash
32+
# Official releases include built-in OAuth - just run and authenticate when prompted
33+
./github-mcp-server stdio
34+
```
35+
36+
The server will prompt for browser-based authentication when you first call a tool that requires GitHub access.
37+
38+
## Developer Configuration
39+
40+
Developers building from source or wanting to use their own OAuth app can provide credentials explicitly.
41+
42+
### Environment Variables
1643

1744
| Variable | Description | Required |
1845
|----------|-------------|----------|
19-
| `GITHUB_OAUTH_CLIENT_ID` | OAuth app client ID | Yes |
46+
| `GITHUB_OAUTH_CLIENT_ID` | OAuth app client ID | For custom OAuth apps |
2047
| `GITHUB_OAUTH_CLIENT_SECRET` | OAuth app client secret | Recommended |
2148

22-
### Optional Flags
49+
### Command Line Flags
2350

2451
| Flag | Environment Variable | Description |
2552
|------|---------------------|-------------|
53+
| `--oauth-client-id` | `GITHUB_OAUTH_CLIENT_ID` | Override OAuth app client ID |
54+
| `--oauth-client-secret` | `GITHUB_OAUTH_CLIENT_SECRET` | Override OAuth app client secret |
2655
| `--oauth-callback-port` | `GITHUB_OAUTH_CALLBACK_PORT` | Fixed port for OAuth callback (required for Docker with `-p` flag) |
2756
| `--oauth-scopes` | `GITHUB_OAUTH_SCOPES` | Custom OAuth scopes (comma-separated) |
2857

58+
### Building with Custom OAuth Credentials
59+
60+
When building from source, you can bake in your own OAuth credentials:
61+
62+
```bash
63+
# Build with custom OAuth credentials
64+
go build -ldflags="-X github.com/github/github-mcp-server/internal/buildinfo.OAuthClientID=your-client-id \
65+
-X github.com/github/github-mcp-server/internal/buildinfo.OAuthClientSecret=your-client-secret" \
66+
./cmd/github-mcp-server
67+
68+
# Or use environment variables during development
69+
export GITHUB_OAUTH_CLIENT_ID="your-client-id"
70+
export GITHUB_OAUTH_CLIENT_SECRET="your-client-secret"
71+
./github-mcp-server stdio
72+
```
73+
74+
For Docker builds:
75+
76+
```bash
77+
docker build \
78+
--build-arg OAUTH_CLIENT_ID="your-client-id" \
79+
--build-arg OAUTH_CLIENT_SECRET="your-client-secret" \
80+
-t github-mcp-server .
81+
```
82+
2983
## Authentication Flows
3084

3185
The server automatically selects the appropriate OAuth flow based on the environment:
@@ -52,18 +106,31 @@ Used when running in Docker or when a browser cannot be opened:
52106

53107
## Usage Examples
54108

55-
### Local Binary
109+
### Local Binary (Official Release)
56110

57111
```bash
58-
# Set OAuth credentials
112+
# Official releases have built-in OAuth - just run!
113+
./github-mcp-server stdio
114+
# Authentication will be prompted when a tool is called
115+
```
116+
117+
### Local Binary (Custom OAuth)
118+
119+
```bash
120+
# Override with your own OAuth app
59121
export GITHUB_OAUTH_CLIENT_ID="your-client-id"
60122
export GITHUB_OAUTH_CLIENT_SECRET="your-client-secret"
61-
62-
# Run without PAT - OAuth will trigger when tools are called
63123
./github-mcp-server stdio
64124
```
65125

66-
### Docker (with Device Flow)
126+
### Docker (Official Image)
127+
128+
```bash
129+
# Official images have built-in OAuth - just run!
130+
docker run -i --rm ghcr.io/github/github-mcp-server stdio
131+
```
132+
133+
### Docker (with Custom OAuth)
67134

68135
```bash
69136
docker run -i --rm \
@@ -77,8 +144,6 @@ docker run -i --rm \
77144
```bash
78145
docker run -i --rm \
79146
--network=host \
80-
-e GITHUB_OAUTH_CLIENT_ID="your-client-id" \
81-
-e GITHUB_OAUTH_CLIENT_SECRET="your-client-secret" \
82147
ghcr.io/github/github-mcp-server stdio --oauth-callback-port=8085
83148
```
84149

@@ -91,8 +156,6 @@ docker run -i --rm \
91156
"command": "docker",
92157
"args": [
93158
"run", "-i", "--rm",
94-
"-e", "GITHUB_OAUTH_CLIENT_ID=your-client-id",
95-
"-e", "GITHUB_OAUTH_CLIENT_SECRET=your-client-secret",
96159
"ghcr.io/github/github-mcp-server",
97160
"stdio"
98161
],

internal/buildinfo/buildinfo.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Package buildinfo contains build-time injected values.
2+
//
3+
// These values are set via -ldflags during the build process.
4+
// For example:
5+
//
6+
// go build -ldflags="-X github.com/github/github-mcp-server/internal/buildinfo.OAuthClientID=xxx"
7+
//
8+
// The OAuth credentials are used as default values for stdio mode when no
9+
// PAT or explicit OAuth configuration is provided. This enables a "just works"
10+
// experience for most users while still allowing developer overrides.
11+
//
12+
// Note: These credentials are intentionally baked into the binary. While they
13+
// can be reverse-engineered, this provides a barrier against trivial cloning
14+
// and establishes clear provenance for the official GitHub MCP Server.
15+
package buildinfo
16+
17+
// OAuthClientID is the default GitHub OAuth App Client ID.
18+
// Set at build time via -ldflags for official releases.
19+
// Empty string means no default OAuth credentials are available.
20+
var OAuthClientID string
21+
22+
// OAuthClientSecret is the default GitHub OAuth App Client Secret.
23+
// Set at build time via -ldflags for official releases.
24+
// While called a "secret", OAuth client secrets in native apps cannot truly
25+
// be kept secret and are considered public per RFC 8252.
26+
var OAuthClientSecret string
27+
28+
// HasOAuthCredentials returns true if build-time OAuth credentials are available.
29+
func HasOAuthCredentials() bool {
30+
return OAuthClientID != ""
31+
}

0 commit comments

Comments
 (0)