Class: MCPClient::Auth::OAuthProvider

Inherits:
Object
  • Object
show all
Defined in:
lib/mcp_client/auth/oauth_provider.rb

Overview

OAuth 2.1 provider for MCP client authentication Handles the complete OAuth flow including server discovery, client registration, authorization, token exchange, and refresh

Defined Under Namespace

Classes: MemoryStorage

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(server_url:, redirect_uri: 'http://localhost:8080/callback', scope: nil, logger: nil, storage: nil) ⇒ OAuthProvider

Initialize OAuth provider

Parameters:

  • server_url (String)

    The MCP server URL (used as OAuth resource parameter)

  • redirect_uri (String) (defaults to: 'http://localhost:8080/callback')

    OAuth redirect URI (default: localhost:8080/callback)

  • scope (String, nil) (defaults to: nil)

    OAuth scope

  • logger (Logger, nil) (defaults to: nil)

    Optional logger

  • storage (Object, nil) (defaults to: nil)

    Storage backend for tokens and client info



33
34
35
36
37
38
39
40
# File 'lib/mcp_client/auth/oauth_provider.rb', line 33

def initialize(server_url:, redirect_uri: 'http://localhost:8080/callback', scope: nil, logger: nil, storage: nil)
  self.server_url = server_url
  self.redirect_uri = redirect_uri
  self.scope = scope
  self.logger = logger || Logger.new($stdout, level: Logger::WARN)
  self.storage = storage || MemoryStorage.new
  @http_client = create_http_client
end

Instance Attribute Details

#loggerLogger

Returns Logger instance.

Returns:

  • (Logger)

    Logger instance



24
# File 'lib/mcp_client/auth/oauth_provider.rb', line 24

attr_accessor :redirect_uri, :scope, :logger, :storage

#redirect_uriString

Returns OAuth redirect URI.

Returns:

  • (String)

    OAuth redirect URI



24
25
26
# File 'lib/mcp_client/auth/oauth_provider.rb', line 24

def redirect_uri
  @redirect_uri
end

#scopeString?

Returns OAuth scope.

Returns:

  • (String, nil)

    OAuth scope



24
# File 'lib/mcp_client/auth/oauth_provider.rb', line 24

attr_accessor :redirect_uri, :scope, :logger, :storage

#server_urlString

Returns The MCP server URL (normalized).

Returns:

  • (String)

    The MCP server URL (normalized)



24
# File 'lib/mcp_client/auth/oauth_provider.rb', line 24

attr_accessor :redirect_uri, :scope, :logger, :storage

#storageObject

Returns Storage backend for tokens and client info.

Returns:

  • (Object)

    Storage backend for tokens and client info



24
# File 'lib/mcp_client/auth/oauth_provider.rb', line 24

attr_accessor :redirect_uri, :scope, :logger, :storage

Instance Method Details

#access_tokenToken?

Get current access token (refresh if needed)

Returns:

  • (Token, nil)

    Current valid access token or nil



49
50
51
52
53
54
55
56
57
58
59
# File 'lib/mcp_client/auth/oauth_provider.rb', line 49

def access_token
  token = storage.get_token(server_url)
  logger.debug("OAuth access_token: retrieved token=#{token ? 'present' : 'nil'} for #{server_url}")
  return nil unless token

  # Return token if still valid
  return token unless token.expired? || token.expires_soon?

  # Try to refresh if we have a refresh token
  refresh_token(token) if token.refresh_token
end

#apply_authorization(request) ⇒ void

This method returns an undefined value.

Apply OAuth authorization to HTTP request

Parameters:

  • request (Faraday::Request)

    HTTP request to authorize



117
118
119
120
121
122
123
124
# File 'lib/mcp_client/auth/oauth_provider.rb', line 117

def apply_authorization(request)
  token = access_token
  logger.debug("OAuth apply_authorization: token=#{token ? 'present' : 'nil'}")
  return unless token

  logger.debug("OAuth applying authorization header: #{token.to_header[0..20]}...")
  request.headers['Authorization'] = token.to_header
end

#complete_authorization_flow(code, state) ⇒ Token

Complete OAuth authorization flow with authorization code

Parameters:

  • code (String)

    Authorization code from callback

  • state (String)

    State parameter from callback

Returns:

  • (Token)

    Access token

Raises:



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/mcp_client/auth/oauth_provider.rb', line 89

def complete_authorization_flow(code, state)
  # Verify state parameter
  stored_state = storage.get_state(server_url)
  raise ArgumentError, 'Invalid state parameter' unless stored_state == state

  # Get stored PKCE and client info
  pkce = storage.get_pkce(server_url)
  client_info = storage.get_client_info(server_url)
   = discover_authorization_server

  raise MCPClient::Errors::ConnectionError, 'Missing PKCE or client info' unless pkce && client_info

  # Exchange authorization code for tokens
  token = exchange_authorization_code(, client_info, code, pkce)

  # Store token
  storage.set_token(server_url, token)

  # Clean up temporary data
  storage.delete_pkce(server_url)
  storage.delete_state(server_url)

  token
end

#handle_unauthorized_response(response) ⇒ ResourceMetadata?

Handle 401 Unauthorized response (for server discovery)

Parameters:

  • response (Faraday::Response)

    HTTP response

Returns:



129
130
131
132
133
134
135
136
137
138
139
# File 'lib/mcp_client/auth/oauth_provider.rb', line 129

def handle_unauthorized_response(response)
  www_authenticate = response.headers['WWW-Authenticate'] || response.headers['www-authenticate']
  return nil unless www_authenticate

  # Parse WWW-Authenticate header to extract resource metadata URL
  # Format: Bearer resource="https://example.com/.well-known/oauth-protected-resource"
  if (match = www_authenticate.match(/resource="([^"]+)"/))
     = match[1]
    ()
  end
end

#start_authorization_flowString

Start OAuth authorization flow

Returns:

  • (String)

    Authorization URL to redirect user to

Raises:



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/mcp_client/auth/oauth_provider.rb', line 64

def start_authorization_flow
  # Discover authorization server
   = discover_authorization_server

  # Register client if needed
  client_info = get_or_register_client()

  # Generate PKCE parameters
  pkce = PKCE.new
  storage.set_pkce(server_url, pkce)

  # Generate state parameter
  state = SecureRandom.urlsafe_base64(32)
  storage.set_state(server_url, state)

  # Build authorization URL
  build_authorization_url(, client_info, pkce, state)
end