- Notifications
You must be signed in to change notification settings - Fork3.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 ourterms of service andprivacy 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 OAuthscopes for all enabled tools. This helps users determine what scopestheir 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-scopesUsage 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 newlist-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) | ||
| } |
CopilotAINov 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 togithub.CleanToolsets() that exists in the stdio command (seeinternal/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 toolsetsenabledToolsets,invalidToolsets:=github.CleanToolsets(enabledToolsets)// Optionally warn about invalid toolsets (like server.go does)iflen(invalidToolsets)>0 {fmt.Fprintf(os.Stderr,"Invalid toolsets ignored: %s\n",strings.Join(invalidToolsets,", "))}// If "all" is present, override all other toolsetsifgithub.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) | ||
| } | ||
| } | ||
| } | ||
| } |
CopilotAINov 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 ofacceptedScopeStrs 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:=rangerequiredScopes {accepted:=scopes.GetAcceptedScopes(reqScope)for_,accScope:=rangeaccepted {ifaccScope!=reqScope {// Don't duplicate the required scopeacceptedScopeMap[accScope.String()]=true } }}acceptedScopeStrs:=make([]string,0,len(acceptedScopeMap))forscope:=rangeacceptedScopeMap {acceptedScopeStrs=append(acceptedScopeStrs,scope)}sort.Strings(acceptedScopeStrs)
This approach is more efficient and clearer in intent.
| acceptedScopeStrs:= []string{} | |
| for_,reqScope:=rangerequiredScopes { | |
| accepted:=scopes.GetAcceptedScopes(reqScope) | |
| for_,accScope:=rangeaccepted { | |
| ifaccScope!=reqScope {// Don't duplicate the required scope | |
| accStr:=accScope.String() | |
| // Avoid duplicates | |
| found:=false | |
| for_,existing:=rangeacceptedScopeStrs { | |
| ifexisting==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:=rangerequiredScopes { | |
| accepted:=scopes.GetAcceptedScopes(reqScope) | |
| for_,accScope:=rangeaccepted { | |
| ifaccScope!=reqScope {// Don't duplicate the required scope | |
| acceptedScopeMap[accScope.String()]=true | |
| } | |
| } | |
| } | |
| acceptedScopeStrs:=make([]string,0,len(acceptedScopeMap)) | |
| forscope:=rangeacceptedScopeMap { | |
| 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 "$@" |
CopilotAINov 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 likescript/generate-docs, consider usinggo 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 "$@" |
SamMorrowDrums commentedNov 25, 2025
Note: By default (without $ ./github-mcp-server list-scopes --output=summaryRequired OAuth scopesfor enabled tools: (no scope requiredfor publicread access) read:org repoTotal: 3 unique scope(s) Compare this to all toolsets: $ ./github-mcp-server list-scopes --toolsets=all --output=summaryRequired OAuth scopesfor enabled tools: (no scope requiredfor publicread access) gist notifications project public_repo read:org read:project repo security_eventsTotal: 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