Add application_type to the Dynamic Client Registration request (#1613)
This commit is contained in:
parent
28806b1b6e
commit
20c464c55b
3 changed files with 223 additions and 3 deletions
|
|
@ -34,10 +34,11 @@ internal sealed partial class ClientOAuthProvider : McpHttpClient
|
|||
private readonly AuthorizationRedirectDelegate _authorizationRedirectDelegate;
|
||||
private readonly Uri? _clientMetadataDocumentUri;
|
||||
|
||||
// _dcrClientName, _dcrClientUri, _dcrInitialAccessToken and _dcrResponseDelegate are used for dynamic client registration (RFC 7591)
|
||||
// _dcrClientName, _dcrClientUri, _dcrInitialAccessToken, _dcrConfiguredApplicationType and _dcrResponseDelegate are used for dynamic client registration (RFC 7591)
|
||||
private readonly string? _dcrClientName;
|
||||
private readonly Uri? _dcrClientUri;
|
||||
private readonly string? _dcrInitialAccessToken;
|
||||
private readonly string? _dcrConfiguredApplicationType;
|
||||
private readonly Func<DynamicClientRegistrationResponse, CancellationToken, Task>? _dcrResponseDelegate;
|
||||
|
||||
private readonly HttpClient _httpClient;
|
||||
|
|
@ -110,6 +111,7 @@ internal sealed partial class ClientOAuthProvider : McpHttpClient
|
|||
_dcrClientUri = options.DynamicClientRegistration?.ClientUri;
|
||||
_dcrInitialAccessToken = options.DynamicClientRegistration?.InitialAccessToken;
|
||||
_dcrResponseDelegate = options.DynamicClientRegistration?.ResponseDelegate;
|
||||
_dcrConfiguredApplicationType = options.DynamicClientRegistration?.ApplicationType;
|
||||
_tokenCache = options.TokenCache ?? new InMemoryTokenCache();
|
||||
}
|
||||
|
||||
|
|
@ -788,6 +790,7 @@ internal sealed partial class ClientOAuthProvider : McpHttpClient
|
|||
|
||||
LogPerformingDynamicClientRegistration(authServerMetadata.RegistrationEndpoint);
|
||||
|
||||
var dcrApplicationType = ResolveApplicationType(_dcrConfiguredApplicationType, _redirectUri);
|
||||
var registrationRequest = new DynamicClientRegistrationRequest
|
||||
{
|
||||
RedirectUris = [_redirectUri.ToString()],
|
||||
|
|
@ -797,6 +800,7 @@ internal sealed partial class ClientOAuthProvider : McpHttpClient
|
|||
ClientName = _dcrClientName,
|
||||
ClientUri = _dcrClientUri?.ToString(),
|
||||
Scope = ComputeEffectiveScope(protectedResourceMetadata, authServerMetadata),
|
||||
ApplicationType = dcrApplicationType,
|
||||
};
|
||||
|
||||
var requestBytes = JsonSerializer.SerializeToUtf8Bytes(registrationRequest, McpJsonUtilities.JsonContext.Default.DynamicClientRegistrationRequest);
|
||||
|
|
@ -818,7 +822,9 @@ internal sealed partial class ClientOAuthProvider : McpHttpClient
|
|||
if (!httpResponse.IsSuccessStatusCode)
|
||||
{
|
||||
var errorContent = await httpResponse.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
ThrowFailedToHandleUnauthorizedResponse($"Dynamic client registration failed with status {httpResponse.StatusCode}: {errorContent}");
|
||||
ThrowFailedToHandleUnauthorizedResponse(
|
||||
$"Dynamic client registration failed with status {httpResponse.StatusCode}: {errorContent} " +
|
||||
$"(application_type: '{dcrApplicationType}', redirect_uri: '{_redirectUri}').");
|
||||
}
|
||||
|
||||
using var responseStream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
|
@ -852,6 +858,18 @@ internal sealed partial class ClientOAuthProvider : McpHttpClient
|
|||
}
|
||||
}
|
||||
|
||||
private static string ResolveApplicationType(string? configuredApplicationType, Uri redirectUri)
|
||||
=> configuredApplicationType ?? InferApplicationType(redirectUri);
|
||||
|
||||
private static string InferApplicationType(Uri redirectUri)
|
||||
{
|
||||
if (redirectUri.Scheme is "http" or "https")
|
||||
{
|
||||
return redirectUri.IsLoopback ? "native" : "web";
|
||||
}
|
||||
return "native";
|
||||
}
|
||||
|
||||
private static string? GetResourceUri(ProtectedResourceMetadata protectedResourceMetadata)
|
||||
=> protectedResourceMetadata.Resource;
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,24 @@ public sealed class DynamicClientRegistrationOptions
|
|||
/// </remarks>
|
||||
public string? InitialAccessToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the OIDC <c>application_type</c> sent during dynamic client registration.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// When <see langword="null"/>, the SDK infers the value from the configured
|
||||
/// <see cref="ClientOAuthOptions.RedirectUri"/>: loopback hosts (<c>localhost</c>,
|
||||
/// <c>127.0.0.1</c>, <c>[::1]</c>) and custom-scheme URIs map to <c>"native"</c>; remote
|
||||
/// <c>http://</c> and <c>https://</c> URIs map to <c>"web"</c>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When set explicitly, the value is sent without modification. Use this to account for
|
||||
/// authorization-server-specific requirements or to retry a registration with an adjusted
|
||||
/// application type.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public string? ApplicationType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the delegate used for handling the dynamic client registration response.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -48,4 +48,10 @@ internal sealed class DynamicClientRegistrationRequest
|
|||
/// </summary>
|
||||
[JsonPropertyName("scope")]
|
||||
public string? Scope { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the OIDC application type ("native" or "web") for the client.
|
||||
/// </summary>
|
||||
[JsonPropertyName("application_type")]
|
||||
public string? ApplicationType { get; init; }
|
||||
}
|
||||
Loading…
Reference in a new issue