Class: Keycardai::MCP::MetadataApp

Inherits:
Object
  • Object
show all
Defined in:
lib/keycardai/mcp/metadata_app.rb

Overview

Rack app serving the resource server's OAuth discovery surface (RFC 9728 / RFC 8414): the protected-resource metadata (with path insertion for sub-path mounts), a server-side proxy of the zone's authorization-server metadata (with the resource= rewrite shim for pre-RFC-8707 MCP clients), and optionally the server's own public JWKS. Responses carry permissive CORS so browser clients can read them.

run Keycardai::MCP::MetadataApp.new(issuer: zone_url, scopes_supported: ["mcp:tools"])

Constant Summary collapse

PROTECTED_RESOURCE_PATH =
"/.well-known/oauth-protected-resource"
AUTHORIZATION_SERVER_PATH =
"/.well-known/oauth-authorization-server"
JWKS_PATH =
"/.well-known/jwks.json"
LEGACY_MCP_PROTOCOL_VERSION =
"2025-03-26"

Instance Method Summary collapse

Constructor Details

#initialize(issuer:, scopes_supported: nil, resource_name: nil, resource_documentation: nil, public_jwks: nil, http_client: Keycardai::OAuth::HTTP::NetHTTPClient.new, timeout: 10) ⇒ MetadataApp

Returns a new instance of MetadataApp.

Parameters:

  • issuer (String)

    the zone's issuer URL

  • scopes_supported (Array<String>, nil) (defaults to: nil)
  • resource_name (String, nil) (defaults to: nil)
  • resource_documentation (String, nil) (defaults to: nil)
  • public_jwks (Hash, nil) (defaults to: nil)

    served at /.well-known/jwks.json when set

  • http_client (#get) (defaults to: Keycardai::OAuth::HTTP::NetHTTPClient.new)

    transport for the AS-metadata proxy

  • timeout (Numeric) (defaults to: 10)

    upstream fetch timeout

Raises:

  • (Keycardai::OAuth::ConfigurationError)


28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/keycardai/mcp/metadata_app.rb', line 28

def initialize(issuer:, scopes_supported: nil, resource_name: nil, resource_documentation: nil,
               public_jwks: nil, http_client: Keycardai::OAuth::HTTP::NetHTTPClient.new, timeout: 10)
  raise Keycardai::OAuth::ConfigurationError, "MetadataApp requires an issuer" if issuer.nil? || issuer.empty?

  @issuer = issuer
  @scopes_supported = scopes_supported
  @resource_name = resource_name
  @resource_documentation = resource_documentation
  @public_jwks = public_jwks
  @http_client = http_client
  @timeout = timeout
end

Instance Method Details

#call(env) ⇒ Object



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/keycardai/mcp/metadata_app.rb', line 41

def call(env)
  return preflight_response if env["REQUEST_METHOD"] == "OPTIONS"

  path = env["PATH_INFO"].to_s
  case path
  when %r{\A#{Regexp.escape(PROTECTED_RESOURCE_PATH)}(/.*)?\z}
    protected_resource_response(env, Regexp.last_match(1).to_s)
  when AUTHORIZATION_SERVER_PATH
    authorization_server_response(env)
  when JWKS_PATH
    jwks_response
  else
    RackSupport.json_response({ "error" => "not_found" }, status: 404)
  end
end