Merge pull request #408 from koic/dcr_application_type

Set OIDC `application_type` on Dynamic Client Registration per SEP-837
This commit is contained in:
Koichi ITO 2026-06-17 10:57:19 +09:00 • committed by GitHub
commit aac41cdff8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 140 additions and 1 deletions

View file

@ -237,6 +237,23 @@ module MCP
false
end
# Infers the OIDC Dynamic Client Registration `application_type` for a client from its `redirect_uris`.
# Per SEP-837, MCP clients MUST specify an appropriate application type during Dynamic Client Registration
# so the authorization server can apply the matching redirect URI policy.
#
# Returns `"native"` when every redirect URI is a native-app URI: a custom non-http(s) scheme (RFC 8252 Section 7.1)
# or an http(s) URI whose host is a loopback address (`localhost`, `127.0.0.0/8`, or `::1`, RFC 8252 Section 7.3).
# Returns `"web"` otherwise, including when `redirect_uris` is nil, empty, or contains an unparseable URI.
#
# - https://github.com/modelcontextprotocol/modelcontextprotocol/pull/837
# - https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata
def infer_application_type(redirect_uris)
uris = Array(redirect_uris)
return "web" if uris.empty?
uris.all? { |uri| native_redirect_uri?(uri) } ? "native" : "web"
end
# Like `canonicalize_url` but also strips query string, fragment, and
# userinfo. This variant is used for identity comparison against
# the request URL Faraday actually sends, which differs from the value
@ -345,6 +362,20 @@ module MCP
nil
end
# A redirect URI counts as native when it uses a custom non-http(s) scheme
# (e.g. `com.example.app:/callback`) or when it is an http(s) URI whose host is
# a loopback address. A URI without a scheme or one that fails to parse is not native.
def native_redirect_uri?(url)
uri = URI.parse(url.to_s)
scheme = uri.scheme&.downcase
return false if scheme.nil?
return loopback_host?(uri.host) if ["http", "https"].include?(scheme)
true
rescue URI::InvalidURIError
false
end
def base_url(uri)
port_part = uri.port && uri.port != uri.default_port ? ":#{uri.port}" : ""
"#{uri.scheme}://#{uri.host}#{port_part}"

View file

@ -440,7 +440,7 @@ module MCP
end
response = begin
http_post_json(registration_endpoint, @provider.client_metadata)
http_post_json(registration_endpoint, registration_client_metadata)
rescue Faraday::Error => e
raise AuthorizationError,
"Dynamic client registration failed: #{e.class}: #{e.message}."
@ -466,6 +466,20 @@ module MCP
info
end
# Returns the client metadata to submit on Dynamic Client Registration.
# Per SEP-837, MCP clients MUST specify an appropriate OIDC `application_type`
# so the authorization server can apply the matching redirect URI policy.
# When the user did not set one explicitly, infer `"native"` vs `"web"` from
# the registered `redirect_uris`; an explicit value always wins.
# https://github.com/modelcontextprotocol/modelcontextprotocol/pull/837
def registration_client_metadata
metadata = @provider.client_metadata
return metadata if metadata[:application_type] || metadata["application_type"]
redirect_uris = metadata[:redirect_uris] || metadata["redirect_uris"]
metadata.merge("application_type" => Discovery.infer_application_type(redirect_uris))
end
# Reads `key` from a `client_information` hash that may use either string or
# symbol keys, so users can persist the result of `JSON.parse` *or* a hand-built
# `{ client_id:, client_secret: }` and have both work.

View file

@ -13,6 +13,9 @@ module MCP
# - `client_metadata` - Hash sent to the authorization server's Dynamic Client
# Registration endpoint. Must include at minimum `redirect_uris`,
# `grant_types`, `response_types`, and `token_endpoint_auth_method`.
# When `application_type` is omitted, the SDK infers `"native"` or `"web"`
# from `redirect_uris` per SEP-837 before registering; an explicit value
# always wins.
# - `redirect_uri` - String: the redirect URI used for the authorization
# request. Must be one of `redirect_uris` in `client_metadata`.
# - `redirect_handler` - Callable invoked with the fully-built authorization