-
Notifications
You must be signed in to change notification settings - Fork 3.1k
feat: Add list-scopes command to show required OAuth scopes #1487
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: SamMorrowDrums/oauth-scopes-phase2
Are you sure you want to change the base?
feat: Add list-scopes command to show required OAuth scopes #1487
Conversation
Adds a new 'list-scopes' subcommand that outputs the required OAuth scopes for all enabled tools. This helps users determine what scopes their token needs to use specific tools. Features: - Respects all toolset configuration flags (--toolsets, --read-only) - Three output formats: text (default), json, summary - JSON output includes tools, unique_scopes, scopes_by_tool, tools_by_scope - Calculates accepted scopes (parent scopes that satisfy requirements) - Includes convenience wrapper script at script/list-scopes Usage examples: github-mcp-server list-scopes github-mcp-server list-scopes --toolsets=all --output=json github-mcp-server list-scopes --read-only --output=summary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds a new list-scopes subcommand to help users determine which OAuth scopes their GitHub token needs to use specific tools. The command respects all toolset configuration flags and supports multiple output formats (text, json, summary).
Key changes:
- New CLI command that analyzes enabled toolsets and reports required OAuth scopes
- Calculates both required scopes and accepted parent scopes that satisfy requirements
- Three output formats for different use cases (human-readable, programmatic, and concise)
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| script/list-scopes | Shell wrapper script that builds and executes the list-scopes command with argument forwarding |
| cmd/github-mcp-server/list_scopes.go | Main implementation of the list-scopes subcommand including command definition, scope collection logic, and output formatting functions |
| // Process enabled toolsets (same logic as server.go) | ||
| // If "all" is present, override all other toolsets | ||
| if github.ContainsToolset(enabledToolsets, github.ToolsetMetadataAll.ID) { | ||
| enabledToolsets = []string{github.ToolsetMetadataAll.ID} | ||
| } | ||
| // If "default" is present, expand to real toolset IDs | ||
| if github.ContainsToolset(enabledToolsets, github.ToolsetMetadataDefault.ID) { | ||
| enabledToolsets = github.AddDefaultToolset(enabledToolsets) | ||
| } |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The toolset processing logic is missing a call to github.CleanToolsets() that exists in the stdio command (see internal/ghmcp/server.go line 102). This function:
- Removes whitespace from toolset names
- Removes duplicates
- Validates toolset names and identifies invalid ones
Without this, the command may behave inconsistently with the stdio command when users provide invalid or malformed toolset names. Add the CleanToolsets call before processing special keywords:
// Clean up the passed toolsets
enabledToolsets, invalidToolsets := github.CleanToolsets(enabledToolsets)
// Optionally warn about invalid toolsets (like server.go does)
if len(invalidToolsets) > 0 {
fmt.Fprintf(os.Stderr, "Invalid toolsets ignored: %s\n", strings.Join(invalidToolsets, ", "))
}
// If "all" is present, override all other toolsets
if github.ContainsToolset(enabledToolsets, github.ToolsetMetadataAll.ID) {
enabledToolsets = []string{github.ToolsetMetadataAll.ID}
}
// ...| acceptedScopeStrs := []string{} | ||
| for _, reqScope := range requiredScopes { | ||
| accepted := scopes.GetAcceptedScopes(reqScope) | ||
| for _, accScope := range accepted { | ||
| if accScope != reqScope { // Don't duplicate the required scope | ||
| accStr := accScope.String() | ||
| // Avoid duplicates | ||
| found := false | ||
| for _, existing := range acceptedScopeStrs { | ||
| if existing == accStr { | ||
| found = true | ||
| break | ||
| } | ||
| } | ||
| if !found { | ||
| acceptedScopeStrs = append(acceptedScopeStrs, accStr) | ||
| } | ||
| } | ||
| } | ||
| } |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The manual deduplication of acceptedScopeStrs using nested loops is O(n²) and could be simplified for better maintainability and performance. Consider using a map for deduplication:
// Calculate accepted scopes (scopes that also satisfy the requirement due to hierarchy)
acceptedScopeMap := make(map[string]bool)
for _, reqScope := range requiredScopes {
accepted := scopes.GetAcceptedScopes(reqScope)
for _, accScope := range accepted {
if accScope != reqScope { // Don't duplicate the required scope
acceptedScopeMap[accScope.String()] = true
}
}
}
acceptedScopeStrs := make([]string, 0, len(acceptedScopeMap))
for scope := range acceptedScopeMap {
acceptedScopeStrs = append(acceptedScopeStrs, scope)
}
sort.Strings(acceptedScopeStrs)This approach is more efficient and clearer in intent.
| acceptedScopeStrs := []string{} | |
| for _, reqScope := range requiredScopes { | |
| accepted := scopes.GetAcceptedScopes(reqScope) | |
| for _, accScope := range accepted { | |
| if accScope != reqScope { // Don't duplicate the required scope | |
| accStr := accScope.String() | |
| // Avoid duplicates | |
| found := false | |
| for _, existing := range acceptedScopeStrs { | |
| if existing == accStr { | |
| found = true | |
| break | |
| } | |
| } | |
| if !found { | |
| acceptedScopeStrs = append(acceptedScopeStrs, accStr) | |
| } | |
| } | |
| } | |
| } | |
| // Calculate accepted scopes (scopes that also satisfy the requirement due to hierarchy) | |
| acceptedScopeMap := make(map[string]bool) | |
| for _, reqScope := range requiredScopes { | |
| accepted := scopes.GetAcceptedScopes(reqScope) | |
| for _, accScope := range accepted { | |
| if accScope != reqScope { // Don't duplicate the required scope | |
| acceptedScopeMap[accScope.String()] = true | |
| } | |
| } | |
| } | |
| acceptedScopeStrs := make([]string, 0, len(acceptedScopeMap)) | |
| for scope := range acceptedScopeMap { | |
| acceptedScopeStrs = append(acceptedScopeStrs, scope) | |
| } |
| # Build the server if it doesn't exist or is outdated | ||
| if [ ! -f github-mcp-server ] || [ cmd/github-mcp-server/list_scopes.go -nt github-mcp-server ]; then | ||
| echo "Building github-mcp-server..." >&2 | ||
| go build -o github-mcp-server ./cmd/github-mcp-server | ||
| fi | ||
|
|
||
| exec ./github-mcp-server list-scopes "$@" |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The conditional build check may not detect all necessary rebuilds (e.g., when dependencies in other packages change). For consistency with other scripts like script/generate-docs, consider using go run instead:
exec go run ./cmd/github-mcp-server list-scopes "$@"This approach is simpler, always ensures up-to-date code execution, and aligns with the project's script patterns. The build time overhead is minimal (~1s according to the guidelines).
| # Build the server if it doesn't exist or is outdated | |
| if [ ! -f github-mcp-server ] || [ cmd/github-mcp-server/list_scopes.go -nt github-mcp-server ]; then | |
| echo "Building github-mcp-server..." >&2 | |
| go build -o github-mcp-server ./cmd/github-mcp-server | |
| fi | |
| exec ./github-mcp-server list-scopes "$@" | |
| # Always run the latest code, consistent with other scripts | |
| exec go run ./cmd/github-mcp-server list-scopes "$@" |
|
Note: By default (without $ ./github-mcp-server list-scopes --output=summary
Required OAuth scopes for enabled tools:
(no scope required for public read access)
read:org
repo
Total: 3 unique scope(s)Compare this to all toolsets: $ ./github-mcp-server list-scopes --toolsets=all --output=summary
Required OAuth scopes for enabled tools:
(no scope required for public read access)
gist
notifications
project
public_repo
read:org
read:project
repo
security_events
Total: 9 unique scope(s)This matches the same default behavior as the |
Summary
Adds a new
list-scopessubcommand that outputs the required OAuth scopes for all enabled tools. This helps users determine what scopes their token needs to use specific tools.Part 3 of the OAuth scopes work:
Changes
cmd/github-mcp-server/list_scopes.go- new subcommandscript/list-scopes- convenience wrapper scriptFeatures
--toolsets,--read-only)text(default),json,summaryUsage Examples
Example Output
Testing
script/lint- 0 issuesscript/test- All tests pass