package scopes import "github.com/github/github-mcp-server/pkg/inventory" // ToolScopeMap maps tool names to their scope requirements. type ToolScopeMap map[string]*ToolScopeInfo // ToolScopeInfo contains scope information for a single tool. type ToolScopeInfo struct { // RequiredScopes contains the scopes that are directly required by this tool. RequiredScopes []string // AcceptedScopes contains all scopes that satisfy the requirements (including parent scopes). AcceptedScopes []string } // globalToolScopeMap is populated from inventory when SetToolScopeMapFromInventory is called var globalToolScopeMap ToolScopeMap // SetToolScopeMapFromInventory builds and stores a tool scope map from an inventory. // This should be called after building the inventory to make scopes available for middleware. func SetToolScopeMapFromInventory(inv *inventory.Inventory) { globalToolScopeMap = GetToolScopeMapFromInventory(inv) } // SetGlobalToolScopeMap sets the global tool scope map directly. // This is useful for testing when you don't have a full inventory. func SetGlobalToolScopeMap(m ToolScopeMap) { globalToolScopeMap = m } // GetToolScopeMap returns the global tool scope map. // Returns an empty map if SetToolScopeMapFromInventory hasn't been called yet. func GetToolScopeMap() (ToolScopeMap, error) { if globalToolScopeMap == nil { return make(ToolScopeMap), nil } return globalToolScopeMap, nil } // GetToolScopeInfo returns scope information for a specific tool from the global scope map. func GetToolScopeInfo(toolName string) (*ToolScopeInfo, error) { m, err := GetToolScopeMap() if err != nil { return nil, err } return m[toolName], nil } // GetToolScopeMapFromInventory builds a tool scope map from an inventory. // This extracts scope information from ServerTool.RequiredScopes and ServerTool.AcceptedScopes. func GetToolScopeMapFromInventory(inv *inventory.Inventory) ToolScopeMap { result := make(ToolScopeMap) // Get all tools from the inventory (both enabled and disabled) // We need all tools for scope checking purposes allTools := inv.AllTools() for i := range allTools { tool := &allTools[i] if len(tool.RequiredScopes) > 0 || len(tool.AcceptedScopes) > 0 { result[tool.Tool.Name] = &ToolScopeInfo{ RequiredScopes: tool.RequiredScopes, AcceptedScopes: tool.AcceptedScopes, } } } return result } // HasAcceptedScope checks if any of the provided user scopes satisfy the tool's requirements. func (t *ToolScopeInfo) HasAcceptedScope(userScopes ...string) bool { if t == nil || len(t.AcceptedScopes) == 0 { return true // No scopes required } userScopeSet := make(map[string]bool) for _, scope := range userScopes { userScopeSet[scope] = true } for _, scope := range t.AcceptedScopes { if userScopeSet[scope] { return true } } return false } // MissingScopes returns the required scopes that are not present in the user's scopes. func (t *ToolScopeInfo) MissingScopes(userScopes ...string) []string { if t == nil || len(t.RequiredScopes) == 0 { return nil } // Create a set of user scopes for O(1) lookup userScopeSet := make(map[string]bool, len(userScopes)) for _, s := range userScopes { userScopeSet[s] = true } // Check if any accepted scope is present hasAccepted := false for _, scope := range t.AcceptedScopes { if userScopeSet[scope] { hasAccepted = true break } } if hasAccepted { return nil // User has sufficient scopes } // Return required scopes as the minimum needed missing := make([]string, len(t.RequiredScopes)) copy(missing, t.RequiredScopes) return missing } // GetRequiredScopesSlice returns the required scopes as a slice of strings. func (t *ToolScopeInfo) GetRequiredScopesSlice() []string { if t == nil { return nil } scopes := make([]string, len(t.RequiredScopes)) copy(scopes, t.RequiredScopes) return scopes }