Skip to content

Commit 2c9c85e

Browse files
committed
Prevent GET SSE request failure from causing client disposal exception
Example failure caused by this: Failed ModelContextProtocol.AspNetCore.Tests.MapMcpStatelessTests.Server_ShutsDownQuickly_WhenClientIsConnected [138 ms] Error Message: System.Net.Http.HttpRequestException : An error occurred while sending the request. ---- System.InvalidOperationException : Reading is not allowed after reader was completed. Stack Trace: at System.Net.Http.HttpConnection.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken) at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken) at ModelContextProtocol.Client.McpHttpClient.SendAsync(HttpRequestMessage request, JsonRpcMessage message, CancellationToken cancellationToken) in /_/src/ModelContextProtocol.Core/Client/McpHttpClient.cs:line 22 at ModelContextProtocol.Client.StreamableHttpClientSessionTransport.ReceiveUnsolicitedMessagesAsync() in /_/src/ModelContextProtocol.Core/Client/StreamableHttpClientSessionTransport.cs:line 178 at ModelContextProtocol.Client.StreamableHttpClientSessionTransport.DisposeAsync() in /_/src/ModelContextProtocol.Core/Client/StreamableHttpClientSessionTransport.cs:line 149 at ModelContextProtocol.Client.McpClientImpl.DisposeAsync() in /_/src/ModelContextProtocol.Core/Client/McpClientImpl.cs:line 235 at ModelContextProtocol.AspNetCore.Tests.MapMcpTests.Server_ShutsDownQuickly_WhenClientIsConnected() in /_/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs:line 238 at ModelContextProtocol.AspNetCore.Tests.MapMcpTests.Server_ShutsDownQuickly_WhenClientIsConnected() in /_/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs:line 238 --- End of stack trace from previous location --- ----- Inner Stack Trace ----- at System.IO.Pipelines.ThrowHelper.ThrowInvalidOperationException_NoReadingAllowed() at System.IO.Pipelines.Pipe.AdvanceReader(SequencePosition& consumed, SequencePosition& examined) at System.IO.Pipelines.PipeReaderStream.HandleReadResult(ReadResult result, Span`1 buffer) at System.IO.Pipelines.PipeReaderStream.HandleReadResult(ReadResult result, Span`1 buffer) at System.IO.Pipelines.PipeReaderStream.ReadAsyncInternal(Memory`1 buffer, CancellationToken cancellationToken) at ModelContextProtocol.AspNetCore.Tests.Utils.KestrelInMemoryConnection.DuplexStream.ReadAsync(Memory`1 buffer, CancellationToken cancellationToken) in /_/tests/ModelContextProtocol.AspNetCore.Tests/Utils/KestrelInMemoryConnection.cs:line 81 at System.Net.Http.HttpConnection.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
1 parent d1fddf6 commit 2c9c85e

File tree

1 file changed

+17
-6
lines changed

1 file changed

+17
-6
lines changed

src/ModelContextProtocol.Core/Client/StreamableHttpClientSessionTransport.cs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,16 +175,27 @@ private async Task ReceiveUnsolicitedMessagesAsync()
175175
request.Headers.Accept.Add(s_textEventStreamMediaType);
176176
CopyAdditionalHeaders(request.Headers, _options.AdditionalHeaders, SessionId, _negotiatedProtocolVersion);
177177

178-
using var response = await _httpClient.SendAsync(request, message: null, _connectionCts.Token).ConfigureAwait(false);
179-
180-
if (!response.IsSuccessStatusCode)
178+
// Server support for the GET request is optional. If it fails, we don't care. It just means we won't receive unsolicited messages.
179+
HttpResponseMessage response;
180+
try
181+
{
182+
response = await _httpClient.SendAsync(request, message: null, _connectionCts.Token).ConfigureAwait(false);
183+
}
184+
catch (HttpRequestException)
181185
{
182-
// Server support for the GET request is optional. If it fails, we don't care. It just means we won't receive unsolicited messages.
183186
return;
184187
}
185188

186-
using var responseStream = await response.Content.ReadAsStreamAsync(_connectionCts.Token).ConfigureAwait(false);
187-
await ProcessSseResponseAsync(responseStream, relatedRpcRequest: null, _connectionCts.Token).ConfigureAwait(false);
189+
using (response)
190+
{
191+
if (!response.IsSuccessStatusCode)
192+
{
193+
return;
194+
}
195+
196+
using var responseStream = await response.Content.ReadAsStreamAsync(_connectionCts.Token).ConfigureAwait(false);
197+
await ProcessSseResponseAsync(responseStream, relatedRpcRequest: null, _connectionCts.Token).ConfigureAwait(false);
198+
}
188199
}
189200

190201
private async Task<JsonRpcMessageWithId?> ProcessSseResponseAsync(Stream responseStream, JsonRpcRequest? relatedRpcRequest, CancellationToken cancellationToken)

0 commit comments

Comments
 (0)