Skip to content

Conversation

@SamMorrowDrums
Copy link
Collaborator

Summary

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.

Part 3 of the OAuth scopes work:

Changes

  • Add cmd/github-mcp-server/list_scopes.go - new subcommand
  • Add script/list-scopes - convenience wrapper script

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)

Usage Examples

# List scopes for default toolsets
github-mcp-server list-scopes

# List scopes for specific toolsets  
github-mcp-server list-scopes --toolsets=repos,issues,pull_requests

# List scopes for all toolsets
github-mcp-server list-scopes --toolsets=all

# Output as JSON (for programmatic use)
github-mcp-server list-scopes --output=json

# Just show unique scopes needed
github-mcp-server list-scopes --output=summary

# Read-only mode (excludes write tools)
github-mcp-server list-scopes --read-only --output=summary

Example Output

$ 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)

Testing

  • script/lint - 0 issues
  • script/test - All tests pass

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
Copilot AI review requested due to automatic review settings November 25, 2025 13:27
@SamMorrowDrums SamMorrowDrums requested a review from a team as a code owner November 25, 2025 13:27
Copilot finished reviewing on behalf of SamMorrowDrums November 25, 2025 13:32
Copy link
Contributor

Copilot AI left a 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

Comment on lines +120 to +128
// 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)
}
Copy link

Copilot AI Nov 25, 2025

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:

  1. Removes whitespace from toolset names
  2. Removes duplicates
  3. 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}
}
// ...

Copilot uses AI. Check for mistakes.
Comment on lines +182 to +201
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)
}
}
}
}
Copy link

Copilot AI Nov 25, 2025

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.

Suggested change
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)
}

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +24
# 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 "$@"
Copy link

Copilot AI Nov 25, 2025

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).

Suggested change
# 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 "$@"

Copilot uses AI. Check for mistakes.
@SamMorrowDrums
Copy link
Collaborator Author

Note: By default (without --toolsets), the command shows scopes for the default toolset (context, repos, issues, pull_requests, users):

$ ./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 stdio command.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants