Module: Keycardai::MCP::RackSupport

Defined in:
lib/keycardai/mcp/rack_support.rb

Overview

Shared Rack plumbing: request-origin derivation and the RFC 6750 challenge responses. Not public API.

Class Method Summary collapse

Class Method Details

.challenge_response(env, status:, error: nil, description: nil) ⇒ Object

An RFC 6750 challenge response. A nil error code produces the bare challenge used for missing credentials.



32
33
34
35
36
37
38
39
40
41
# File 'lib/keycardai/mcp/rack_support.rb', line 32

def challenge_response(env, status:, error: nil, description: nil)
  params = []
  params << %(error="#{error}") if error
  params << %(error_description="#{description}") if description
  params << %(resource_metadata="#{(env)}")
  body = JSON.dump({ "error" => error || "unauthorized" })
  [status,
   { "content-type" => "application/json", "www-authenticate" => "Bearer #{params.join(", ")}" },
   [body]]
end

.json_response(payload, status: 200, headers: {}) ⇒ Object



43
44
45
46
47
# File 'lib/keycardai/mcp/rack_support.rb', line 43

def json_response(payload, status: 200, headers: {})
  [status,
   { "content-type" => "application/json", "access-control-allow-origin" => "*" }.merge(headers),
   [JSON.dump(payload)]]
end

.origin(env) ⇒ Object

The request origin (scheme + host [+ non-default port]).



13
14
15
16
17
18
19
20
21
22
23
# File 'lib/keycardai/mcp/rack_support.rb', line 13

def origin(env)
  scheme = env["rack.url_scheme"] || "http"
  host = env["HTTP_HOST"]
  unless host
    host = env["SERVER_NAME"].to_s
    port = env["SERVER_PORT"].to_s
    default = scheme == "https" ? "443" : "80"
    host = "#{host}:#{port}" unless port.empty? || port == default
  end
  "#{scheme}://#{host}"
end

.resource_metadata_url(env) ⇒ Object

The RFC 9728 protected-resource metadata URL advertised in challenges.



26
27
28
# File 'lib/keycardai/mcp/rack_support.rb', line 26

def (env)
  "#{origin(env)}/.well-known/oauth-protected-resource"
end