Class: RubyLLM::MCP::Auth::UrlBuilder

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

Overview

Utility class for building OAuth URLs Handles discovery URLs, authorization URLs, and URL normalization

Class Method Summary collapse

Class Method Details

.build_authorization_server_metadata_urls(issuer_url) ⇒ Array<String>

Build authorization server metadata URLs (RFC 8414 + OIDC compatibility)

Parameters:

  • issuer_url (String)

    authorization server issuer URL

Returns:

  • (Array<String>)

    metadata URLs in required priority order



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/ruby_llm/mcp/auth/url_builder.rb', line 53

def self.(issuer_url)
  uri = URI.parse(issuer_url)
  origin = origin_for(uri)
  path_component = normalized_path_component(uri.path)

  if path_component
    [
      "#{origin}/.well-known/oauth-authorization-server/#{path_component}",
      "#{origin}/.well-known/openid-configuration/#{path_component}",
      "#{origin}/#{path_component}/.well-known/openid-configuration"
    ]
  else
    [
      "#{origin}/.well-known/oauth-authorization-server",
      "#{origin}/.well-known/openid-configuration"
    ]
  end
end

.build_authorization_url(authorization_endpoint, client_id, redirect_uri, scope, state, pkce, resource) ⇒ String

Build OAuth authorization URL

Parameters:

  • authorization_endpoint (String)

    auth server endpoint

  • client_id (String)

    client ID

  • redirect_uri (String)

    redirect URI

  • scope (String, nil)

    requested scope

  • state (String)

    CSRF state

  • pkce (PKCE)

    PKCE parameters

  • resource (String)

    resource indicator (RFC 8707)

Returns:

  • (String)

    authorization URL



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/ruby_llm/mcp/auth/url_builder.rb', line 81

def self.build_authorization_url(authorization_endpoint, client_id, redirect_uri, scope, state, pkce, resource) # rubocop:disable Metrics/ParameterLists
  params = {
    response_type: "code",
    client_id: client_id,
    redirect_uri: redirect_uri,
    scope: scope,
    state: state, # CSRF protection
    code_challenge: pkce.code_challenge,
    code_challenge_method: pkce.code_challenge_method, # S256
    resource: resource # RFC 8707 - Resource Indicators
  }.compact

  uri = URI.parse(authorization_endpoint)
  uri.query = URI.encode_www_form(params)
  uri.to_s
end

.build_discovery_url(server_url, discovery_type = :authorization_server) ⇒ String

Build discovery URL for OAuth server metadata

Parameters:

  • server_url (String)

    MCP server URL

  • discovery_type (Symbol) (defaults to: :authorization_server)

    :authorization_server or :protected_resource

Returns:

  • (String)

    discovery URL



13
14
15
# File 'lib/ruby_llm/mcp/auth/url_builder.rb', line 13

def self.build_discovery_url(server_url, discovery_type = :authorization_server)
  build_discovery_urls(server_url, discovery_type).first
end

.build_discovery_urls(server_url, discovery_type = :authorization_server) ⇒ Array<String>

Build ordered discovery URLs for OAuth metadata

Parameters:

  • server_url (String)

    MCP server URL

  • discovery_type (Symbol) (defaults to: :authorization_server)

    :authorization_server or :protected_resource

Returns:

  • (Array<String>)

    discovery URLs in priority order



21
22
23
24
25
26
27
28
29
30
# File 'lib/ruby_llm/mcp/auth/url_builder.rb', line 21

def self.build_discovery_urls(server_url, discovery_type = :authorization_server)
  case discovery_type
  when :authorization_server
    (server_url)
  when :protected_resource
    (server_url)
  else
    raise ArgumentError, "Unknown discovery type: #{discovery_type}"
  end
end

.build_protected_resource_metadata_urls(server_url) ⇒ Array<String>

Build protected resource metadata URLs (RFC 9728 / MCP Section 4.2) Ordered as required by the MCP spec:

  1. Path-based well-known URI
  2. Root well-known URI

Parameters:

  • server_url (String)

    MCP server URL

Returns:

  • (Array<String>)

    protected resource metadata URLs in priority order



38
39
40
41
42
43
44
45
46
47
48
# File 'lib/ruby_llm/mcp/auth/url_builder.rb', line 38

def self.(server_url)
  uri = URI.parse(server_url)
  origin = origin_for(uri)
  endpoint = "oauth-protected-resource"
  path_component = normalized_path_component(uri.path)

  urls = []
  urls << "#{origin}/.well-known/#{endpoint}/#{path_component}" if path_component
  urls << "#{origin}/.well-known/#{endpoint}"
  urls.uniq
end

.default_port?(uri) ⇒ Boolean

Check if port is default for scheme

Parameters:

  • uri (URI)

    parsed URI

Returns:

  • (Boolean)

    true if default port



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

def self.default_port?(uri)
  (uri.scheme == "http" && uri.port == 80) ||
    (uri.scheme == "https" && uri.port == 443)
end

.get_authorization_base_url(server_url) ⇒ String

Get authorization base URL from server URL

Parameters:

  • server_url (String)

    MCP server URL

Returns:

  • (String)

    authorization base URL (scheme + host + port)



101
102
103
104
# File 'lib/ruby_llm/mcp/auth/url_builder.rb', line 101

def self.get_authorization_base_url(server_url)
  uri = URI.parse(server_url)
  origin_for(uri)
end

.normalized_path_component(path) ⇒ String?

Convert URI path to a clean path component with no leading/trailing slash

Parameters:

  • path (String, nil)

    path from URI

Returns:

  • (String, nil)

    normalized path component or nil for root



126
127
128
129
130
131
# File 'lib/ruby_llm/mcp/auth/url_builder.rb', line 126

def self.normalized_path_component(path)
  return nil if path.nil? || path.empty? || path == "/"

  cleaned = path.split("/").reject(&:empty?).join("/")
  cleaned.empty? ? nil : cleaned
end

.origin_for(uri) ⇒ String

Build scheme://host(:port)

Parameters:

  • uri (URI)

    parsed URI

Returns:

  • (String)

    origin URL



117
118
119
120
121
# File 'lib/ruby_llm/mcp/auth/url_builder.rb', line 117

def self.origin_for(uri)
  origin = "#{uri.scheme}://#{uri.host}"
  origin += ":#{uri.port}" if uri.port && !default_port?(uri)
  origin
end