You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Document scope-filter limitations and fail-open posture
Correct the over-claim that the scope hierarchy covers all OR cases. The
hierarchy only models ancestor substitution, not sibling alternatives, so it
cannot represent every way a tool may legitimately be satisfied.
- Reword HasRequiredScopes doc: AND-of-ORs is ancestor-only; note the
best-effort/fail-open posture and make the future CNF extension concrete using
code scanning as the motivating example (security_events OR public_repo OR repo).
- Note in CreateToolScopeFilter that filtering is a best-effort UX filter, not an
authorization boundary (gated to ghp_ PATs, skipped when scopes can't be fetched).
- Verify security tools ({security_events}) stay behavior-neutral under AND: a
repo token still satisfies them; a public_repo-only token was already hidden
before. Add explicit neutrality tests at the scope and filter layers.
- docs/scope-filtering.md: add a Limitations and Fail-Open Posture section
(sibling scopes, org roles, repo visibility) and a best-effort intro note.
No tool declarations or scopes changed; no CNF structure built.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy file name to clipboardExpand all lines: docs/scope-filtering.md
+15-1Lines changed: 15 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,6 +4,8 @@ The GitHub MCP Server automatically filters available tools based on your classi
4
4
5
5
> **Note:** This feature applies to **classic PATs** (tokens starting with `ghp_`). Fine-grained PATs, GitHub App installation tokens, and server-to-server tokens don't support scope detection and show all tools.
6
6
7
+
> **Important:** Scope filtering is a best-effort UX convenience, **not an authorization boundary**. The GitHub API is always the source of truth and enforces real permissions. The server therefore **fails open**: it only hides a tool when confident your token cannot use it, and shows the tool whenever access is plausible. See [Limitations and Fail-Open Posture](#limitations-and-fail-open-posture).
8
+
7
9
## How It Works
8
10
9
11
When the server starts with a classic PAT, 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.
@@ -76,6 +78,18 @@ If the server cannot fetch your token's scopes (e.g., network issues, rate limit
76
78
WARN: failed to fetch token scopes, continuing without scope filtering
77
79
```
78
80
81
+
## Limitations and Fail-Open Posture
82
+
83
+
Scope filtering is a **best-effort UX nicety**, not an authorization boundary. The GitHub API is the source of truth and enforces real permissions regardless of what the server shows. Because of this, the server is designed to **fail open**: it only hides a tool (or, for OAuth, issues a scope challenge) when it is confident the token cannot use it. When access is plausible, it prefers to show the tool and let the API decide. Filtering is also limited to classic PATs (`ghp_`) and is skipped entirely when scopes can't be fetched.
84
+
85
+
A tool's declared scopes are **all required** (logical AND), and each one may be satisfied directly or by an ancestor scope from the [hierarchy](#scope-hierarchy). However, some ways a tool can legitimately be used cannot be determined from OAuth scopes alone, so the server intentionally does not try to model them:
86
+
87
+
-**Sibling scopes outside the hierarchy.** The hierarchy only models *ancestor* substitution. For example, code scanning alerts on a **public** repository are readable with `public_repo`, which is a *sibling* of the declared `security_events` (both are children of `repo`), not an ancestor. Token expansion can't bridge siblings. Capturing this faithfully would require representing requirements as a list of OR-groups (groups AND-ed, members within a group OR-ed), e.g. code scanning = `security_events` OR `public_repo` OR `repo`. That model isn't implemented today; instead the server relies on the fail-open posture so these cases aren't wrongly hidden.
88
+
-**Organization roles.** Roles such as *security manager* grant access orthogonally to OAuth scopes and are invisible to scope detection. A user may legitimately have access the server cannot see from scopes alone.
89
+
-**Public vs. private repositories.** Whether a given scope suffices depends on the target repository's visibility, which isn't known at filter time.
90
+
91
+
In each of these cases the server errs toward showing the tool; if the token truly lacks access, the API returns the appropriate error.
92
+
79
93
## Classic vs Fine-Grained Personal Access Tokens
80
94
81
95
**Classic PATs** (`ghp_` prefix) support OAuth scopes and return them in the `X-OAuth-Scopes` header. Scope filtering works fully with these tokens.
@@ -92,7 +106,7 @@ WARN: failed to fetch token scopes, continuing without scope filtering
92
106
|---------|-------|----------|
93
107
| Missing expected tools | Token lacks required scope |[Edit your PAT's scopes](https://github.com/settings/tokens) in GitHub settings |
94
108
| All tools visible despite limited PAT | Scope detection failed | Check logs for warnings about scope fetching |
95
-
| "Insufficient permissions" errors | Tool visible but scope insufficient |This shouldn't happen with scope filtering; report as bug|
109
+
| "Insufficient permissions" errors | Tool visible but scope insufficient |Expected in some cases (fail-open, public/private ambiguity, org roles, or scope detection skipped). The API enforces the real boundary—grant the needed scope or access|
96
110
97
111
> **Tip:** You can adjust the scopes of an existing classic PAT at any time via [GitHub's token settings](https://github.com/settings/tokens). After updating scopes, restart the MCP server to pick up the changes.
0 commit comments