Skip to content

Commit a233bc4

Browse files
fix: address PR review feedback
- Add runtime.GOOS check to Docker detection (macOS/Windows compatibility) - Use cancellable contexts for elicitation goroutines (pollCtx, elicitCtx) - Mark listener.Close() error as intentionally ignored in cleanup - Remove redundant oauthMgr.GetAccessToken() check (always empty in lazy auth) - Inline CSS in OAuth templates (remove CDN dependency) - Add documentation to Result struct about token refresh behavior
1 parent c126802 commit a233bc4

File tree

6 files changed

+191
-35
lines changed

6 files changed

+191
-35
lines changed

cmd/github-mcp-server/main.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,6 @@ var (
104104
}
105105
}
106106

107-
// Extract token from OAuth manager if available
108-
if oauthMgr != nil && token == "" {
109-
token = oauthMgr.GetAccessToken()
110-
}
111-
112107
ttl := viper.GetDuration("repo-access-cache-ttl")
113108
stdioServerConfig := ghmcp.StdioServerConfig{
114109
Version: version,
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
---
2+
title: "Intelligent Scope Features"
3+
date: 2026-01
4+
description: "OAuth scope challenges, automatic PAT filtering, and comprehensive scope documentation for smarter authentication"
5+
category: feature
6+
---
7+
8+
# Intelligent Scope Features
9+
10+
GitHub MCP Server now intelligently handles OAuth scopes—filtering tools based on your permissions and enabling dynamic scope requests when needed.
11+
12+
## What's New
13+
14+
### OAuth Scope Challenges
15+
16+
The server now implements [MCP scope challenge handling](https://modelcontextprotocol.io/specification/2025-11-05/basic/authorization#scope-challenge-handling). Instead of failing when you lack a required scope, it requests additional permissions dynamically—start with minimal permissions and expand them as needed.
17+
18+
### PAT Scope Filtering
19+
20+
For classic Personal Access Tokens (`ghp_`), tools are automatically filtered based on your token's scopes. The server discovers your scopes at startup and hides tools you can't use.
21+
22+
**Example:** If your PAT only has `repo` and `gist` scopes, tools requiring `admin:org`, `project`, or `notifications` are hidden.
23+
24+
### Server-to-Server Token Handling
25+
26+
For server-to-server tokens (like `GITHUB_TOKEN` in Actions), the server hides user-context tools like `get_me` that don't apply without a human user.
27+
28+
### Documented OAuth Scopes
29+
30+
Every MCP tool now documents its required and accepted OAuth scopes in the README and tool metadata.
31+
32+
### New `list-scopes` Command
33+
34+
Discover what scopes your toolsets need:
35+
36+
```bash
37+
github-mcp-server list-scopes --output=summary
38+
github-mcp-server list-scopes --toolsets=all --output=json
39+
```
40+
41+
## Scope Hierarchy
42+
43+
The server understands GitHub's scope hierarchy, so parent scopes satisfy child scope requirements:
44+
45+
| Parent Scope | Covers |
46+
|-------------|--------|
47+
| `repo` | `public_repo`, `security_events` |
48+
| `admin:org` | `write:org`, `read:org` |
49+
| `project` | `read:project` |
50+
| `write:org` | `read:org` |
51+
52+
If a tool requires `read:org` and your token has `admin:org`, the tool is available.
53+
54+
## Authentication Comparison
55+
56+
| Authentication Method | Scope Handling |
57+
|----------------------|----------------|
58+
| **OAuth** (remote server) | Scope challenges — request permissions on-demand |
59+
| **Classic PAT** (`ghp_`) | Automatic filtering — hide unavailable tools |
60+
| **Fine-grained PAT** (`github_pat_`) | No filtering — fine-grained permissions, not OAuth scopes |
61+
| **GitHub App** (`ghs_`) | No filtering — fine-grained permissions, not OAuth scopes |
62+
| **Server-to-Server** (`GITHUB_TOKEN`) | User tools hidden — no user context available |
63+
64+
## Getting Started
65+
66+
**OAuth users:** No action required—scope challenges work automatically.
67+
68+
**PAT users:** Run `list-scopes` to discover required scopes, create a PAT at [github.com/settings/tokens](https://github.com/settings/tokens), and start the server.
69+
70+
## Related Documentation
71+
72+
- [PAT Scope Filtering Guide](https://github.com/github/github-mcp-server/blob/v0.29.0/docs/scope-filtering.md)
73+
- [OAuth Authentication Guide](https://github.com/github/github-mcp-server/blob/v0.29.0/docs/oauth-authentication.md)
74+
- [Server Configuration](https://github.com/github/github-mcp-server/blob/v0.29.0/docs/server-configuration.md)
75+
76+
## Feedback
77+
78+
Share your experience in the [Scope filtering/challenging discussion](https://github.com/github/github-mcp-server/discussions/1802). We're exploring ways to better support fine-grained permissions in the future.

internal/oauth/manager.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,11 @@ func (m *Manager) startDeviceFlowWithElicitation(ctx context.Context, session *m
124124
go func() {
125125
elicitID, err := generateRandomToken()
126126
if err != nil {
127+
// Non-critical: use fallback ID if generation fails
127128
elicitID = "fallback-id"
128129
}
129-
result, err := session.Elicit(ctx, &mcp.ElicitParams{
130+
// Use pollCtx so elicitation is cancelled when polling completes or is cancelled
131+
result, err := session.Elicit(pollCtx, &mcp.ElicitParams{
130132
Mode: "url",
131133
URL: deviceAuth.VerificationURI,
132134
ElicitationID: elicitID,
@@ -209,7 +211,7 @@ func (m *Manager) startPKCEFlowWithElicitation(ctx context.Context, session *mcp
209211
shutdownCtx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
210212
defer cancel()
211213
_ = server.Shutdown(shutdownCtx)
212-
listener.Close()
214+
_ = listener.Close() // Error intentionally ignored in cleanup
213215
}
214216

215217
// Try to open browser - if it works, no elicitation needed
@@ -218,13 +220,18 @@ func (m *Manager) startPKCEFlowWithElicitation(ctx context.Context, session *mcp
218220
// Channel to signal elicitation cancellation
219221
elicitCancelChan := make(chan struct{}, 1)
220222

223+
// Create cancellable context for elicitation
224+
elicitCtx, cancelElicit := context.WithCancel(ctx)
225+
defer cancelElicit()
226+
221227
// Only elicit if browser failed to open (e.g., headless environment)
222228
// and we need to show the user the URL manually
223229
if browserErr != nil && session != nil {
224230
// Run elicitation in goroutine so we can monitor callback in parallel
225231
go func() {
226-
elicitID, _ := generateRandomToken()
227-
result, err := session.Elicit(ctx, &mcp.ElicitParams{
232+
elicitID, _ := generateRandomToken() // Non-critical: empty ID is acceptable
233+
// Use elicitCtx so elicitation is cancelled when auth completes
234+
result, err := session.Elicit(elicitCtx, &mcp.ElicitParams{
228235
Mode: "url",
229236
URL: authURL,
230237
ElicitationID: elicitID,

internal/oauth/oauth.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,16 @@ type Config struct {
5454
CallbackPort int // Fixed callback port (0 for random)
5555
}
5656

57-
// Result contains the OAuth flow result
57+
// Result contains the OAuth flow result.
58+
//
59+
// Note: This implementation does not currently perform automatic token refresh.
60+
// GitHub OAuth tokens for OAuth Apps do not expire, but GitHub Apps tokens do.
61+
// Callers should handle re-authentication when API calls fail with auth errors.
5862
type Result struct {
5963
AccessToken string
60-
RefreshToken string
64+
RefreshToken string // Captured but not currently used for automatic refresh
6165
TokenType string
62-
Expiry time.Time
66+
Expiry time.Time // Zero value if token does not expire
6367
}
6468

6569
// generatePKCEVerifier generates a PKCE code verifier
@@ -74,8 +78,16 @@ func generatePKCEVerifier() (string, error) {
7478
return verifier, nil
7579
}
7680

77-
// isRunningInDocker detects if the process is running inside a Docker container
81+
// isRunningInDocker detects if the process is running inside a Docker container.
82+
// This detection is used to determine whether to use device flow (no browser available)
83+
// or PKCE flow (browser can be opened). On non-Linux systems, this always returns false
84+
// since the detection relies on Linux-specific paths.
7885
func isRunningInDocker() bool {
86+
// Docker detection only works on Linux where /proc filesystem exists
87+
if runtime.GOOS != "linux" {
88+
return false
89+
}
90+
7991
// Check for .dockerenv file (most common indicator)
8092
if _, err := os.Stat("/.dockerenv"); err == nil {
8193
return true
Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,60 @@
11
<!DOCTYPE html>
2-
<html lang="en" data-color-mode="auto" data-light-theme="light" data-dark-theme="dark">
2+
<html lang="en">
33
<head>
44
<meta charset="utf-8">
55
<meta name="viewport" content="width=device-width, initial-scale=1">
66
<title>Authorization Failed</title>
7-
<link href="https://cdn.jsdelivr.net/npm/@primer/css@22/dist/primer.min.css" rel="stylesheet">
87
<style>
98
html, body { height: 100%; margin: 0; }
10-
body { display: flex; align-items: center; justify-content: center; min-height: 100vh; }
11-
.card { width: 500px; box-shadow: none !important; }
9+
body {
10+
display: flex;
11+
align-items: center;
12+
justify-content: center;
13+
min-height: 100vh;
14+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif;
15+
background-color: #0d1117;
16+
color: #e6edf3;
17+
}
18+
.card {
19+
width: 500px;
20+
background-color: #161b22;
21+
border: 1px solid #30363d;
22+
border-radius: 6px;
23+
padding: 32px;
24+
text-align: center;
25+
}
26+
.octicon { margin-bottom: 16px; }
27+
h1 {
28+
font-size: 20px;
29+
font-weight: 600;
30+
margin: 0 0 12px 0;
31+
color: #f85149;
32+
}
33+
p { font-size: 16px; color: #8b949e; margin: 16px 0 0 0; }
34+
.flash-error {
35+
margin-top: 16px;
36+
padding: 12px 16px;
37+
background-color: rgba(248, 81, 73, 0.1);
38+
border: 1px solid rgba(248, 81, 73, 0.4);
39+
border-radius: 6px;
40+
}
41+
code {
42+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
43+
font-size: 14px;
44+
color: #f85149;
45+
}
1246
</style>
1347
</head>
14-
<body class="color-bg-canvas">
15-
<div class="Box color-bg-default card p-6 text-center">
16-
<svg class="octicon color-fg-default mb-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="80" height="80" fill="currentColor">
48+
<body>
49+
<div class="card">
50+
<svg class="octicon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="80" height="80" fill="currentColor">
1751
<path d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z"></path>
1852
</svg>
19-
<h1 class="h2 color-fg-danger mb-3">Authorization Failed</h1>
20-
<div class="flash flash-error mt-4">
21-
<code class="f5">{{.ErrorMessage}}</code>
53+
<h1>Authorization Failed</h1>
54+
<div class="flash-error">
55+
<code>{{.ErrorMessage}}</code>
2256
</div>
23-
<p class="f4 color-fg-muted mt-4">You can close this window.</p>
57+
<p>You can close this window.</p>
2458
</div>
2559
</body>
2660
</html>

internal/oauth/templates/success.html

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,55 @@
11
<!DOCTYPE html>
2-
<html lang="en" data-color-mode="auto" data-light-theme="light" data-dark-theme="dark">
2+
<html lang="en">
33
<head>
44
<meta charset="utf-8">
55
<meta name="viewport" content="width=device-width, initial-scale=1">
66
<title>Authorization Successful</title>
7-
<link href="https://cdn.jsdelivr.net/npm/@primer/css@22/dist/primer.min.css" rel="stylesheet">
87
<style>
98
html, body { height: 100%; margin: 0; }
10-
body { display: flex; align-items: center; justify-content: center; min-height: 100vh; }
11-
.card { width: 500px; box-shadow: none !important; }
9+
body {
10+
display: flex;
11+
align-items: center;
12+
justify-content: center;
13+
min-height: 100vh;
14+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif;
15+
background-color: #0d1117;
16+
color: #e6edf3;
17+
}
18+
.card {
19+
width: 500px;
20+
background-color: #161b22;
21+
border: 1px solid #30363d;
22+
border-radius: 6px;
23+
padding: 32px;
24+
text-align: center;
25+
}
26+
.octicon { margin-bottom: 16px; }
27+
h1 {
28+
font-size: 20px;
29+
font-weight: 600;
30+
margin: 0 0 12px 0;
31+
color: #3fb950;
32+
}
33+
p { font-size: 16px; color: #8b949e; margin: 0; }
34+
.flash {
35+
margin-top: 16px;
36+
padding: 12px 16px;
37+
background-color: rgba(56, 139, 253, 0.15);
38+
border: 1px solid rgba(56, 139, 253, 0.4);
39+
border-radius: 6px;
40+
}
41+
.flash p { font-size: 14px; }
1242
</style>
1343
</head>
14-
<body class="color-bg-canvas">
15-
<div class="Box color-bg-default card p-6 text-center">
16-
<svg class="octicon color-fg-default mb-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="80" height="80" fill="currentColor">
44+
<body>
45+
<div class="card">
46+
<svg class="octicon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="80" height="80" fill="currentColor">
1747
<path d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z"></path>
1848
</svg>
19-
<h1 class="h2 color-fg-success mb-3">Authorization Successful</h1>
20-
<p class="f4 color-fg-muted">You have successfully authorized the GitHub MCP Server.</p>
21-
<div class="flash mt-4">
22-
<p class="mb-0 f5">You can close this window and retry your request.</p>
49+
<h1>Authorization Successful</h1>
50+
<p>You have successfully authorized the GitHub MCP Server.</p>
51+
<div class="flash">
52+
<p>You can close this window and retry your request.</p>
2353
</div>
2454
</div>
2555
</body>

0 commit comments

Comments
 (0)