Class: Mcp::Auth::ScopeRegistry

Inherits:
Object
  • Object
show all
Defined in:
lib/mcp/auth/scope_registry.rb

Overview

ScopeRegistry manages OAuth scopes for MCP Auth

By default, provides basic MCP scopes (mcp:read, mcp:write) automatically. Applications can register custom scopes which will replace the defaults.

Example:

Mcp::Auth::ScopeRegistry.register_scope('mcp:tools',
  name: 'Tool Execution',
  description: 'Execute tools and actions',
  required: false
)

Constant Summary collapse

STANDARD_OIDC_SCOPES =

Standard OpenID Connect scopes. These are not application resource scopes (so they are not registered or shown as consent checkboxes) but are recognized when present so OIDC discovery/id_token issuance works.

%w[openid profile email].freeze

Class Method Summary collapse

Class Method Details

.available_scopesObject

All available scopes If no scopes are registered, returns basic MCP scopes for backwards compatibility



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/mcp/auth/scope_registry.rb', line 30

def available_scopes
  return custom_scopes unless custom_scopes.empty?

  # Fallback: If no scopes registered, use basic MCP scopes
  # This ensures backwards compatibility
  {
    'mcp:read' => {
      name: 'Read Access',
      description: 'Read your data and resources',
      required: true
    },
    'mcp:write' => {
      name: 'Write Access',
      description: 'Create and modify data on your behalf',
      required: false
    }
  }
end

.clear_scopes!Object

Clear all registered scopes (useful for testing)



59
60
61
# File 'lib/mcp/auth/scope_registry.rb', line 59

def clear_scopes!
  @custom_scopes = {}
end

.custom_scopesObject

Custom scopes registered by the application



24
25
26
# File 'lib/mcp/auth/scope_registry.rb', line 24

def custom_scopes
  @custom_scopes ||= {}
end

.default_scope_stringObject

Get default scopes string for a client



109
110
111
112
# File 'lib/mcp/auth/scope_registry.rb', line 109

def default_scope_string
  # Return all registered scopes, or empty string if none
  available_scopes.keys.join(' ')
end

.format_for_display(requested_scopes) ⇒ Object

Format scopes for consent screen display



94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/mcp/auth/scope_registry.rb', line 94

def format_for_display(requested_scopes)
  scopes = requested_scopes.is_a?(String) ? requested_scopes.split : requested_scopes

  scopes.map do |scope|
     = (scope)
    {
      key: scope,
      name: [:name],
      description: [:description],
      required: [:required]
    }
  end
end

.register_scope(scope_key, name:, description:, required: false) ⇒ Object

Register a custom scope



50
51
52
53
54
55
56
# File 'lib/mcp/auth/scope_registry.rb', line 50

def register_scope(scope_key, name:, description:, required: false)
  custom_scopes[scope_key.to_s] = {
    name: name,
    description: description,
    required: required
  }
end

.scope_exists?(scope) ⇒ Boolean

Check if a scope exists

Returns:

  • (Boolean)


64
65
66
# File 'lib/mcp/auth/scope_registry.rb', line 64

def scope_exists?(scope)
  available_scopes.key?(scope.to_s)
end

.scope_metadata(scope) ⇒ Object

Get scope metadata



69
70
71
72
73
74
75
# File 'lib/mcp/auth/scope_registry.rb', line 69

def (scope)
  available_scopes[scope.to_s] || {
    name: scope,
    description: scope,
    required: false
  }
end

.validate_scopes(requested_scopes) ⇒ Object

Validate and filter requested scopes



78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/mcp/auth/scope_registry.rb', line 78

def validate_scopes(requested_scopes)
  # If no scopes requested, return all required scopes
  return available_scopes.select { |_, meta| meta[:required] }.keys if requested_scopes.blank?

  scopes = requested_scopes.is_a?(String) ? requested_scopes.split : requested_scopes

  # Filter to only valid registered scopes
  valid_scopes = scopes.select { |scope| scope_exists?(scope) }

  # Always include required scopes
  required_scopes = available_scopes.select { |_, meta| meta[:required] }.keys

  (valid_scopes + required_scopes).uniq
end