From 4f90f109fb5fb6a23aeabea1533d1e54b3ad982e Mon Sep 17 00:00:00 2001 From: King Star Date: Sat, 22 Aug 2026 04:33:53 +0800 Subject: [PATCH] test(oauth): extend metadata discovery probe timeout --- .../OAuth/AuthTests.cs | 116 +++++++++--------- .../OAuth/DcrFailureTests.cs | 6 +- .../OAuth/OAuthTestBase.cs | 20 +++ .../Program.cs | 3 + 4 files changed, 84 insertions(+), 61 deletions(-) diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/AuthTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/AuthTests.cs index 693c77943..ffed1a52a 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/AuthTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/AuthTests.cs @@ -46,7 +46,7 @@ public async Task CanAuthenticate() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -74,7 +74,7 @@ public async Task AuthorizationCallbackHandler_ReceivesConfiguredRedirectUri() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(callbackContext); @@ -116,7 +116,7 @@ public async Task AuthorizationRedirectDelegate_ReceivesConfiguredUrisAndSkipsRe }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(receivedAuthorizationUri); @@ -143,7 +143,7 @@ public async Task AuthorizationRedirectDelegate_DoesNotSkipMetadataIssuerValidat }, }, HttpClient, LoggerFactory); - var ex = await Assert.ThrowsAsync(() => McpClient.CreateAsync( + var ex = await Assert.ThrowsAsync(() => CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); Assert.Contains("does not match the expected issuer", ex.Message); @@ -188,7 +188,7 @@ public async Task CanAuthenticate_WhenAuthorizationResponseStateMatches() return HandleAuthorizationUrlAsync(context, cancellationToken); }); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(requestedState); @@ -203,7 +203,7 @@ public async Task CannotAuthenticate_WhenAuthorizationResponseStateIsMissing() await using var transport = CreateOAuthTransport( (_, _) => Task.FromResult(new() { Code = "unused-code" })); - var ex = await Assert.ThrowsAsync(() => McpClient.CreateAsync( + var ex = await Assert.ThrowsAsync(() => CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); Assert.Contains("did not include the required state parameter", ex.Message); @@ -218,7 +218,7 @@ public async Task CannotAuthenticate_WhenAuthorizationResponseStateMismatches() (_, _) => Task.FromResult( new() { Code = "unused-code", State = "unexpected-state" })); - var ex = await Assert.ThrowsAsync(() => McpClient.CreateAsync( + var ex = await Assert.ThrowsAsync(() => CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); Assert.Contains("state did not match", ex.Message); @@ -239,7 +239,7 @@ public async Task AuthorizationRequests_UseUniqueStateValues() return HandleAuthorizationUrlAsync(context, cancellationToken); }); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -257,7 +257,7 @@ public async Task CannotAuthenticate_WithoutOAuthConfiguration() Endpoint = new(McpServerUrl), }, HttpClient, LoggerFactory); - var httpEx = await Assert.ThrowsAsync(async () => await McpClient.CreateAsync( + var httpEx = await Assert.ThrowsAsync(async () => await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); Assert.Equal(HttpStatusCode.Unauthorized, httpEx.StatusCode); @@ -281,7 +281,7 @@ public async Task CannotAuthenticate_WithUnregisteredClient() }, HttpClient, LoggerFactory); // The EqualException is thrown by HandleAuthorizationUrlAsync when the /authorize request gets a 400 - var equalEx = await Assert.ThrowsAsync(async () => await McpClient.CreateAsync( + var equalEx = await Assert.ThrowsAsync(async () => await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); } @@ -305,7 +305,7 @@ public async Task CanAuthenticate_WithDynamicClientRegistration() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.Equal("native", TestOAuthServer.LastApplicationType); @@ -330,7 +330,7 @@ public async Task DynamicClientRegistration_UsesExplicitApplicationType() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.Equal("web", TestOAuthServer.LastApplicationType); @@ -356,7 +356,7 @@ public async Task CanAuthenticate_WithClientMetadataDocument() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -378,7 +378,7 @@ public async Task CannotAuthenticate_WhenMetadataOmitsPkceMethods() }, }, HttpClient, LoggerFactory); - await Assert.ThrowsAsync(() => McpClient.CreateAsync( + await Assert.ThrowsAsync(() => CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); // No discovery endpoint advertises PKCE, so metadata discovery is exhausted. The precise PKCE reason @@ -406,7 +406,7 @@ public async Task CannotAuthenticate_WhenMetadataLacksS256PkceMethod() }, }, HttpClient, LoggerFactory); - await Assert.ThrowsAsync(() => McpClient.CreateAsync( + await Assert.ThrowsAsync(() => CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); Assert.Contains( @@ -435,7 +435,7 @@ public async Task CanAuthenticate_WhenFirstMetadataEndpointOmitsPkce_ButAnotherA }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -466,7 +466,7 @@ public async Task UsesDynamicClientRegistration_WhenCimdNotSupported() }, HttpClient, LoggerFactory); // Should succeed via dynamic client registration. - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -493,7 +493,7 @@ public async Task DoesNotUseClientMetadataDocument_WhenClientIdIsSpecified() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -515,7 +515,7 @@ public async Task CannotAuthenticate_WithInvalidClientMetadataDocument(string ur }, }, HttpClient, LoggerFactory); - var ex = await Assert.ThrowsAsync(() => McpClient.CreateAsync( + var ex = await Assert.ThrowsAsync(() => CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); Assert.StartsWith("Failed to handle unauthorized response", ex.Message); @@ -579,7 +579,7 @@ public async Task CanAuthenticate_WithTokenRefresh() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken); @@ -614,7 +614,7 @@ public async Task CanAuthenticate_WithExtraParams() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(lastAuthorizationUri?.Query); @@ -644,7 +644,7 @@ public async Task CannotOverrideExistingParameters_WithExtraParams(string parame }, }, HttpClient, LoggerFactory); - await Assert.ThrowsAsync(() => McpClient.CreateAsync( + await Assert.ThrowsAsync(() => CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); } @@ -665,7 +665,7 @@ public async Task CanAuthenticate_WithoutResourceInWwwAuthenticateHeader() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -687,7 +687,7 @@ public async Task CanAuthenticate_WithoutResourceInWwwAuthenticateHeader_WithPat }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -720,7 +720,7 @@ public async Task AuthorizationFlow_UsesScopeFromProtectedResourceMetadata() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); var requestedScopeSet = new HashSet(requestedScope!.Split(' ')); @@ -773,7 +773,7 @@ public async Task AuthorizationFlow_UsesScopeFromChallengeHeader() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.Equal(challengeScopes, requestedScope); @@ -868,7 +868,7 @@ public async Task AuthorizationFlow_UsesScopeFromForbiddenHeader() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.Equal("mcp:tools", requestedScope); @@ -973,7 +973,7 @@ public async Task AuthorizationFlow_AccumulatesScopesAcrossMultipleStepUps() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); // Initial auth gets "mcp:tools" from protected resource metadata @@ -1092,7 +1092,7 @@ public async Task AuthorizationFlow_ConcurrentStepUps_ReuseSteppedUpToken_WhenCh }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); // Initial connect requests "mcp:tools" from protected resource metadata. @@ -1187,7 +1187,7 @@ public async Task AuthorizationFlow_StopsSteppingUpWhenChallengeAddsNoNewScope() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); // Initial auth gets "mcp:tools" from protected resource metadata. @@ -1282,7 +1282,7 @@ public async Task AuthorizationFlow_AllowsOneStepUpEvenWhenChallengeAddsNoNewSco }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); // Initial auth already requests "mcp:tools" from protected resource metadata. @@ -1325,7 +1325,7 @@ public async Task AuthorizationFails_WhenResourceMetadataPortDiffers() }, }, HttpClient, LoggerFactory); - await Assert.ThrowsAsync(() => McpClient.CreateAsync( + await Assert.ThrowsAsync(() => CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); } @@ -1364,7 +1364,7 @@ public async Task CannotAuthenticate_WhenProtectedResourceMetadataMissingResourc }, }, HttpClient, LoggerFactory); - var ex = await Assert.ThrowsAsync(() => McpClient.CreateAsync( + var ex = await Assert.ThrowsAsync(() => CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); Assert.Contains("Resource URI in metadata", ex.Message); @@ -1392,7 +1392,7 @@ public async Task CanAuthenticate_WithAuthorizationServerPathInsertionMetadata() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); var requests = TestOAuthServer.MetadataRequests.ToArray(); @@ -1425,7 +1425,7 @@ public async Task CanAuthenticate_WithAuthorizationServerPathFallbacks() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.Equal( @@ -1446,7 +1446,7 @@ public async Task CannotAuthenticate_WhenAuthorizationServerMetadataIssuerMismat await using var app = await StartMcpServerAsync(); await using var transport = CreateOAuthTransport(); - var ex = await Assert.ThrowsAsync(() => McpClient.CreateAsync( + var ex = await Assert.ThrowsAsync(() => CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); Assert.Contains("does not match the expected issuer", ex.Message); @@ -1461,7 +1461,7 @@ public async Task CannotAuthenticate_WhenAuthorizationServerMetadataOmitsIssuer( await using var app = await StartMcpServerAsync(); await using var transport = CreateOAuthTransport(); - var ex = await Assert.ThrowsAsync(() => McpClient.CreateAsync( + var ex = await Assert.ThrowsAsync(() => CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); Assert.Contains("did not provide the required issuer", ex.Message); @@ -1476,7 +1476,7 @@ public async Task CanAuthenticate_WhenAuthorizationResponseIssuerMatches() await using var app = await StartMcpServerAsync(); await using var transport = CreateOAuthTransport(); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -1495,7 +1495,7 @@ public async Task CannotAuthenticate_WhenAuthorizationResponseIssuerIsInvalid( await using var app = await StartMcpServerAsync(); await using var transport = CreateOAuthTransport(); - var ex = await Assert.ThrowsAsync(() => McpClient.CreateAsync( + var ex = await Assert.ThrowsAsync(() => CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); Assert.Contains(expectedMessage, ex.Message); @@ -1552,7 +1552,7 @@ public async Task CanAuthenticate_WithResourceMetadataPathFallbacks() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.Equal( @@ -1609,7 +1609,7 @@ public async Task CannotAuthenticate_WhenResourceMetadataResourceIsNonRootParent var ex = await Assert.ThrowsAsync(async () => { - await McpClient.CreateAsync( + await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); }); @@ -1654,7 +1654,7 @@ public async Task CanAuthenticate_WhenWwwAuthenticateResourceMetadataIsRootPath( }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -1697,7 +1697,7 @@ public async Task CannotAuthenticate_WhenResourceMetadataUriDoesNotMatch() }, HttpClient, LoggerFactory); // This should fail because the resource URI doesn't match - var ex = await Assert.ThrowsAsync(() => McpClient.CreateAsync( + var ex = await Assert.ThrowsAsync(() => CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); Assert.Contains("does not match", ex.Message); @@ -1744,7 +1744,7 @@ public async Task CannotAuthenticate_WhenResourceMetadataResourceIsDifferentPath // This should fail because the resource URI is a different path on the same host, // which is neither an exact match nor the authority-only base URL. - var ex = await Assert.ThrowsAsync(() => McpClient.CreateAsync( + var ex = await Assert.ThrowsAsync(() => CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); Assert.Contains("does not match", ex.Message); @@ -1791,7 +1791,7 @@ public async Task ResourceMetadata_DoesNotAddTrailingSlash() // This should succeed - the client should not add a trailing slash // If the client incorrectly added a trailing slash, ValidResources would reject it - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -1943,7 +1943,7 @@ public async Task ResourceMetadata_PreservesExplicitTrailingSlash() // This should succeed with the explicitly configured trailing slash // If the client incorrectly trimmed the slash, ValidResources would reject it - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -2058,7 +2058,7 @@ await context.Response.WriteAsync($$""" }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -2160,7 +2160,7 @@ public async Task CanAuthenticate_WithLegacyServerUsingDefaultEndpointFallback() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -2231,7 +2231,7 @@ await context.Response.WriteAsync($$""" }, }, HttpClient, LoggerFactory); - var ex = await Assert.ThrowsAsync(() => McpClient.CreateAsync( + var ex = await Assert.ThrowsAsync(() => CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); // The specific PKCE failure reason is surfaced rather than a generic discovery failure or a @@ -2264,7 +2264,7 @@ public async Task AuthorizationFlow_AppendsOfflineAccess_WhenServerAdvertisesIt( }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(requestedScope); @@ -2296,7 +2296,7 @@ public async Task AuthorizationFlow_DoesNotAppendOfflineAccess_WhenServerDoesNot }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(requestedScope); @@ -2335,7 +2335,7 @@ public async Task AuthorizationFlow_DoesNotDuplicateOfflineAccess_WhenAlreadyPre }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(requestedScope); @@ -2373,7 +2373,7 @@ public async Task AuthorizationFlow_ScopeSelector_CanFilterServerProposedScopes( }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.Equal("mcp:tools", requestedScope); @@ -2404,7 +2404,7 @@ public async Task AuthorizationFlow_ScopeSelector_CanAddCustomScope() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(requestedScope); @@ -2441,7 +2441,7 @@ public async Task AuthorizationFlow_ScopeSelector_ReceivesNull_WhenServerProvide }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.Null(capturedInput); @@ -2471,7 +2471,7 @@ public async Task AuthorizationFlow_ScopeSelector_ReturningNull_OmitsScopeParame }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.False(scopePresent); @@ -2501,7 +2501,7 @@ public async Task AuthorizationFlow_ScopeSelector_ReturningEmpty_OmitsScopeParam }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.False(scopePresent); @@ -2544,7 +2544,7 @@ public async Task DynamicClientRegistration_ScopeSelector_AppliesToDcrScope() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.Equal("mcp:tools", TestOAuthServer.LastRegistrationScope); diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/DcrFailureTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/DcrFailureTests.cs index 43a69de68..3a2f74384 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/DcrFailureTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/DcrFailureTests.cs @@ -31,7 +31,7 @@ public async Task DcrRejection_PropagatesToConsumer_WithStatusBodyAndSentParamet }, }, HttpClient, LoggerFactory); - var ex = await Assert.ThrowsAsync(() => McpClient.CreateAsync( + var ex = await Assert.ThrowsAsync(() => CreateMcpClientAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); // The consumer needs enough to produce a meaningful error: the HTTP status, the AS error @@ -63,7 +63,7 @@ public async Task ConsumerCanRetryRegistration_WithAdjustedRedirectUri_AfterReje }, }, HttpClient, LoggerFactory); - await Assert.ThrowsAsync(() => McpClient.CreateAsync( + await Assert.ThrowsAsync(() => CreateMcpClientAsync( firstTransport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); // Second attempt: a new provider on the SAME HttpClient with an adjusted (loopback) redirect @@ -84,7 +84,7 @@ await Assert.ThrowsAsync(() => McpClient.CreateAsync( }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await CreateMcpClientAsync( secondTransport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.Equal("native", TestOAuthServer.LastApplicationType); diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/OAuthTestBase.cs b/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/OAuthTestBase.cs index 80167b0c9..1fd5cbb8f 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/OAuthTestBase.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/OAuthTestBase.cs @@ -3,10 +3,13 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; using ModelContextProtocol.AspNetCore.Authentication; using ModelContextProtocol.AspNetCore.Tests.Utils; using ModelContextProtocol.Authentication; +using ModelContextProtocol.Client; +using ModelContextProtocol.Tests.Utils; using System.Net; namespace ModelContextProtocol.AspNetCore.Tests.OAuth; @@ -81,6 +84,23 @@ public async ValueTask DisposeAsync() } } + protected Task CreateMcpClientAsync( + IClientTransport clientTransport, + ILoggerFactory? loggerFactory = null, + CancellationToken cancellationToken = default) + { + return McpClient.CreateAsync( + clientTransport, + new McpClientOptions + { + // Keep OAuth metadata discovery inside the client initialization budget on slow CI. + // The default five-second probe can cancel an in-flight metadata request. + DiscoverProbeTimeout = TestConstants.DefaultTimeout, + }, + loggerFactory, + cancellationToken); + } + protected async Task StartMcpServerAsync(string path = "", string? authScheme = null, Action? configureMiddleware = null) { // Wait for the OAuth server to be ready before starting the MCP server. diff --git a/tests/ModelContextProtocol.ConformanceClient/Program.cs b/tests/ModelContextProtocol.ConformanceClient/Program.cs index 718c782f9..c23202da8 100644 --- a/tests/ModelContextProtocol.ConformanceClient/Program.cs +++ b/tests/ModelContextProtocol.ConformanceClient/Program.cs @@ -22,6 +22,9 @@ McpClientOptions options = new() { + // OAuth metadata discovery runs under the initial server/discover probe token. + // Keep slow CI handshakes from cancelling metadata requests after five seconds. + DiscoverProbeTimeout = TimeSpan.FromSeconds(60), ClientInfo = new() { Name = "ConformanceClient",