Module: Mcp::Auth::ProtectedResource

Extended by:
ActiveSupport::Concern
Includes:
ControllerHelpers
Defined in:
lib/mcp/auth/protected_resource.rb

Overview

Resource-server side of the MCP authorization spec. Include this in the controller that serves your MCP endpoint to validate the incoming Bearer token and expose the authenticated principal via Mcp::Auth::ControllerHelpers (mcp_user_id, mcp_scope, …).

class McpController < ApplicationController
  include Mcp::Auth::ProtectedResource
  before_action :authenticate_mcp_token!
  before_action -> { require_mcp_scope!('mcp:read') }, only: :show
end

On a missing/invalid/expired token it answers 401 with the RFC 9728 WWW-Authenticate header so MCP clients can discover the authorization server from the protected-resource metadata.

Instance Method Summary collapse

Methods included from ControllerHelpers

#mcp_api_key, #mcp_authenticated?, #mcp_email, #mcp_org_id, #mcp_scope, #mcp_token, #mcp_user_id

Instance Method Details

#authenticate_mcp_token!Object

Validates the Bearer access token (signature, expiry, revocation status, and — when a resource is configured — the RFC 8707 audience). On success the decoded claims are stashed in request.env for ControllerHelpers and the payload is returned; on failure it renders 401 and halts the action.



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/mcp/auth/protected_resource.rb', line 27

def authenticate_mcp_token!
  token = mcp_bearer_token
  payload = token && Services::TokenService.validate_access_token(token, resource: mcp_resource_identifier)

  unless payload
    render_mcp_unauthorized('invalid_token', 'The access token is missing, invalid, or expired')
    return false
  end

  request.env['mcp.user_id'] = payload[:sub]
  request.env['mcp.org_id']  = payload[:org]
  request.env['mcp.email']   = payload[:email]
  request.env['mcp.token']   = token
  request.env['mcp.scope']   = payload[:scope]
  request.env['mcp.api_key'] = payload[:api_key_id]
  payload
end

#require_mcp_scope!(*required) ⇒ Object

Enforce that the validated token carries every given scope. Renders 403 insufficient_scope and returns false when any is missing.



47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/mcp/auth/protected_resource.rb', line 47

def require_mcp_scope!(*required)
  granted = mcp_scope.to_s.split
  missing = required.map(&:to_s) - granted
  return true if missing.empty?

  render_mcp_unauthorized(
    'insufficient_scope',
    "Missing required scope: #{missing.join(' ')}",
    status: :forbidden
  )
  false
end