From 3563d1efc0fdc3fd91721247d9e6d1fbbe9165ab Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 30 Jan 2026 18:18:23 +0000
Subject: [PATCH 1/4] Initial plan
From 0c2494568882c47d82dcf6f2aed06ac64ebff375 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 30 Jan 2026 18:24:23 +0000
Subject: [PATCH 2/4] Centralize HttpClient and process timeout constants in
tests
- Add HttpClientTimeout and HttpClientHealthCheckTimeout constants to TestConstants
- Update all HttpClient.Timeout usages in tests to use centralized constants
- Update all WaitForExitAsync calls to use TestConstants.DefaultTimeout
- Update HttpClientTransportOptions.ConnectionTimeout default from 30s to 60s to match test timeout
- Ensure consistent timeout behavior across tests to prevent CI timeouts
Co-authored-by: ericstj <8918108+ericstj@users.noreply.github.com>
---
.../Client/HttpClientTransportOptions.cs | 4 ++--
tests/Common/Utils/TestConstants.cs | 12 ++++++++++++
.../ServerConformanceTests.cs | 2 +-
.../Utils/KestrelInMemoryTest.cs | 2 +-
.../EverythingSseServerFixture.cs | 5 +++--
.../StdioServerIntegrationTests.cs | 2 +-
6 files changed, 20 insertions(+), 7 deletions(-)
diff --git a/src/ModelContextProtocol.Core/Client/HttpClientTransportOptions.cs b/src/ModelContextProtocol.Core/Client/HttpClientTransportOptions.cs
index 73eaae700..413e552d7 100644
--- a/src/ModelContextProtocol.Core/Client/HttpClientTransportOptions.cs
+++ b/src/ModelContextProtocol.Core/Client/HttpClientTransportOptions.cs
@@ -57,7 +57,7 @@ public required Uri Endpoint
/// Gets or sets a timeout used to establish the initial connection to the SSE server.
///
///
- /// The timeout used to establish the initial connection to the SSE server. The default is 30 seconds.
+ /// The timeout used to establish the initial connection to the SSE server. The default is 60 seconds.
///
///
/// This timeout controls how long the client waits for:
@@ -67,7 +67,7 @@ public required Uri Endpoint
///
/// If the timeout expires before the connection is established, a is thrown.
///
- public TimeSpan ConnectionTimeout { get; set; } = TimeSpan.FromSeconds(30);
+ public TimeSpan ConnectionTimeout { get; set; } = TimeSpan.FromSeconds(60);
///
/// Gets or sets custom HTTP headers to include in requests to the SSE server.
diff --git a/tests/Common/Utils/TestConstants.cs b/tests/Common/Utils/TestConstants.cs
index eae9a54b1..4c3444ae7 100644
--- a/tests/Common/Utils/TestConstants.cs
+++ b/tests/Common/Utils/TestConstants.cs
@@ -10,4 +10,16 @@ public static class TestConstants
/// Set to 60 seconds to provide sufficient buffer for slow CI environments.
///
public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(60);
+
+ ///
+ /// Timeout for HttpClient operations in tests.
+ /// Set to 60 seconds to provide sufficient buffer for slow CI environments.
+ ///
+ public static readonly TimeSpan HttpClientTimeout = TimeSpan.FromSeconds(60);
+
+ ///
+ /// Timeout for short-lived HTTP requests during server health checks.
+ /// Set to 2 seconds for quick failure detection while polling.
+ ///
+ public static readonly TimeSpan HttpClientHealthCheckTimeout = TimeSpan.FromSeconds(2);
}
diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/ServerConformanceTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/ServerConformanceTests.cs
index 031a67a18..b1d37420a 100644
--- a/tests/ModelContextProtocol.AspNetCore.Tests/ServerConformanceTests.cs
+++ b/tests/ModelContextProtocol.AspNetCore.Tests/ServerConformanceTests.cs
@@ -47,7 +47,7 @@ public async ValueTask InitializeAsync()
// Wait for server to be ready (retry for up to 30 seconds)
var timeout = TimeSpan.FromSeconds(30);
var stopwatch = Stopwatch.StartNew();
- using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(2) };
+ using var httpClient = new HttpClient { Timeout = TestConstants.HttpClientHealthCheckTimeout };
while (stopwatch.Elapsed < timeout)
{
diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/Utils/KestrelInMemoryTest.cs b/tests/ModelContextProtocol.AspNetCore.Tests/Utils/KestrelInMemoryTest.cs
index c93a27650..0bbff49b4 100644
--- a/tests/ModelContextProtocol.AspNetCore.Tests/Utils/KestrelInMemoryTest.cs
+++ b/tests/ModelContextProtocol.AspNetCore.Tests/Utils/KestrelInMemoryTest.cs
@@ -42,7 +42,7 @@ public KestrelInMemoryTest(ITestOutputHelper testOutputHelper)
protected static void ConfigureHttpClient(HttpClient httpClient)
{
httpClient.BaseAddress = new Uri("http://localhost:5000/");
- httpClient.Timeout = TimeSpan.FromSeconds(10);
+ httpClient.Timeout = TestConstants.HttpClientTimeout;
}
public override void Dispose()
diff --git a/tests/ModelContextProtocol.Tests/EverythingSseServerFixture.cs b/tests/ModelContextProtocol.Tests/EverythingSseServerFixture.cs
index f12aff5e5..71f82bf10 100644
--- a/tests/ModelContextProtocol.Tests/EverythingSseServerFixture.cs
+++ b/tests/ModelContextProtocol.Tests/EverythingSseServerFixture.cs
@@ -1,5 +1,6 @@
using System.Diagnostics;
using System.Net;
+using ModelContextProtocol.Tests.Utils;
namespace ModelContextProtocol.Tests;
@@ -33,7 +34,7 @@ public async Task StartAsync()
?? throw new InvalidOperationException($"Could not start process for {processStartInfo.FileName} with '{processStartInfo.Arguments}'.");
// Poll until the server is ready (up to 30 seconds)
- using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(2) };
+ using var httpClient = new HttpClient { Timeout = TestConstants.HttpClientHealthCheckTimeout };
var endpoint = $"http://localhost:{_port}/sse";
var deadline = DateTime.UtcNow.AddSeconds(30);
@@ -72,7 +73,7 @@ public async ValueTask DisposeAsync()
using var stopProcess = Process.Start(stopInfo)
?? throw new InvalidOperationException($"Could not stop process for {stopInfo.FileName} with '{stopInfo.Arguments}'.");
- await stopProcess.WaitForExitAsync(TimeSpan.FromSeconds(10));
+ await stopProcess.WaitForExitAsync(TestConstants.DefaultTimeout);
}
catch (Exception ex)
{
diff --git a/tests/ModelContextProtocol.Tests/StdioServerIntegrationTests.cs b/tests/ModelContextProtocol.Tests/StdioServerIntegrationTests.cs
index d14c376c1..88604f533 100644
--- a/tests/ModelContextProtocol.Tests/StdioServerIntegrationTests.cs
+++ b/tests/ModelContextProtocol.Tests/StdioServerIntegrationTests.cs
@@ -46,7 +46,7 @@ public async Task SigInt_DisposesTestServerWithHosting_Gracefully()
// https://github.com/dotnet/runtime/issues/109432, https://github.com/dotnet/runtime/issues/44944
Assert.Equal(0, kill(process.Id, SIGINT));
- await process.WaitForExitAsync(TimeSpan.FromSeconds(10));
+ await process.WaitForExitAsync(TestConstants.DefaultTimeout);
Assert.True(process.HasExited);
Assert.Equal(0, process.ExitCode);
From 6d5b204fe180fdbd93731721194d32ad8ff0d3bc Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 30 Jan 2026 18:38:52 +0000
Subject: [PATCH 3/4] Address PR feedback: revert product change and rename
constant
- Revert HttpClientTransportOptions.ConnectionTimeout from 60s back to 30s (no product changes)
- Rename HttpClientHealthCheckTimeout to HttpClientPollingTimeout per feedback
- Update all usages of the renamed constant
- HttpClientTransportTests.cs does not need changes (uses test-specific ConnectionTimeout for mock handlers)
Co-authored-by: ericstj <8918108+ericstj@users.noreply.github.com>
---
.../Client/HttpClientTransportOptions.cs | 4 ++--
tests/Common/Utils/TestConstants.cs | 4 ++--
.../ServerConformanceTests.cs | 2 +-
.../ModelContextProtocol.Tests/EverythingSseServerFixture.cs | 2 +-
4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/ModelContextProtocol.Core/Client/HttpClientTransportOptions.cs b/src/ModelContextProtocol.Core/Client/HttpClientTransportOptions.cs
index 413e552d7..73eaae700 100644
--- a/src/ModelContextProtocol.Core/Client/HttpClientTransportOptions.cs
+++ b/src/ModelContextProtocol.Core/Client/HttpClientTransportOptions.cs
@@ -57,7 +57,7 @@ public required Uri Endpoint
/// Gets or sets a timeout used to establish the initial connection to the SSE server.
///
///
- /// The timeout used to establish the initial connection to the SSE server. The default is 60 seconds.
+ /// The timeout used to establish the initial connection to the SSE server. The default is 30 seconds.
///
///
/// This timeout controls how long the client waits for:
@@ -67,7 +67,7 @@ public required Uri Endpoint
///
/// If the timeout expires before the connection is established, a is thrown.
///
- public TimeSpan ConnectionTimeout { get; set; } = TimeSpan.FromSeconds(60);
+ public TimeSpan ConnectionTimeout { get; set; } = TimeSpan.FromSeconds(30);
///
/// Gets or sets custom HTTP headers to include in requests to the SSE server.
diff --git a/tests/Common/Utils/TestConstants.cs b/tests/Common/Utils/TestConstants.cs
index 4c3444ae7..4a337df36 100644
--- a/tests/Common/Utils/TestConstants.cs
+++ b/tests/Common/Utils/TestConstants.cs
@@ -18,8 +18,8 @@ public static class TestConstants
public static readonly TimeSpan HttpClientTimeout = TimeSpan.FromSeconds(60);
///
- /// Timeout for short-lived HTTP requests during server health checks.
+ /// Timeout for short-lived HTTP requests during polling operations.
/// Set to 2 seconds for quick failure detection while polling.
///
- public static readonly TimeSpan HttpClientHealthCheckTimeout = TimeSpan.FromSeconds(2);
+ public static readonly TimeSpan HttpClientPollingTimeout = TimeSpan.FromSeconds(2);
}
diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/ServerConformanceTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/ServerConformanceTests.cs
index b1d37420a..02a98b890 100644
--- a/tests/ModelContextProtocol.AspNetCore.Tests/ServerConformanceTests.cs
+++ b/tests/ModelContextProtocol.AspNetCore.Tests/ServerConformanceTests.cs
@@ -47,7 +47,7 @@ public async ValueTask InitializeAsync()
// Wait for server to be ready (retry for up to 30 seconds)
var timeout = TimeSpan.FromSeconds(30);
var stopwatch = Stopwatch.StartNew();
- using var httpClient = new HttpClient { Timeout = TestConstants.HttpClientHealthCheckTimeout };
+ using var httpClient = new HttpClient { Timeout = TestConstants.HttpClientPollingTimeout };
while (stopwatch.Elapsed < timeout)
{
diff --git a/tests/ModelContextProtocol.Tests/EverythingSseServerFixture.cs b/tests/ModelContextProtocol.Tests/EverythingSseServerFixture.cs
index 71f82bf10..e579ff9f0 100644
--- a/tests/ModelContextProtocol.Tests/EverythingSseServerFixture.cs
+++ b/tests/ModelContextProtocol.Tests/EverythingSseServerFixture.cs
@@ -34,7 +34,7 @@ public async Task StartAsync()
?? throw new InvalidOperationException($"Could not start process for {processStartInfo.FileName} with '{processStartInfo.Arguments}'.");
// Poll until the server is ready (up to 30 seconds)
- using var httpClient = new HttpClient { Timeout = TestConstants.HttpClientHealthCheckTimeout };
+ using var httpClient = new HttpClient { Timeout = TestConstants.HttpClientPollingTimeout };
var endpoint = $"http://localhost:{_port}/sse";
var deadline = DateTime.UtcNow.AddSeconds(30);
From b72d0f7d3e29dc8bd042a376b4e4728abaa4f262 Mon Sep 17 00:00:00 2001
From: Eric StJohn
Date: Fri, 30 Jan 2026 12:07:45 -0800
Subject: [PATCH 4/4] Add wait for TestOAuthServer to start
---
.../OAuth/OAuthTestBase.cs | 5 +++++
.../Program.cs | 21 ++++++++++++++++++-
2 files changed, 25 insertions(+), 1 deletion(-)
diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/OAuthTestBase.cs b/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/OAuthTestBase.cs
index 52b5616d0..5d66ee80e 100644
--- a/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/OAuthTestBase.cs
+++ b/tests/ModelContextProtocol.AspNetCore.Tests/OAuth/OAuthTestBase.cs
@@ -83,6 +83,11 @@ public async ValueTask DisposeAsync()
protected async Task StartMcpServerAsync(string path = "", string? authScheme = null)
{
+ // Wait for the OAuth server to be ready before starting the MCP server.
+ // This prevents race conditions in CI where the OAuth server may not be
+ // fully initialized when the first test request is made.
+ await TestOAuthServer.ServerStarted.WaitAsync(TestContext.Current.CancellationToken);
+
Builder.Services.Configure(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.TokenValidationParameters.ValidAudience = $"{McpServerUrl}{path}";
diff --git a/tests/ModelContextProtocol.TestOAuthServer/Program.cs b/tests/ModelContextProtocol.TestOAuthServer/Program.cs
index e13c731de..364836311 100644
--- a/tests/ModelContextProtocol.TestOAuthServer/Program.cs
+++ b/tests/ModelContextProtocol.TestOAuthServer/Program.cs
@@ -33,6 +33,7 @@ public sealed class Program
private readonly ILoggerProvider? _loggerProvider;
private readonly IConnectionListenerFactory? _kestrelTransport;
+ private readonly TaskCompletionSource _serverStarted = new(TaskCreationOptions.RunContinuationsAsynchronously);
///
/// Initializes a new instance of the class with logging and transport parameters.
@@ -47,6 +48,11 @@ public Program(ILoggerProvider? loggerProvider = null, IConnectionListenerFactor
_kestrelTransport = kestrelTransport;
}
+ ///
+ /// Gets a task that completes when the server has started and is ready to accept connections.
+ ///
+ public Task ServerStarted => _serverStarted.Task;
+
// Track if we've already issued an already-expired token for the CanAuthenticate_WithTokenRefresh test which uses the test-refresh-client registration.
public bool HasRefreshedToken { get; set; }
@@ -541,7 +547,20 @@ IResult HandleMetadataRequest(HttpContext context, string? issuerPath = null)
Console.WriteLine($"Demo Client ID: {clientId}");
Console.WriteLine($"Demo Client Secret: {clientSecret}");
- await app.RunAsync(cancellationToken);
+ await app.StartAsync(cancellationToken);
+ _serverStarted.TrySetResult();
+
+ // Wait until cancellation is requested
+ try
+ {
+ await Task.Delay(Timeout.Infinite, cancellationToken);
+ }
+ catch (OperationCanceledException)
+ {
+ // Expected when cancellation is requested
+ }
+
+ await app.StopAsync();
}
///