Add per-call OAuth scope checks by SamMorrowDrums · Pull Request #3128 · github/github-mcp-server · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 84 additions & 101 deletions README.md

Large diffs are not rendered by default.

40 changes: 2 additions & 38 deletions cmd/github-mcp-server/generate_docs.go
47 changes: 21 additions & 26 deletions cmd/github-mcp-server/list_scopes.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@ import (

// ToolScopeInfo contains scope information for a single tool.
type ToolScopeInfo struct {
Name string `json:"name"`
Toolset string `json:"toolset"`
ReadOnly bool `json:"read_only"`
RequiredScopes []string `json:"required_scopes"`
AcceptedScopes []string `json:"accepted_scopes,omitempty"`
Name string `json:"name"`
Toolset string `json:"toolset"`
ReadOnly bool `json:"read_only"`
ChallengeScopes []string `json:"challenge_scopes,omitempty"`
}

// ScopesOutput is the full output structure for the list-scopes command.
Expand All @@ -36,12 +35,11 @@ type ScopesOutput struct {

var listScopesCmd = &cobra.Command{
Use: "list-scopes",
Short: "List required OAuth scopes for enabled tools",
Long: `List the required OAuth scopes for all enabled tools.
Short: "List OAuth scope policies for enabled tools",
Long: `List the OAuth challenge scopes for all enabled tools.

This command creates an inventory based on the same flags as the stdio command
and outputs the required OAuth scopes for each enabled tool. This is useful for
determining what scopes a token needs to use specific tools.
and outputs the scopes each enabled tool may request in an OAuth challenge.

The output format can be controlled with the --output flag:
- text (default): Human-readable text output
Expand Down Expand Up @@ -153,30 +151,27 @@ func collectToolScopes(inv *inventory.Inventory, readOnly bool) ScopesOutput {
for _, serverTool := range availableTools {
tool := serverTool.Tool

// Get scope information directly from ServerTool
requiredScopes := serverTool.RequiredScopes
acceptedScopes := serverTool.AcceptedScopes
challengeScopes := serverTool.ScopeAccess.Scopes

// Determine if tool is read-only
isReadOnly := serverTool.IsReadOnly()

toolInfo := ToolScopeInfo{
Name: tool.Name,
Toolset: string(serverTool.Toolset.ID),
ReadOnly: isReadOnly,
RequiredScopes: requiredScopes,
AcceptedScopes: acceptedScopes,
Name: tool.Name,
Toolset: string(serverTool.Toolset.ID),
ReadOnly: isReadOnly,
ChallengeScopes: challengeScopes,
}
tools = append(tools, toolInfo)

// Track unique scopes
for _, s := range requiredScopes {
for _, s := range challengeScopes {
scopeSet[s] = true
toolsByScope[s] = append(toolsByScope[s], tool.Name)
}

// Track scopes by tool
scopesByTool[tool.Name] = requiredScopes
scopesByTool[tool.Name] = challengeScopes
}

// Sort tools by name
Expand Down Expand Up @@ -225,7 +220,7 @@ func outputSummary(output ScopesOutput) error {
return nil
}

fmt.Println("Required OAuth scopes for enabled tools:")
fmt.Println("OAuth scope policies for enabled tools:")
fmt.Println()
for _, scope := range output.UniqueScopes {
fmt.Printf(" %s\n", formatScopeDisplay(scope))
Expand All @@ -235,8 +230,8 @@ func outputSummary(output ScopesOutput) error {
}

func outputText(output ScopesOutput) error {
fmt.Printf("OAuth Scopes for Enabled Tools\n")
fmt.Printf("==============================\n\n")
fmt.Printf("OAuth Challenge Scopes for Enabled Tools\n")
fmt.Printf("========================================\n\n")

fmt.Printf("Enabled Toolsets: %s\n", strings.Join(output.EnabledToolsets, ", "))
fmt.Printf("Read-Only Mode: %v\n\n", output.ReadOnly)
Expand Down Expand Up @@ -265,8 +260,8 @@ func outputText(output ScopesOutput) error {
}

scopeStr := "(no scope required)"
if len(tool.RequiredScopes) > 0 {
scopeStr = strings.Join(tool.RequiredScopes, ", ")
if len(tool.ChallengeScopes) > 0 {
scopeStr = strings.Join(tool.ChallengeScopes, ", ")
}

fmt.Printf(" %s %s: %s\n", rwIndicator, tool.Name, scopeStr)
Expand All @@ -278,9 +273,9 @@ func outputText(output ScopesOutput) error {
fmt.Println("## Summary")
fmt.Println()
if len(output.UniqueScopes) == 0 {
fmt.Println("No OAuth scopes required for enabled tools.")
fmt.Println("No OAuth scopes are used by enabled tools.")
} else {
fmt.Println("Unique scopes required:")
fmt.Println("Unique challenge scopes:")
for _, scope := range output.UniqueScopes {
fmt.Printf(" • %s\n", formatScopeDisplay(scope))
}
Expand Down
38 changes: 8 additions & 30 deletions cmd/github-mcp-server/main_test.go
Loading
Loading