diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7504760a9..7f9411025 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -97,6 +97,12 @@ The test projects include shared infrastructure in `tests/Common/Utils/` that mo Use `TestConstants.DefaultTimeout` (60 seconds) rather than hardcoded values. CI machines are often slower than developer workstations, and short timeouts cause flaky failures. +For direct client connections to controlled test servers that respond to discovery, use `TestMcpClient.CreateAsync`. +It disables the separate discovery probe timeout while retaining the overall `TestConstants.DefaultTimeout` initialization deadline. +If configuring client options, start with `TestMcpClient.CreateOptions()` before applying test-specific settings; explicitly supplied options are otherwise preserved unchanged. +Do not use these defaults in discovery-timeout tests or with servers that silently ignore discovery, since those rely on the probe timeout for fallback. +For a mock that only implements the legacy initialize handshake, explicitly select its supported protocol version instead of waiting for discovery to time out. + ```csharp // Good using var cts = CancellationTokenSource.CreateLinkedTokenSource(TestContext.Current.CancellationToken); diff --git a/tests/Common/Utils/TestMcpClient.cs b/tests/Common/Utils/TestMcpClient.cs new file mode 100644 index 000000000..8847bade2 --- /dev/null +++ b/tests/Common/Utils/TestMcpClient.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Logging; +using ModelContextProtocol.Client; + +namespace ModelContextProtocol.Tests.Utils; + +/// +/// Creates clients for controlled test servers that respond to discovery, including with a fallback error. +/// Do not use these defaults with servers that silently ignore discovery or in discovery-timeout tests. +/// +internal static class TestMcpClient +{ + public static McpClientOptions CreateOptions() => new() + { + DiscoverProbeTimeout = Timeout.InfiniteTimeSpan, + InitializationTimeout = TestConstants.DefaultTimeout, + }; + + public static Task CreateAsync( + IClientTransport clientTransport, + McpClientOptions? clientOptions = null, + ILoggerFactory? loggerFactory = null, + CancellationToken cancellationToken = default) => + McpClient.CreateAsync(clientTransport, clientOptions ?? CreateOptions(), loggerFactory, cancellationToken); +} diff --git a/tests/ModelContextProtocol.AotCompatibility.TestApp/ModelContextProtocol.AotCompatibility.TestApp.csproj b/tests/ModelContextProtocol.AotCompatibility.TestApp/ModelContextProtocol.AotCompatibility.TestApp.csproj index a8ab66edf..626891314 100644 --- a/tests/ModelContextProtocol.AotCompatibility.TestApp/ModelContextProtocol.AotCompatibility.TestApp.csproj +++ b/tests/ModelContextProtocol.AotCompatibility.TestApp/ModelContextProtocol.AotCompatibility.TestApp.csproj @@ -12,6 +12,9 @@ + + + diff --git a/tests/ModelContextProtocol.AotCompatibility.TestApp/Program.cs b/tests/ModelContextProtocol.AotCompatibility.TestApp/Program.cs index 708cd9361..e71c6a6c9 100644 --- a/tests/ModelContextProtocol.AotCompatibility.TestApp/Program.cs +++ b/tests/ModelContextProtocol.AotCompatibility.TestApp/Program.cs @@ -3,6 +3,7 @@ using ModelContextProtocol.Extensions.Apps; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; +using ModelContextProtocol.Tests.Utils; using System.IO.Pipelines; Pipe clientToServerPipe = new(), serverToClientPipe = new(); @@ -18,7 +19,7 @@ _ = server.RunAsync(); // Connect a client using a stream-based transport over the same in-memory pipe. -await using McpClient client = await McpClient.CreateAsync( +await using McpClient client = await TestMcpClient.CreateAsync( new StreamClientTransport(clientToServerPipe.Writer.AsStream(), serverToClientPipe.Reader.AsStream())); // List all tools. diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/AuthorizeAttributeTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/AuthorizeAttributeTests.cs index 369bd7eb0..aa6e18924 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/AuthorizeAttributeTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/AuthorizeAttributeTests.cs @@ -25,12 +25,8 @@ private async Task ConnectAsync() Endpoint = new("http://localhost:5000"), }, HttpClient, LoggerFactory); - // This server supports discovery; slow startup must not trigger legacy fallback. - return await McpClient.CreateAsync(transport, new() - { - DiscoverProbeTimeout = Timeout.InfiniteTimeSpan, - InitializationTimeout = TestConstants.DefaultTimeout, - }, cancellationToken: TestContext.Current.CancellationToken, loggerFactory: LoggerFactory); + return await TestMcpClient.CreateAsync(transport, + cancellationToken: TestContext.Current.CancellationToken, loggerFactory: LoggerFactory); } [Fact] diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/HttpTaskIntegrationTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/HttpTaskIntegrationTests.cs index d64a55987..fa8915c51 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/HttpTaskIntegrationTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/HttpTaskIntegrationTests.cs @@ -6,6 +6,7 @@ using ModelContextProtocol.Extensions.Tasks; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; +using ModelContextProtocol.Tests.Utils; using Moq; using System.Security.Claims; @@ -30,7 +31,7 @@ public async Task WithTasks_CanCallToolOverHttp() new HttpClientTransportOptions { Endpoint = new("http://localhost:5000") }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); @@ -67,7 +68,7 @@ public async Task WithTasks_AfterOrdinaryFilter_RunsFilter() new HttpClientTransportOptions { Endpoint = new("http://localhost:5000") }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); @@ -120,7 +121,7 @@ [new Claim(ClaimTypes.NameIdentifier, "test-user")], new HttpClientTransportOptions { Endpoint = new("http://localhost:5000") }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); @@ -166,7 +167,7 @@ public async Task WithTasks_UnauthorizedTool_DoesNotCreateTask(bool registerTask new HttpClientTransportOptions { Endpoint = new("http://localhost:5000") }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); @@ -217,7 +218,7 @@ public async Task WithTasks_ReauthorizesToolChangedByOrdinaryFilter() new HttpClientTransportOptions { Endpoint = new("http://localhost:5000") }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/July2026ProtocolHttpFallbackTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/July2026ProtocolHttpFallbackTests.cs index 22cc36ad7..0a9a46ceb 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/July2026ProtocolHttpFallbackTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/July2026ProtocolHttpFallbackTests.cs @@ -221,7 +221,7 @@ await StartServerAsync(async context => }, HttpClient, LoggerFactory); // Default options prefer 2026-07-28 but allow automatic fallback to an initialize-handshake server. - await using var client = await McpClient.CreateAsync(transport, new McpClientOptions(), + await using var client = await TestMcpClient.CreateAsync(transport, loggerFactory: LoggerFactory, cancellationToken: ct); Assert.Equal(McpProtocolVersions.June2025ProtocolVersion, client.NegotiatedProtocolVersion); @@ -308,7 +308,7 @@ await StartServerAsync(async context => }, HttpClient, LoggerFactory); // Default options prefer 2026-07-28 but allow automatic fallback to an initialize-handshake server. - await using var client = await McpClient.CreateAsync(transport, new McpClientOptions(), + await using var client = await TestMcpClient.CreateAsync(transport, loggerFactory: LoggerFactory, cancellationToken: ct); Assert.Equal(McpProtocolVersions.November2025ProtocolVersion, client.NegotiatedProtocolVersion); @@ -354,10 +354,10 @@ await WriteJsonRpcErrorAsync(context, HttpStatusCode.BadRequest, var exception = await Assert.ThrowsAsync(async () => { - await using var client = await McpClient.CreateAsync(transport, new McpClientOptions - { - ProtocolVersion = McpProtocolVersions.July2026ProtocolVersion, - }, loggerFactory: LoggerFactory, cancellationToken: ct); + var clientOptions = TestMcpClient.CreateOptions(); + clientOptions.ProtocolVersion = McpProtocolVersions.July2026ProtocolVersion; + await using var client = await McpClient.CreateAsync(transport, clientOptions, + loggerFactory: LoggerFactory, cancellationToken: ct); }); Assert.Equal(McpErrorCode.HeaderMismatch, exception.ErrorCode); @@ -408,9 +408,8 @@ await WriteJsonRpcErrorAsync( var exception = await Assert.ThrowsAsync(async () => { - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, - new McpClientOptions(), loggerFactory: LoggerFactory, cancellationToken: ct); }); @@ -481,10 +480,10 @@ await StartServerAsync(async context => Endpoint = new("http://localhost:5000/mcp"), }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync(transport, new McpClientOptions - { - ProtocolVersion = McpProtocolVersions.July2026ProtocolVersion, - }, loggerFactory: LoggerFactory, cancellationToken: ct); + var clientOptions = TestMcpClient.CreateOptions(); + clientOptions.ProtocolVersion = McpProtocolVersions.July2026ProtocolVersion; + await using var client = await McpClient.CreateAsync(transport, clientOptions, + loggerFactory: LoggerFactory, cancellationToken: ct); await client.ListToolsAsync(cancellationToken: ct); @@ -541,10 +540,10 @@ await StartServerAsync(async context => OwnsSession = false, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync(transport, new McpClientOptions - { - ProtocolVersion = McpProtocolVersions.July2026ProtocolVersion, - }, loggerFactory: LoggerFactory, cancellationToken: ct); + var clientOptions = TestMcpClient.CreateOptions(); + clientOptions.ProtocolVersion = McpProtocolVersions.July2026ProtocolVersion; + await using var client = await McpClient.CreateAsync(transport, clientOptions, + loggerFactory: LoggerFactory, cancellationToken: ct); Assert.Equal("", discoverSessionId); } diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/July2026ProtocolHybridSessionModeTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/July2026ProtocolHybridSessionModeTests.cs index 01a4b26cf..c4b398f95 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/July2026ProtocolHybridSessionModeTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/July2026ProtocolHybridSessionModeTests.cs @@ -4,6 +4,7 @@ using ModelContextProtocol.Client; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; +using ModelContextProtocol.Tests.Utils; using System.Net; using System.Net.Http.Headers; using System.Text; @@ -109,7 +110,8 @@ private Task ConnectClientAsync(string? protocolVersion = null, Actio // A null ProtocolVersion prefers 2026-07-28 and probes with server/discover before considering a // fallback to the initialize handshake. Pinning an older version forces the initialize handshake. - var clientOptions = new McpClientOptions { ProtocolVersion = protocolVersion }; + var clientOptions = TestMcpClient.CreateOptions(); + clientOptions.ProtocolVersion = protocolVersion; configureClient?.Invoke(clientOptions); return McpClient.CreateAsync(transport, clientOptions, LoggerFactory, TestContext.Current.CancellationToken); } diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/July2026ProtocolStatefulFallbackTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/July2026ProtocolStatefulFallbackTests.cs index 142536b9d..8956eaf76 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/July2026ProtocolStatefulFallbackTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/July2026ProtocolStatefulFallbackTests.cs @@ -83,7 +83,7 @@ private async Task ConnectDefaultClientAsync(Action // Default options: ProtocolVersion is null, which now prefers the 2026-07-28 protocol revision and probes // with server/discover before falling back to an initialize handshake. - var clientOptions = new McpClientOptions(); + var clientOptions = TestMcpClient.CreateOptions(); configureClient?.Invoke(clientOptions); return await McpClient.CreateAsync(transport, clientOptions, LoggerFactory, TestContext.Current.CancellationToken); } diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs index d413bf528..7424f8ac0 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Time.Testing; using ModelContextProtocol.AspNetCore.Tests.Utils; using ModelContextProtocol.Client; using ModelContextProtocol.Protocol; @@ -37,11 +38,87 @@ protected async Task ConnectAsync( TransportMode = UseStreamableHttp ? HttpTransportMode.StreamableHttp : HttpTransportMode.Sse, }, HttpClient, LoggerFactory); - var clientOptions = new McpClientOptions(); + var clientOptions = TestMcpClient.CreateOptions(); configureClient?.Invoke(clientOptions); return await McpClient.CreateAsync(transport, clientOptions, LoggerFactory, TestContext.Current.CancellationToken); } + [Fact] + public async Task ConnectAsync_SlowDiscovery_PreservesModernProtocol() + { + Assert.SkipWhen(UseStreamableHttp && !Stateless, July2026StatefulStreamableHttpSkipReason); + + var discovery = new AsyncGate(); + var timeProvider = new FakeTimeProvider(); + await using var app = await StartServerWithGatedDiscoveryAsync(discovery); + + var connecting = ConnectAsync(configureClient: options => + { + options.ProtocolVersion = McpProtocolVersions.July2026ProtocolVersion; + options.TimeProvider = timeProvider; + }); + await discovery.WaitUntilEnteredAsync(connecting); + timeProvider.Advance(new McpClientOptions().DiscoverProbeTimeout * 2); + discovery.Release.SetResult(); + + await using var client = await connecting.WaitAsync(TestConstants.DefaultTimeout, TestContext.Current.CancellationToken); + Assert.Equal(McpProtocolVersions.July2026ProtocolVersion, client.NegotiatedProtocolVersion); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task ConnectAsync_Discovery_RespectsConfiguredDeadline(bool customProbeTimeout) + { + Assert.SkipWhen(UseStreamableHttp && !Stateless, July2026StatefulStreamableHttpSkipReason); + + var discovery = new AsyncGate(); + var timeProvider = new FakeTimeProvider(); + var probeTimeout = new McpClientOptions().DiscoverProbeTimeout; + await using var app = await StartServerWithGatedDiscoveryAsync(discovery); + + var connecting = ConnectAsync(configureClient: options => + { + options.ProtocolVersion = McpProtocolVersions.July2026ProtocolVersion; + options.TimeProvider = timeProvider; + if (customProbeTimeout) + { + options.DiscoverProbeTimeout = probeTimeout; + } + }); + await discovery.WaitUntilEnteredAsync(connecting); + timeProvider.Advance(customProbeTimeout ? probeTimeout : TestConstants.DefaultTimeout); + + if (customProbeTimeout) + { + await Assert.ThrowsAsync(() => connecting.WaitAsync(TestConstants.DefaultTimeout, TestContext.Current.CancellationToken)); + } + else + { + var exception = await Assert.ThrowsAsync(() => connecting.WaitAsync(TestConstants.DefaultTimeout, TestContext.Current.CancellationToken)); + Assert.Equal("Initialization timed out", exception.Message); + } + } + + private async Task StartServerWithGatedDiscoveryAsync(AsyncGate discovery) + { + Builder.Services.AddMcpServer() + .WithHttpTransport(ConfigureStateless) + .WithMessageFilters(filters => filters.AddIncomingFilter(next => async (context, cancellationToken) => + { + if (context.JsonRpcMessage is JsonRpcRequest { Method: RequestMethods.ServerDiscover }) + { + await discovery.WaitAsync(cancellationToken); + } + await next(context, cancellationToken); + })); + + var app = Builder.Build(); + app.MapMcp(); + await app.StartAsync(TestContext.Current.CancellationToken); + return app; + } + [Fact] public async Task MapMcp_ThrowsInvalidOperationException_IfWithHttpTransportIsNotCalled() { diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/AuthEventTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/AuthEventTests.cs index 1866dfe13..4aef55169 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/AuthEventTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/AuthEventTests.cs @@ -2,6 +2,7 @@ using ModelContextProtocol.AspNetCore.Authentication; using ModelContextProtocol.Authentication; using ModelContextProtocol.Client; +using ModelContextProtocol.Tests.Utils; using System.Net; using System.Net.Http.Json; @@ -55,7 +56,7 @@ public async Task CanAuthenticate_WithResourceMetadataFromEvent() LoggerFactory ); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken @@ -94,7 +95,7 @@ public async Task CanAuthenticate_WithDynamicClientRegistration_FromEvent() LoggerFactory ); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/AuthTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/AuthTests.cs index 476a0ccf3..2da187d6f 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/AuthTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/AuthTests.cs @@ -48,7 +48,7 @@ public async Task CanAuthenticate() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -76,7 +76,7 @@ public async Task AuthorizationCallbackHandler_ReceivesConfiguredRedirectUri() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(callbackContext); @@ -118,7 +118,7 @@ public async Task AuthorizationRedirectDelegate_ReceivesConfiguredUrisAndSkipsRe }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(receivedAuthorizationUri); @@ -145,7 +145,7 @@ public async Task AuthorizationRedirectDelegate_DoesNotSkipMetadataIssuerValidat }, }, HttpClient, LoggerFactory); - var ex = await Assert.ThrowsAsync(() => McpClient.CreateAsync( + var ex = await Assert.ThrowsAsync(() => TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); Assert.Contains("does not match the expected issuer", ex.Message); @@ -190,7 +190,7 @@ public async Task CanAuthenticate_WhenAuthorizationResponseStateMatches() return HandleAuthorizationUrlAsync(context, cancellationToken); }); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(requestedState); @@ -205,7 +205,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(() => TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); Assert.Contains("did not include the required state parameter", ex.Message); @@ -220,7 +220,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(() => TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); Assert.Contains("state did not match", ex.Message); @@ -241,7 +241,7 @@ public async Task AuthorizationRequests_UseUniqueStateValues() return HandleAuthorizationUrlAsync(context, cancellationToken); }); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -259,7 +259,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 TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); Assert.Equal(HttpStatusCode.Unauthorized, httpEx.StatusCode); @@ -283,7 +283,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 TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); } @@ -307,7 +307,7 @@ public async Task CanAuthenticate_WithDynamicClientRegistration() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.Equal("native", TestOAuthServer.LastApplicationType); @@ -332,7 +332,7 @@ public async Task DynamicClientRegistration_UsesExplicitApplicationType() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.Equal("web", TestOAuthServer.LastApplicationType); @@ -358,7 +358,7 @@ public async Task CanAuthenticate_WithClientMetadataDocument() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -380,7 +380,7 @@ public async Task CannotAuthenticate_WhenMetadataOmitsPkceMethods() }, }, HttpClient, LoggerFactory); - await Assert.ThrowsAsync(() => McpClient.CreateAsync( + await Assert.ThrowsAsync(() => TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); // No discovery endpoint advertises PKCE, so metadata discovery is exhausted. The precise PKCE reason @@ -408,7 +408,7 @@ public async Task CannotAuthenticate_WhenMetadataLacksS256PkceMethod() }, }, HttpClient, LoggerFactory); - await Assert.ThrowsAsync(() => McpClient.CreateAsync( + await Assert.ThrowsAsync(() => TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); Assert.Contains( @@ -437,7 +437,7 @@ public async Task CanAuthenticate_WhenFirstMetadataEndpointOmitsPkce_ButAnotherA }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -468,7 +468,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 TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -495,7 +495,7 @@ public async Task DoesNotUseClientMetadataDocument_WhenClientIdIsSpecified() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -517,7 +517,7 @@ public async Task CannotAuthenticate_WithInvalidClientMetadataDocument(string ur }, }, HttpClient, LoggerFactory); - var ex = await Assert.ThrowsAsync(() => McpClient.CreateAsync( + var ex = await Assert.ThrowsAsync(() => TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); Assert.StartsWith("Failed to handle unauthorized response", ex.Message); @@ -581,7 +581,7 @@ public async Task CanAuthenticate_WithTokenRefresh() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken); @@ -616,7 +616,7 @@ public async Task CanAuthenticate_WithExtraParams() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(lastAuthorizationUri?.Query); @@ -646,7 +646,7 @@ public async Task CannotOverrideExistingParameters_WithExtraParams(string parame }, }, HttpClient, LoggerFactory); - await Assert.ThrowsAsync(() => McpClient.CreateAsync( + await Assert.ThrowsAsync(() => TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); } @@ -667,7 +667,7 @@ public async Task CanAuthenticate_WithoutResourceInWwwAuthenticateHeader() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -689,7 +689,7 @@ public async Task CanAuthenticate_WithoutResourceInWwwAuthenticateHeader_WithPat }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -722,7 +722,7 @@ public async Task AuthorizationFlow_UsesScopeFromProtectedResourceMetadata() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); var requestedScopeSet = new HashSet(requestedScope!.Split(' ')); @@ -775,7 +775,7 @@ public async Task AuthorizationFlow_UsesScopeFromChallengeHeader() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.Equal(challengeScopes, requestedScope); @@ -870,7 +870,7 @@ public async Task AuthorizationFlow_UsesScopeFromForbiddenHeader() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.Equal("mcp:tools", requestedScope); @@ -975,7 +975,7 @@ public async Task AuthorizationFlow_AccumulatesScopesAcrossMultipleStepUps() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); // Initial auth gets "mcp:tools" from protected resource metadata @@ -1094,7 +1094,7 @@ public async Task AuthorizationFlow_ConcurrentStepUps_ReuseSteppedUpToken_WhenCh }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); // Initial connect requests "mcp:tools" from protected resource metadata. @@ -1189,7 +1189,7 @@ public async Task AuthorizationFlow_StopsSteppingUpWhenChallengeAddsNoNewScope() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); // Initial auth gets "mcp:tools" from protected resource metadata. @@ -1284,7 +1284,7 @@ public async Task AuthorizationFlow_AllowsOneStepUpEvenWhenChallengeAddsNoNewSco }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); // Initial auth already requests "mcp:tools" from protected resource metadata. @@ -1327,7 +1327,7 @@ public async Task AuthorizationFails_WhenResourceMetadataPortDiffers() }, }, HttpClient, LoggerFactory); - await Assert.ThrowsAsync(() => McpClient.CreateAsync( + await Assert.ThrowsAsync(() => TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); } @@ -1366,7 +1366,7 @@ public async Task CannotAuthenticate_WhenProtectedResourceMetadataMissingResourc }, }, HttpClient, LoggerFactory); - var ex = await Assert.ThrowsAsync(() => McpClient.CreateAsync( + var ex = await Assert.ThrowsAsync(() => TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); Assert.Contains("Resource URI in metadata", ex.Message); @@ -1394,7 +1394,7 @@ public async Task CanAuthenticate_WithAuthorizationServerPathInsertionMetadata() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); var requests = TestOAuthServer.MetadataRequests.ToArray(); @@ -1427,7 +1427,7 @@ public async Task CanAuthenticate_WithAuthorizationServerPathFallbacks() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.Equal( @@ -1448,7 +1448,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(() => TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); Assert.Contains("does not match the expected issuer", ex.Message); @@ -1463,7 +1463,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(() => TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); Assert.Contains("did not provide the required issuer", ex.Message); @@ -1478,7 +1478,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 TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -1497,7 +1497,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(() => TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); Assert.Contains(expectedMessage, ex.Message); @@ -1623,7 +1623,7 @@ public async Task CannotAuthenticate_WhenResourceMetadataResourceIsNonRootParent var ex = await Assert.ThrowsAsync(async () => { - await McpClient.CreateAsync( + await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); }); @@ -1668,7 +1668,7 @@ public async Task CanAuthenticate_WhenWwwAuthenticateResourceMetadataIsRootPath( }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -1711,7 +1711,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(() => TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); Assert.Contains("does not match", ex.Message); @@ -1758,7 +1758,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(() => TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); Assert.Contains("does not match", ex.Message); @@ -1805,7 +1805,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 TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -1957,7 +1957,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 TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -2072,7 +2072,7 @@ await context.Response.WriteAsync($$""" }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -2174,7 +2174,7 @@ public async Task CanAuthenticate_WithLegacyServerUsingDefaultEndpointFallback() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); } @@ -2245,7 +2245,7 @@ await context.Response.WriteAsync($$""" }, }, HttpClient, LoggerFactory); - var ex = await Assert.ThrowsAsync(() => McpClient.CreateAsync( + var ex = await Assert.ThrowsAsync(() => TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); // The specific PKCE failure reason is surfaced rather than a generic discovery failure or a @@ -2278,7 +2278,7 @@ public async Task AuthorizationFlow_AppendsOfflineAccess_WhenServerAdvertisesIt( }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(requestedScope); @@ -2310,7 +2310,7 @@ public async Task AuthorizationFlow_DoesNotAppendOfflineAccess_WhenServerDoesNot }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(requestedScope); @@ -2349,7 +2349,7 @@ public async Task AuthorizationFlow_DoesNotDuplicateOfflineAccess_WhenAlreadyPre }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(requestedScope); @@ -2387,7 +2387,7 @@ public async Task AuthorizationFlow_ScopeSelector_CanFilterServerProposedScopes( }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.Equal("mcp:tools", requestedScope); @@ -2418,7 +2418,7 @@ public async Task AuthorizationFlow_ScopeSelector_CanAddCustomScope() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(requestedScope); @@ -2455,7 +2455,7 @@ public async Task AuthorizationFlow_ScopeSelector_ReceivesNull_WhenServerProvide }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.Null(capturedInput); @@ -2485,7 +2485,7 @@ public async Task AuthorizationFlow_ScopeSelector_ReturningNull_OmitsScopeParame }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.False(scopePresent); @@ -2515,7 +2515,7 @@ public async Task AuthorizationFlow_ScopeSelector_ReturningEmpty_OmitsScopeParam }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.False(scopePresent); @@ -2558,7 +2558,7 @@ public async Task DynamicClientRegistration_ScopeSelector_AppliesToDcrScope() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( 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..375deb6f8 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/DcrFailureTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/DcrFailureTests.cs @@ -1,5 +1,6 @@ using ModelContextProtocol.Authentication; using ModelContextProtocol.Client; +using ModelContextProtocol.Tests.Utils; namespace ModelContextProtocol.AspNetCore.Tests.OAuth; @@ -31,7 +32,7 @@ public async Task DcrRejection_PropagatesToConsumer_WithStatusBodyAndSentParamet }, }, HttpClient, LoggerFactory); - var ex = await Assert.ThrowsAsync(() => McpClient.CreateAsync( + var ex = await Assert.ThrowsAsync(() => TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); // The consumer needs enough to produce a meaningful error: the HTTP status, the AS error @@ -63,7 +64,7 @@ public async Task ConsumerCanRetryRegistration_WithAdjustedRedirectUri_AfterReje }, }, HttpClient, LoggerFactory); - await Assert.ThrowsAsync(() => McpClient.CreateAsync( + await Assert.ThrowsAsync(() => TestMcpClient.CreateAsync( firstTransport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); // Second attempt: a new provider on the SAME HttpClient with an adjusted (loopback) redirect @@ -84,7 +85,7 @@ await Assert.ThrowsAsync(() => McpClient.CreateAsync( }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( secondTransport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.Equal("native", TestOAuthServer.LastApplicationType); diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/DefaultAuthorizationUrlHandlerTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/DefaultAuthorizationUrlHandlerTests.cs index 825ebe2fb..85ec3289a 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/DefaultAuthorizationUrlHandlerTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/DefaultAuthorizationUrlHandlerTests.cs @@ -1,5 +1,6 @@ using ModelContextProtocol.Authentication; using ModelContextProtocol.Client; +using ModelContextProtocol.Tests.Utils; namespace ModelContextProtocol.AspNetCore.Tests.OAuth; @@ -31,7 +32,7 @@ public async Task RejectsAuthorizationCodeWithoutAbsoluteRedirectUrl() try { - var ex = await Assert.ThrowsAsync(() => McpClient.CreateAsync( + var ex = await Assert.ThrowsAsync(() => TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/IdentityAssertionGrantIntegrationTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/IdentityAssertionGrantIntegrationTests.cs index 6972fa4b4..9051f1686 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/IdentityAssertionGrantIntegrationTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/IdentityAssertionGrantIntegrationTests.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using ModelContextProtocol.Authentication; using ModelContextProtocol.Client; +using ModelContextProtocol.Tests.Utils; using System.Net.Http.Headers; namespace ModelContextProtocol.AspNetCore.Tests.OAuth; @@ -76,7 +77,7 @@ public async Task CanAuthenticate_WithIdentityAssertionGrantProvider() mcpHttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/TokenCacheTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/TokenCacheTests.cs index 5b6f054ba..2fa865981 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/TokenCacheTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/TokenCacheTests.cs @@ -1,6 +1,7 @@ using ModelContextProtocol.Authentication; using ModelContextProtocol.Client; using ModelContextProtocol.Protocol; +using ModelContextProtocol.Tests.Utils; namespace ModelContextProtocol.AspNetCore.Tests.OAuth; @@ -36,7 +37,7 @@ public async Task GetTokenAsync_CachedAccessTokenIsUsedForOutgoingRequests() }, }, HttpClient, LoggerFactory); - await using (var setupClient = await McpClient.CreateAsync(setupTransport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)) + await using (var setupClient = await TestMcpClient.CreateAsync(setupTransport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)) { // Just connecting should trigger auth and storage. } @@ -63,7 +64,7 @@ public async Task GetTokenAsync_CachedAccessTokenIsUsedForOutgoingRequests() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync(transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); + await using var client = await TestMcpClient.CreateAsync(transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.False(authDelegateCalledAgain, "AuthorizationCallbackHandler should not be called when token is valid"); } @@ -88,7 +89,7 @@ public async Task StoreTokenAsync_NewlyAcquiredAccessTokenIsCached() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync(transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); + await using var client = await TestMcpClient.CreateAsync(transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(tokenCache.LastStoredToken); Assert.False(string.IsNullOrEmpty(tokenCache.LastStoredToken.AccessToken)); @@ -119,7 +120,7 @@ public async Task GetTokenAsync_InvalidCachedTokenTriggersAuthDelegate() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync(transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); + await using var client = await TestMcpClient.CreateAsync(transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.True(authDelegateCalled, "AuthorizationCallbackHandler should be called when cached token is invalid"); Assert.NotNull(tokenCache.LastStoredToken); @@ -151,7 +152,7 @@ public async Task GetTokenAsync_InvalidAccessTokenTriggersRefresh() }, }, HttpClient, LoggerFactory); - await using (var setupClient = await McpClient.CreateAsync(setupTransport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)) + await using (var setupClient = await TestMcpClient.CreateAsync(setupTransport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)) { // Just connecting should trigger auth and storage. } @@ -181,7 +182,7 @@ public async Task GetTokenAsync_InvalidAccessTokenTriggersRefresh() }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync(transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); + await using var client = await TestMcpClient.CreateAsync(transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.False(authDelegateCalledAgain, "AuthorizationCallbackHandler should not be called when refresh token is valid"); Assert.True(TestOAuthServer.HasRefreshedToken, "Token should have been refreshed"); @@ -207,7 +208,7 @@ public async Task GetTokenAsync_ExplicitClientDoesNotRefreshTokenFromDifferentAu }, }, HttpClient, LoggerFactory); - await using (var setupClient = await McpClient.CreateAsync( + await using (var setupClient = await TestMcpClient.CreateAsync( setupTransport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)) @@ -236,7 +237,7 @@ public async Task GetTokenAsync_ExplicitClientDoesNotRefreshTokenFromDifferentAu }, }, HttpClient, LoggerFactory); - var exception = await Assert.ThrowsAsync(() => McpClient.CreateAsync( + var exception = await Assert.ThrowsAsync(() => TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)); @@ -266,7 +267,7 @@ public async Task GetTokenAsync_ExplicitClientDoesNotRefreshTokenIssuedToDiffere }, }, HttpClient, LoggerFactory); - await using (var setupClient = await McpClient.CreateAsync( + await using (var setupClient = await TestMcpClient.CreateAsync( setupTransport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)) @@ -295,7 +296,7 @@ public async Task GetTokenAsync_ExplicitClientDoesNotRefreshTokenIssuedToDiffere }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); @@ -325,7 +326,7 @@ public async Task GetTokenAsync_ExplicitClientWithLegacyUnboundCache_Reauthorize }, }, HttpClient, LoggerFactory); - await using (var setupClient = await McpClient.CreateAsync( + await using (var setupClient = await TestMcpClient.CreateAsync( setupTransport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)) @@ -354,7 +355,7 @@ public async Task GetTokenAsync_ExplicitClientWithLegacyUnboundCache_Reauthorize }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); @@ -393,7 +394,7 @@ public async Task GetTokenAsync_ColdStartWithDynamicRegistration_RefreshesUsingP }, }, HttpClient, LoggerFactory); - await using (var setupClient = await McpClient.CreateAsync(setupTransport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)) + await using (var setupClient = await TestMcpClient.CreateAsync(setupTransport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)) { // Just connecting should trigger dynamic registration, authorization, and storage. } @@ -432,7 +433,7 @@ public async Task GetTokenAsync_ColdStartWithDynamicRegistration_RefreshesUsingP }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync(transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); + await using var client = await TestMcpClient.CreateAsync(transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.False(authDelegateCalledAgain, "Authorization callback should not be called when the persisted refresh token can be used"); Assert.True(TestOAuthServer.HasRefreshedToken, "Token should have been refreshed using the persisted client credentials"); @@ -457,7 +458,7 @@ public async Task GetTokenAsync_ColdStartWithCredentialsFromDifferentAuthorizati }, }, HttpClient, LoggerFactory); - await using (var setupClient = await McpClient.CreateAsync( + await using (var setupClient = await TestMcpClient.CreateAsync( setupTransport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)) @@ -485,7 +486,7 @@ public async Task GetTokenAsync_ColdStartWithCredentialsFromDifferentAuthorizati }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); @@ -513,7 +514,7 @@ public async Task GetTokenAsync_ColdStartWithLegacyUnboundCredentials_Reregister }, }, HttpClient, LoggerFactory); - await using (var setupClient = await McpClient.CreateAsync( + await using (var setupClient = await TestMcpClient.CreateAsync( setupTransport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)) @@ -542,7 +543,7 @@ public async Task GetTokenAsync_ColdStartWithLegacyUnboundCredentials_Reregister }, }, HttpClient, LoggerFactory); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); @@ -580,7 +581,7 @@ public async Task GetTokenAsync_ColdStartWithoutPersistedClientId_FallsBackToRea }, }, HttpClient, LoggerFactory); - await using (var setupClient = await McpClient.CreateAsync(setupTransport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)) + await using (var setupClient = await TestMcpClient.CreateAsync(setupTransport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken)) { } @@ -618,7 +619,7 @@ public async Task GetTokenAsync_ColdStartWithoutPersistedClientId_FallsBackToRea // Should not throw "Client ID is not available"; instead re-authorizes via dynamic // registration and the authorization-code flow. - await using var client = await McpClient.CreateAsync(transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); + await using var client = await TestMcpClient.CreateAsync(transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); Assert.True(authDelegateCalledAgain, "Authorization callback should be called to re-authorize when no client ID is available to refresh"); Assert.False(TestOAuthServer.HasRefreshedToken, "A refresh should not be attempted without a client ID"); diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/SseServerIntegrationTestFixture.cs b/tests/ModelContextProtocol.AspNetCore.Tests/SseServerIntegrationTestFixture.cs index c382c4385..7ebec277c 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/SseServerIntegrationTestFixture.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/SseServerIntegrationTestFixture.cs @@ -46,7 +46,7 @@ public SseServerIntegrationTestFixture() public Task ConnectMcpClientAsync(McpClientOptions? options, ILoggerFactory loggerFactory) { - return McpClient.CreateAsync( + return TestMcpClient.CreateAsync( new HttpClientTransport(DefaultTransportOptions, HttpClient, loggerFactory), options, loggerFactory, diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/StatelessServerTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/StatelessServerTests.cs index a95d989c5..c310e5475 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/StatelessServerTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/StatelessServerTests.cs @@ -65,7 +65,7 @@ private async Task StartAsync() } private Task ConnectMcpClientAsync(McpClientOptions? clientOptions = null) - => McpClient.CreateAsync( + => TestMcpClient.CreateAsync( new HttpClientTransport(DefaultTransportOptions, HttpClient, LoggerFactory), clientOptions, LoggerFactory, TestContext.Current.CancellationToken); @@ -107,7 +107,7 @@ public async Task SamplingRequest_Fails_WithInvalidOperationException() { await StartAsync(); - var mcpClientOptions = new McpClientOptions(); + var mcpClientOptions = TestMcpClient.CreateOptions(); mcpClientOptions.Handlers.SamplingHandler = (_, _, _) => { throw new UnreachableException(); @@ -125,7 +125,7 @@ public async Task RootsRequest_Fails_WithInvalidOperationException() { await StartAsync(); - var mcpClientOptions = new McpClientOptions(); + var mcpClientOptions = TestMcpClient.CreateOptions(); mcpClientOptions.Handlers.RootsHandler = (_, _) => { throw new UnreachableException(); @@ -143,7 +143,7 @@ public async Task ElicitRequest_Fails_WithInvalidOperationException() { await StartAsync(); - var mcpClientOptions = new McpClientOptions(); + var mcpClientOptions = TestMcpClient.CreateOptions(); mcpClientOptions.Handlers.ElicitationHandler = (_, _) => { throw new UnreachableException(); @@ -160,7 +160,7 @@ public async Task ElicitRequest_Fails_WithInvalidOperationException() public async Task ClientCapabilities_AreAvailableFromInjectedServer() { await StartAsync(); - var clientOptions = new McpClientOptions(); + var clientOptions = TestMcpClient.CreateOptions(); clientOptions.Handlers.SamplingHandler = (_, _, _) => throw new UnreachableException(); clientOptions.Handlers.RootsHandler = (_, _) => throw new UnreachableException(); clientOptions.Handlers.ElicitationHandler = (_, _) => throw new UnreachableException(); @@ -179,7 +179,7 @@ public async Task ClientCapabilities_AreAvailableFromInjectedServer() public async Task InterceptedServerToClientRequests_Succeed_InStatelessMode() { await StartAsync(); - var clientOptions = new McpClientOptions(); + var clientOptions = TestMcpClient.CreateOptions(); clientOptions.Handlers.SamplingHandler = (_, _, _) => throw new UnreachableException(); clientOptions.Handlers.ElicitationHandler = (_, _) => throw new UnreachableException(); await using var client = await ConnectMcpClientAsync(clientOptions); diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/StreamableHttpClientConformanceTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/StreamableHttpClientConformanceTests.cs index 649286b09..9bfe39c71 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/StreamableHttpClientConformanceTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/StreamableHttpClientConformanceTests.cs @@ -620,9 +620,11 @@ public async Task TasksClient_SendsRoutingNameHeader_EndToEnd() TransportMode = HttpTransportMode.StreamableHttp, }, HttpClient, LoggerFactory); + var clientOptions = TestMcpClient.CreateOptions(); + clientOptions.ProtocolVersion = McpProtocolVersions.July2026ProtocolVersion; await using var client = await McpClient.CreateAsync( transport, - new McpClientOptions { ProtocolVersion = "2026-07-28" }, + clientOptions, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); diff --git a/tests/ModelContextProtocol.Tests/Client/MrtrIntegrationTests.cs b/tests/ModelContextProtocol.Tests/Client/MrtrIntegrationTests.cs index 86a107fd9..a696978f6 100644 --- a/tests/ModelContextProtocol.Tests/Client/MrtrIntegrationTests.cs +++ b/tests/ModelContextProtocol.Tests/Client/MrtrIntegrationTests.cs @@ -155,7 +155,8 @@ public async Task LegacyRequestOnMrtrSession_LogsWarning() var clientToServer = new Pipe(); var serverToClient = new Pipe(); - var clientOptions = new McpClientOptions { ProtocolVersion = "2026-07-28" }; + var clientOptions = TestMcpClient.CreateOptions(); + clientOptions.ProtocolVersion = "2026-07-28"; clientOptions.Handlers.ElicitationHandler = (request, ct) => new ValueTask(new ElicitResult { Action = "accept" }); clientOptions.Handlers.SamplingHandler = (request, progress, ct) => @@ -411,7 +412,8 @@ public async Task IncompleteResultRetry_OmittingRequestState_StripsStaleStateFro // Pin to the 2026-07-28 protocol so the client performs the server/discover handshake and // treats InputRequiredResult as an MRTR round-trip. - var clientOptions = new McpClientOptions { ProtocolVersion = "2026-07-28" }; + var clientOptions = TestMcpClient.CreateOptions(); + clientOptions.ProtocolVersion = "2026-07-28"; clientOptions.Handlers.ElicitationHandler = (_, _) => new ValueTask(new ElicitResult { diff --git a/tests/ModelContextProtocol.Tests/ClientIntegrationTestFixture.cs b/tests/ModelContextProtocol.Tests/ClientIntegrationTestFixture.cs index 87c4ca397..7d40fa6b0 100644 --- a/tests/ModelContextProtocol.Tests/ClientIntegrationTestFixture.cs +++ b/tests/ModelContextProtocol.Tests/ClientIntegrationTestFixture.cs @@ -50,12 +50,7 @@ public Task CreateClientAsync(string clientId, McpClientOptions? clie { if (clientId == "test_server" && clientOptions is null) { - // This server supports discovery; slow startup must not trigger legacy fallback. - clientOptions = new() - { - DiscoverProbeTimeout = Timeout.InfiniteTimeSpan, - InitializationTimeout = TestConstants.DefaultTimeout, - }; + clientOptions = TestMcpClient.CreateOptions(); } return McpClient.CreateAsync(new StdioClientTransport(clientId switch diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs index a05084a86..8d5c53568 100644 --- a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs +++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs @@ -5,6 +5,7 @@ using ModelContextProtocol.Client; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; +using ModelContextProtocol.Tests.Utils; using Moq; using System.Collections; using System.Collections.Concurrent; @@ -156,7 +157,7 @@ public async Task Can_Create_Multiple_Servers_From_Options_And_List_Registered_T await using var server = McpServer.Create(transport, options, loggerFactory, ServiceProvider); var serverRunTask = server.RunAsync(TestContext.Current.CancellationToken); - await using (var client = await McpClient.CreateAsync( + await using (var client = await TestMcpClient.CreateAsync( new StreamClientTransport( serverInput: stdinPipe.Writer.AsStream(), serverOutput: stdoutPipe.Reader.AsStream(), diff --git a/tests/ModelContextProtocol.Tests/DiagnosticTests.cs b/tests/ModelContextProtocol.Tests/DiagnosticTests.cs index db6c2c2c4..3f5bda02b 100644 --- a/tests/ModelContextProtocol.Tests/DiagnosticTests.cs +++ b/tests/ModelContextProtocol.Tests/DiagnosticTests.cs @@ -1,6 +1,7 @@ using ModelContextProtocol.Client; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; +using ModelContextProtocol.Tests.Utils; using OpenTelemetry.Trace; using System.Diagnostics; using System.IO.Pipelines; @@ -245,7 +246,7 @@ private static async Task RunConnected(Func action, { serverTask = server.RunAsync(TestContext.Current.CancellationToken); - await using (McpClient client = await McpClient.CreateAsync( + await using (McpClient client = await TestMcpClient.CreateAsync( clientTransport, cancellationToken: TestContext.Current.CancellationToken)) { diff --git a/tests/ModelContextProtocol.Tests/Protocol/CacheableResultWarningTests.cs b/tests/ModelContextProtocol.Tests/Protocol/CacheableResultWarningTests.cs index 20e55a782..891d884c7 100644 --- a/tests/ModelContextProtocol.Tests/Protocol/CacheableResultWarningTests.cs +++ b/tests/ModelContextProtocol.Tests/Protocol/CacheableResultWarningTests.cs @@ -117,13 +117,15 @@ public async Task AutoPaginatingOverload_MultiplePages_WarnsOnlyOncePerMethod() var clientToServer = new Pipe(); var serverToClient = new Pipe(); + var clientOptions = TestMcpClient.CreateOptions(); + clientOptions.ProtocolVersion = July2026ProtocolVersion; var clientTask = McpClient.CreateAsync( new StreamClientTransport( clientToServer.Writer.AsStream(), serverToClient.Reader.AsStream(), LoggerFactory), - new McpClientOptions { ProtocolVersion = July2026ProtocolVersion }, + clientOptions, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); @@ -219,12 +221,14 @@ private async Task RunScenarioAsync( // Pin the protocol version so the client deterministically takes the per-request metadata // (server/discover) lifecycle for 2026-07-28 and the initialize lifecycle for older versions. + var clientOptions = TestMcpClient.CreateOptions(); + clientOptions.ProtocolVersion = serverProtocolVersion; var clientTask = McpClient.CreateAsync( new StreamClientTransport( clientToServer.Writer.AsStream(), serverToClient.Reader.AsStream(), LoggerFactory), - new McpClientOptions { ProtocolVersion = serverProtocolVersion }, + clientOptions, loggerFactory: LoggerFactory, cancellationToken: cancellationToken); diff --git a/tests/ModelContextProtocol.Tests/Protocol/ElicitationClientDefaultsTests.cs b/tests/ModelContextProtocol.Tests/Protocol/ElicitationClientDefaultsTests.cs index e91790512..d43f1311a 100644 --- a/tests/ModelContextProtocol.Tests/Protocol/ElicitationClientDefaultsTests.cs +++ b/tests/ModelContextProtocol.Tests/Protocol/ElicitationClientDefaultsTests.cs @@ -43,6 +43,7 @@ public async Task ClientAppliesDefaults_NullContent() await using ClientElicitationTestTransport transport = new(s_elicitParamsWithDefaults); await using var client = await McpClient.CreateAsync(transport, new McpClientOptions { + ProtocolVersion = "2025-03-26", Handlers = new() { ElicitationHandler = (_, _) => @@ -69,6 +70,7 @@ public async Task ClientAppliesDefaults_EmptyContent() await using ClientElicitationTestTransport transport = new(s_elicitParamsWithDefaults); await using var client = await McpClient.CreateAsync(transport, new McpClientOptions { + ProtocolVersion = "2025-03-26", Handlers = new() { ElicitationHandler = (_, _) => @@ -94,6 +96,7 @@ public async Task ClientAppliesDefaults_PartialContent() await using ClientElicitationTestTransport transport = new(s_elicitParamsWithDefaults); await using var client = await McpClient.CreateAsync(transport, new McpClientOptions { + ProtocolVersion = "2025-03-26", Handlers = new() { ElicitationHandler = (_, _) => @@ -132,6 +135,7 @@ public async Task ClientAppliesDefaults_AllFieldsProvided_NoChange() await using ClientElicitationTestTransport transport = new(s_elicitParamsWithDefaults); await using var client = await McpClient.CreateAsync(transport, new McpClientOptions { + ProtocolVersion = "2025-03-26", Handlers = new() { ElicitationHandler = (_, _) => @@ -168,6 +172,7 @@ public async Task ClientAppliesDefaults_Decline_NoDefaultsApplied() await using ClientElicitationTestTransport transport = new(s_elicitParamsWithDefaults); await using var client = await McpClient.CreateAsync(transport, new McpClientOptions { + ProtocolVersion = "2025-03-26", Handlers = new() { ElicitationHandler = (_, _) => @@ -188,6 +193,7 @@ public async Task ClientAppliesDefaults_Cancel_NoDefaultsApplied() await using ClientElicitationTestTransport transport = new(s_elicitParamsWithDefaults); await using var client = await McpClient.CreateAsync(transport, new McpClientOptions { + ProtocolVersion = "2025-03-26", Handlers = new() { ElicitationHandler = (_, _) => new(new ElicitResult { Action = "cancel" }), @@ -220,6 +226,7 @@ public async Task ClientAppliesDefaults_SchemaWithNoDefaults_NoChange() await using ClientElicitationTestTransport transport = new(paramsNoDefaults); await using var client = await McpClient.CreateAsync(transport, new McpClientOptions { + ProtocolVersion = "2025-03-26", Handlers = new() { ElicitationHandler = (_, _) => @@ -271,6 +278,7 @@ public async Task ClientAppliesDefaults_MultiSelectEnum() await using ClientElicitationTestTransport transport = new(paramsMultiSelect); await using var client = await McpClient.CreateAsync(transport, new McpClientOptions { + ProtocolVersion = "2025-03-26", Handlers = new() { ElicitationHandler = (_, _) => @@ -318,6 +326,7 @@ public async Task ClientAppliesDefaults_TitledSingleSelectEnum() await using ClientElicitationTestTransport transport = new(paramsTitledEnum); await using var client = await McpClient.CreateAsync(transport, new McpClientOptions { + ProtocolVersion = "2025-03-26", Handlers = new() { ElicitationHandler = (_, _) => @@ -382,6 +391,10 @@ await _incomingToClient.Writer.WriteAsync(new JsonRpcResponse }, CancellationToken.None); }); } + else if (message is JsonRpcRequest request) + { + throw new InvalidOperationException($"The elicitation defaults test transport only supports initialize, not '{request.Method}'."); + } else if (message is JsonRpcResponse response) { // Capture the raw elicitation response from the client diff --git a/tests/ModelContextProtocol.Tests/StdioServerIntegrationTests.cs b/tests/ModelContextProtocol.Tests/StdioServerIntegrationTests.cs index 88604f533..1da26a853 100644 --- a/tests/ModelContextProtocol.Tests/StdioServerIntegrationTests.cs +++ b/tests/ModelContextProtocol.Tests/StdioServerIntegrationTests.cs @@ -35,7 +35,7 @@ public async Task SigInt_DisposesTestServerWithHosting_Gracefully() process.StandardInput.BaseStream, serverName: "TestServerWithHosting"); - await using var client = await McpClient.CreateAsync( + await using var client = await TestMcpClient.CreateAsync( new TestClientTransport(streamServerTransport), loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken);