Skip to content

Commit 7a10305

Browse files
Make scope filtering always enabled (remove flag)
Scope filtering is now a built-in feature rather than a configurable option. The server automatically fetches token scopes at startup and filters tools accordingly. If scope detection fails, it logs a warning and continues with all tools available.
1 parent b87d152 commit 7a10305

File tree

4 files changed

+108
-18
lines changed

4 files changed

+108
-18
lines changed

cmd/github-mcp-server/main.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ var (
8484
ContentWindowSize: viper.GetInt("content-window-size"),
8585
LockdownMode: viper.GetBool("lockdown-mode"),
8686
RepoAccessCacheTTL: &ttl,
87-
EnableScopeFiltering: viper.GetBool("enable-scope-filtering"),
8887
}
8988
return ghmcp.RunStdioServer(stdioServerConfig)
9089
},
@@ -110,7 +109,6 @@ func init() {
110109
rootCmd.PersistentFlags().Int("content-window-size", 5000, "Specify the content window size")
111110
rootCmd.PersistentFlags().Bool("lockdown-mode", false, "Enable lockdown mode")
112111
rootCmd.PersistentFlags().Duration("repo-access-cache-ttl", 5*time.Minute, "Override the repo access cache TTL (e.g. 1m, 0s to disable)")
113-
rootCmd.PersistentFlags().Bool("enable-scope-filtering", true, "Filter tools based on the token's OAuth scopes")
114112

115113
// Bind flag to viper
116114
_ = viper.BindPFlag("toolsets", rootCmd.PersistentFlags().Lookup("toolsets"))
@@ -125,7 +123,6 @@ func init() {
125123
_ = viper.BindPFlag("content-window-size", rootCmd.PersistentFlags().Lookup("content-window-size"))
126124
_ = viper.BindPFlag("lockdown-mode", rootCmd.PersistentFlags().Lookup("lockdown-mode"))
127125
_ = viper.BindPFlag("repo-access-cache-ttl", rootCmd.PersistentFlags().Lookup("repo-access-cache-ttl"))
128-
_ = viper.BindPFlag("enable-scope-filtering", rootCmd.PersistentFlags().Lookup("enable-scope-filtering"))
129126

130127
// Add subcommands
131128
rootCmd.AddCommand(stdioCmd)

docs/scope-filtering.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# OAuth Scope Filtering
2+
3+
The GitHub MCP Server automatically filters available tools based on your Personal Access Token's (PAT) OAuth scopes. This ensures you only see tools that your token has permission to use, reducing clutter and preventing errors from attempting operations your token can't perform.
4+
5+
## How It Works
6+
7+
When the server starts, it makes a lightweight HTTP HEAD request to the GitHub API to discover your token's scopes from the `X-OAuth-Scopes` header. Tools that require scopes your token doesn't have are automatically hidden.
8+
9+
**Example:** If your token only has `repo` and `gist` scopes, you won't see tools that require `admin:org`, `project`, or `notifications` scopes.
10+
11+
## Checking Your Token's Scopes
12+
13+
To see what scopes your token has, you can run:
14+
15+
```bash
16+
curl -sI -H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" \
17+
https://api.github.com/user | grep -i x-oauth-scopes
18+
```
19+
20+
Example output:
21+
```
22+
x-oauth-scopes: delete_repo, gist, read:org, repo
23+
```
24+
25+
## Scopes and Tools
26+
27+
The following table shows which OAuth scopes are required for each category of tools:
28+
29+
| Scope | Tools Enabled |
30+
|-------|---------------|
31+
| `repo` | Repository operations, issues, PRs, commits, branches, code search, workflows |
32+
| `public_repo` | Star/unstar public repositories (implicit with `repo`) |
33+
| `read:org` | Read organization info, list teams, team members |
34+
| `write:org` | Organization management (includes `read:org`) |
35+
| `admin:org` | Full organization administration (includes `write:org`, `read:org`) |
36+
| `gist` | Create, update, and manage gists |
37+
| `notifications` | List, manage, and dismiss notifications |
38+
| `read:project` | Read GitHub Projects |
39+
| `project` | Create and manage GitHub Projects (includes `read:project`) |
40+
| `security_events` | Code scanning, Dependabot, secret scanning alerts (implicit with `repo`) |
41+
| `user` | Update user profile |
42+
| `read:user` | Read user profile information |
43+
44+
### Scope Hierarchy
45+
46+
Some scopes implicitly include others:
47+
48+
- `repo` → includes `public_repo`, `security_events`
49+
- `admin:org` → includes `write:org` → includes `read:org`
50+
- `project` → includes `read:project`
51+
52+
This means if your token has `repo`, tools requiring `security_events` will also be available.
53+
54+
## Recommended Token Scopes
55+
56+
For full functionality, we recommend these scopes:
57+
58+
| Use Case | Recommended Scopes |
59+
|----------|-------------------|
60+
| Basic development | `repo`, `read:org` |
61+
| Full development | `repo`, `admin:org`, `gist`, `notifications`, `project` |
62+
| Read-only access | `repo` (with `--read-only` flag) |
63+
| Security analysis | `repo` (includes `security_events`) |
64+
65+
## Graceful Degradation
66+
67+
If the server cannot fetch your token's scopes (e.g., network issues, rate limiting), it logs a warning and continues **without filtering**. This ensures the server remains usable even when scope detection fails.
68+
69+
```
70+
WARN: failed to fetch token scopes, continuing without scope filtering
71+
```
72+
73+
## Fine-Grained Personal Access Tokens
74+
75+
Fine-grained PATs use a different permission model and don't return OAuth scopes in the `X-OAuth-Scopes` header. When using fine-grained PATs, scope filtering will be skipped and all tools will be available. The GitHub API will still enforce permissions at the API level.
76+
77+
## Troubleshooting
78+
79+
| Problem | Cause | Solution |
80+
|---------|-------|----------|
81+
| Missing expected tools | Token lacks required scope | Add the scope to your PAT |
82+
| All tools visible despite limited PAT | Scope detection failed | Check logs for warnings about scope fetching |
83+
| "Insufficient permissions" errors | Tool visible but scope insufficient | This shouldn't happen with scope filtering; report as bug |
84+
85+
## Related Documentation
86+
87+
- [Server Configuration Guide](./server-configuration.md)
88+
- [GitHub PAT Documentation](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)
89+
- [OAuth Scopes Reference](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps)

docs/server-configuration.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ We currently support the following ways in which the GitHub MCP Server can be co
1212
| Read-Only Mode | `X-MCP-Readonly` header or `/readonly` URL | `--read-only` flag or `GITHUB_READ_ONLY` env var |
1313
| Dynamic Mode | Not available | `--dynamic-toolsets` flag or `GITHUB_DYNAMIC_TOOLSETS` env var |
1414
| Lockdown Mode | `X-MCP-Lockdown` header | `--lockdown-mode` flag or `GITHUB_LOCKDOWN_MODE` env var |
15+
| Scope Filtering | Always enabled | Always enabled |
1516

1617
> **Default behavior:** If you don't specify any configuration, the server uses the **default toolsets**: `context`, `issues`, `pull_requests`, `repos`, `users`.
1718
@@ -330,6 +331,16 @@ Lockdown mode ensures the server only surfaces content in public repositories fr
330331

331332
---
332333

334+
### Scope Filtering
335+
336+
**Automatic feature:** The server automatically detects your PAT's OAuth scopes and only shows tools you have permission to use.
337+
338+
This happens transparently at startup - no configuration needed. If scope detection fails (e.g., network issues), the server logs a warning and continues with all tools available.
339+
340+
See [OAuth Scope Filtering](./scope-filtering.md) for details on which scopes enable which tools.
341+
342+
---
343+
333344
## Troubleshooting
334345

335346
| Problem | Cause | Solution |

internal/ghmcp/server.go

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -324,11 +324,6 @@ type StdioServerConfig struct {
324324

325325
// RepoAccessCacheTTL overrides the default TTL for repository access cache entries.
326326
RepoAccessCacheTTL *time.Duration
327-
328-
// EnableScopeFiltering enables PAT scope-based tool filtering.
329-
// When true, the server will fetch the token's OAuth scopes at startup
330-
// and hide tools that require scopes the token doesn't have.
331-
EnableScopeFiltering bool
332327
}
333328

334329
// RunStdioServer is not concurrent safe.
@@ -353,18 +348,16 @@ func RunStdioServer(cfg StdioServerConfig) error {
353348
slogHandler = slog.NewTextHandler(logOutput, &slog.HandlerOptions{Level: slog.LevelInfo})
354349
}
355350
logger := slog.New(slogHandler)
356-
logger.Info("starting server", "version", cfg.Version, "host", cfg.Host, "dynamicToolsets", cfg.DynamicToolsets, "readOnly", cfg.ReadOnly, "lockdownEnabled", cfg.LockdownMode, "scopeFiltering", cfg.EnableScopeFiltering)
351+
logger.Info("starting server", "version", cfg.Version, "host", cfg.Host, "dynamicToolsets", cfg.DynamicToolsets, "readOnly", cfg.ReadOnly, "lockdownEnabled", cfg.LockdownMode)
357352

358-
// Fetch token scopes if scope filtering is enabled
353+
// Fetch token scopes for scope-based tool filtering
359354
var tokenScopes []string
360-
if cfg.EnableScopeFiltering {
361-
fetchedScopes, err := fetchTokenScopesForHost(ctx, cfg.Token, cfg.Host)
362-
if err != nil {
363-
logger.Warn("failed to fetch token scopes, continuing without scope filtering", "error", err)
364-
} else {
365-
tokenScopes = fetchedScopes
366-
logger.Info("token scopes fetched for filtering", "scopes", tokenScopes)
367-
}
355+
fetchedScopes, err := fetchTokenScopesForHost(ctx, cfg.Token, cfg.Host)
356+
if err != nil {
357+
logger.Warn("failed to fetch token scopes, continuing without scope filtering", "error", err)
358+
} else {
359+
tokenScopes = fetchedScopes
360+
logger.Info("token scopes fetched for filtering", "scopes", tokenScopes)
368361
}
369362

370363
ghServer, err := NewMCPServer(MCPServerConfig{

0 commit comments

Comments
 (0)