github-mcp-server icon indicating copy to clipboard operation
github-mcp-server copied to clipboard

feat: Add exported ToolScopeMap for library use

Open SamMorrowDrums opened this issue 1 month ago • 0 comments

Summary

Adds exported types and functions in pkg/scopes for library users who need fast OAuth scope lookups at runtime.

Part 4 (final) of the OAuth scopes work:

  • PR #1485: Phase 1 - OAuth scopes on tool metadata
  • PR #1486: Phase 2 - Fine-grained permissions documentation
  • PR #1487: Phase 3 - list-scopes command
  • This PR: Phase 4 - Exported ToolScopeMap for library use

Changes

  • Add pkg/scopes/tool_scope_map.go - exported types and functions
  • Add pkg/scopes/tool_scope_map_test.go - comprehensive tests

Exported Types

ToolScopeMap

map[string]*ToolScopeInfo for tool name -> scopes lookup

ToolScopeInfo

Contains RequiredScopes and AcceptedScopes as ScopeSet

ScopeSet

map[string]bool for O(1) scope lookup performance

Key Functions

  • BuildToolScopeMapFromMeta(tools []ToolMeta) - builds map from tool definitions
  • NewToolScopeInfo(required []Scope) - creates info from required scopes, auto-calculates accepted scopes
  • GetToolScopeInfo(meta map[string]any) - creates info from tool Meta field

Key Methods

  • ToolScopeInfo.HasAcceptedScope(userScopes ...string) - checks if token has access
  • ToolScopeInfo.MissingScopes(userScopes ...string) - returns missing required scopes
  • ToolScopeMap.AllRequiredScopes() - returns all unique required scopes
  • ToolScopeMap.ToolsRequiringScope(scope) - returns tools that require a scope
  • ToolScopeMap.ToolsAcceptingScope(scope) - returns tools that accept a scope

Usage Example

import "github.com/github/github-mcp-server/pkg/scopes"

// Build scope map from tool definitions
tools := []scopes.ToolMeta{
    {Name: "get_repo", Meta: someToolMeta},
    {Name: "create_issue", Meta: anotherToolMeta},
}
scopeMap := scopes.BuildToolScopeMapFromMeta(tools)

// Check if user's token can use a tool
if info, ok := scopeMap["create_issue"]; ok {
    userScopes := []string{"repo", "user"}
    if info.HasAcceptedScope(userScopes...) {
        // User can use this tool
    } else {
        missing := info.MissingScopes(userScopes...)
        fmt.Printf("Missing scopes: %v\n", missing)
    }
}

// Get all required scopes
allRequired := scopeMap.AllRequiredScopes()
fmt.Printf("All required: %v\n", allRequired.ToSlice())

Design Decisions

  • ScopeSet uses map[string]bool - O(1) lookup performance for production environments
  • AcceptedScopes includes parent scopes - If a tool requires public_repo, repo is also accepted due to hierarchy
  • Functions work with minimal interfaces - ToolMeta struct only requires Name and Meta fields

Testing

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

SamMorrowDrums avatar Nov 25 '25 13:11 SamMorrowDrums