Add application_type to the Dynamic Client Registration request (#1613)
This commit is contained in:
parent
28806b1b6e
commit
20c464c55b
2 changed files with 223 additions and 3 deletions
|
|
@ -110,6 +110,33 @@ public class AuthTests : OAuthTestBase
|
|||
|
||||
await using var client = await McpClient.CreateAsync(
|
||||
transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal("native", TestOAuthServer.LastApplicationType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DynamicClientRegistration_UsesExplicitApplicationType()
|
||||
{
|
||||
await using var app = await StartMcpServerAsync();
|
||||
|
||||
await using var transport = new HttpClientTransport(new()
|
||||
{
|
||||
Endpoint = new(McpServerUrl),
|
||||
OAuth = new ClientOAuthOptions()
|
||||
{
|
||||
RedirectUri = new Uri("http://localhost:1179/callback"),
|
||||
AuthorizationRedirectDelegate = HandleAuthorizationUrlAsync,
|
||||
DynamicClientRegistration = new()
|
||||
{
|
||||
ApplicationType = "web",
|
||||
},
|
||||
},
|
||||
}, HttpClient, LoggerFactory);
|
||||
|
||||
await using var client = await McpClient.CreateAsync(
|
||||
transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal("web", TestOAuthServer.LastApplicationType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -124,7 +151,11 @@ public class AuthTests : OAuthTestBase
|
|||
{
|
||||
RedirectUri = new Uri("http://localhost:1179/callback"),
|
||||
AuthorizationRedirectDelegate = HandleAuthorizationUrlAsync,
|
||||
ClientMetadataDocumentUri = new Uri(ClientMetadataDocumentUrl)
|
||||
ClientMetadataDocumentUri = new Uri(ClientMetadataDocumentUrl),
|
||||
DynamicClientRegistration = new()
|
||||
{
|
||||
ApplicationType = "web",
|
||||
},
|
||||
},
|
||||
}, HttpClient, LoggerFactory);
|
||||
|
||||
|
|
@ -258,6 +289,10 @@ public class AuthTests : OAuthTestBase
|
|||
RedirectUri = new Uri("http://localhost:1179/callback"),
|
||||
AuthorizationRedirectDelegate = HandleAuthorizationUrlAsync,
|
||||
ClientMetadataDocumentUri = new Uri("http://invalid-cimd.example.com"),
|
||||
DynamicClientRegistration = new()
|
||||
{
|
||||
ApplicationType = "web",
|
||||
},
|
||||
},
|
||||
}, HttpClient, LoggerFactory);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
using ModelContextProtocol.Authentication;
|
||||
using ModelContextProtocol.Client;
|
||||
|
||||
namespace ModelContextProtocol.AspNetCore.Tests.OAuth;
|
||||
|
||||
// SEP-837: the SDK doesn't surface or retry DCR failures itself, but a consumer built on the SDK
|
||||
// must be able to. These tests prove that surface: a rejected registration propagates with enough
|
||||
// context to build a meaningful error, and a consumer can retry with an adjusted redirect URI.
|
||||
public class DcrFailureTests : OAuthTestBase
|
||||
{
|
||||
public DcrFailureTests(ITestOutputHelper outputHelper)
|
||||
: base(outputHelper)
|
||||
{
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DcrRejection_PropagatesToConsumer_WithStatusBodyAndSentParameters()
|
||||
{
|
||||
await using var app = await StartMcpServerAsync();
|
||||
|
||||
// A custom-scheme redirect URI infers application_type "native"; the OIDC AS rejects it
|
||||
// with 400 invalid_redirect_uri because it only registers http/https redirect URIs.
|
||||
await using var transport = new HttpClientTransport(new()
|
||||
{
|
||||
Endpoint = new(McpServerUrl),
|
||||
OAuth = new ClientOAuthOptions()
|
||||
{
|
||||
RedirectUri = new Uri("myapp://callback"),
|
||||
AuthorizationRedirectDelegate = HandleAuthorizationUrlAsync,
|
||||
DynamicClientRegistration = new() { ClientName = "Test MCP Client" },
|
||||
},
|
||||
}, HttpClient, LoggerFactory);
|
||||
|
||||
var ex = await Assert.ThrowsAsync<McpException>(() => McpClient.CreateAsync(
|
||||
transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken));
|
||||
|
||||
// The consumer needs enough to produce a meaningful error: the HTTP status, the AS error
|
||||
// body (which echoes the redirect URI), and the application_type the SDK actually sent.
|
||||
Assert.Contains("BadRequest", ex.Message);
|
||||
Assert.Contains("invalid_redirect_uri", ex.Message);
|
||||
Assert.Contains("native", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConsumerCanRetryRegistration_WithAdjustedRedirectUri_AfterRejection()
|
||||
{
|
||||
await using var app = await StartMcpServerAsync();
|
||||
|
||||
// First attempt: a custom-scheme redirect (native) is rejected by the AS. ApplicationType
|
||||
// is held constant at "native" so only the redirect URI changes between the two attempts.
|
||||
await using var firstTransport = new HttpClientTransport(new()
|
||||
{
|
||||
Endpoint = new(McpServerUrl),
|
||||
OAuth = new ClientOAuthOptions()
|
||||
{
|
||||
RedirectUri = new Uri("myapp://callback"),
|
||||
AuthorizationRedirectDelegate = HandleAuthorizationUrlAsync,
|
||||
DynamicClientRegistration = new()
|
||||
{
|
||||
ClientName = "Test MCP Client",
|
||||
ApplicationType = "native",
|
||||
},
|
||||
},
|
||||
}, HttpClient, LoggerFactory);
|
||||
|
||||
await Assert.ThrowsAsync<McpException>(() => McpClient.CreateAsync(
|
||||
firstTransport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken));
|
||||
|
||||
// Second attempt: a new provider on the SAME HttpClient with an adjusted (loopback) redirect
|
||||
// URI that the AS accepts. The retry must succeed, proving the SEP-837 MAY-retry surface works
|
||||
// and that the rejected attempt left no client state behind.
|
||||
await using var secondTransport = new HttpClientTransport(new()
|
||||
{
|
||||
Endpoint = new(McpServerUrl),
|
||||
OAuth = new ClientOAuthOptions()
|
||||
{
|
||||
RedirectUri = new Uri("http://localhost:1179/callback"),
|
||||
AuthorizationRedirectDelegate = HandleAuthorizationUrlAsync,
|
||||
DynamicClientRegistration = new()
|
||||
{
|
||||
ClientName = "Test MCP Client",
|
||||
ApplicationType = "native",
|
||||
},
|
||||
},
|
||||
}, HttpClient, LoggerFactory);
|
||||
|
||||
await using var client = await McpClient.CreateAsync(
|
||||
secondTransport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal("native", TestOAuthServer.LastApplicationType);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue