Skip to content

Conversation

@halter73
Copy link
Contributor

@halter73 halter73 commented Oct 9, 2025

Unlike the SseHandler, the StreamableHttpHandler previously di not listen to the ApplicationStopping token for the long-running GET SSE request. Other requests like the POST requests still do not to ApplicationStopping because we want to give the handlers associated with the POST time to complete during graceful shutdown. The GET request should complete immediately though, so if there are no ongoing tool calls or other operations, the server shuts down immediately.

Fixes #825

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)
…utsDownQuickly_WhenClientIsConnected test

Example Error: System.InvalidOperationException : Reading is not allowed after reader was completed.

  Failed ModelContextProtocol.AspNetCore.Tests.MapMcpStatelessTests.Server_ShutsDownQuickly_WhenClientIsConnected [210 ms]
  Error Message:
   System.InvalidOperationException : Reading is not allowed after reader was completed.
  Stack Trace:
     at System.IO.Pipelines.ThrowHelper.ThrowInvalidOperationException_NoReadingAllowed()
   at System.IO.Pipelines.Pipe.AdvanceReader(SequencePosition& consumed, SequencePosition& examined)
   at System.IO.Pipelines.Pipe.DefaultPipeReader.AdvanceTo(SequencePosition consumed)
   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.FillAsync(Boolean async)
   at System.Net.Http.HttpConnection.ChunkedEncodingReadStream.ReadAsyncCore(Memory`1 buffer, CancellationToken cancellationToken)
   at System.Net.ServerSentEvents.SseParser`1.FillLineBufferAsync(CancellationToken cancellationToken)
   at System.Net.ServerSentEvents.SseParser`1.EnumerateAsync(CancellationToken cancellationToken)+MoveNext()
   at System.Net.ServerSentEvents.SseParser`1.EnumerateAsync(CancellationToken cancellationToken)+System.Threading.Tasks.Sources.IValueTaskSource<System.Boolean>.GetResult()
   at ModelContextProtocol.Client.StreamableHttpClientSessionTransport.ProcessSseResponseAsync(Stream responseStream, JsonRpcRequest relatedRpcRequest, CancellationToken cancellationToken) in /_/src/ModelContextProtocol.Core/Client/StreamableHttpClientSessionTransport.cs:line 192
   at ModelContextProtocol.Client.StreamableHttpClientSessionTransport.ProcessSseResponseAsync(Stream responseStream, JsonRpcRequest relatedRpcRequest, CancellationToken cancellationToken) in /_/src/ModelContextProtocol.Core/Client/StreamableHttpClientSessionTransport.cs:line 192
   at ModelContextProtocol.Client.StreamableHttpClientSessionTransport.ReceiveUnsolicitedMessagesAsync() in /_/src/ModelContextProtocol.Core/Client/StreamableHttpClientSessionTransport.cs:line 187
   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 ---
  Standard Output Messages:
 | [2025-10-10T23:37:09] Microsoft.Hosting.Lifetime Information: Now listening on: http://localhost:5000
 | [2025-10-10T23:37:09] Microsoft.Hosting.Lifetime Information: Application started. Press Ctrl+C to shut down.
 | [2025-10-10T23:37:09] Microsoft.Hosting.Lifetime Information: Hosting environment: Production
 | [2025-10-10T23:37:09] Microsoft.Hosting.Lifetime Information: Content root path: /home/runner/work/csharp-sdk/csharp-sdk/artifacts/bin/ModelContextProtocol.AspNetCore.Tests/Debug/net9.0
 | [2025-10-10T23:37:09] Microsoft.AspNetCore.Hosting.Diagnostics Information: Request starting HTTP/1.1 POST http://localhost:5000/ - application/json;+charset=utf-8 -
 | [2025-10-10T23:37:09] Microsoft.AspNetCore.Routing.EndpointMiddleware Information: Executing endpoint 'MCP Streamable HTTP | HTTP: POST /'
 | [2025-10-10T23:37:09] ModelContextProtocol.Server.McpServer Information: Server (ModelContextProtocol.AspNetCore.Tests 1.0.0.0) method 'initialize' request handler called.
 | [2025-10-10T23:37:09] ModelContextProtocol.Server.McpServer Information: Server (ModelContextProtocol.AspNetCore.Tests 1.0.0.0), Client (ModelContextProtocol.AspNetCore.Tests 1.0.0.0) method 'initialize' request handler completed.
 | [2025-10-10T23:37:09] Microsoft.AspNetCore.Routing.EndpointMiddleware Information: Executed endpoint 'MCP Streamable HTTP | HTTP: POST /'
 | [2025-10-10T23:37:09] Microsoft.AspNetCore.Hosting.Diagnostics Information: Request finished HTTP/1.1 POST http://localhost:5000/ - 200 - text/event-stream 1.4557ms
 | [2025-10-10T23:37:09] Microsoft.AspNetCore.Hosting.Diagnostics Information: Request starting HTTP/1.1 GET http://localhost:5000/ - - -
 | [2025-10-10T23:37:09] ModelContextProtocol.Client.McpClient Information: Client (ModelContextProtocol.AspNetCore.Tests 1.0.0.0) client received server '{"name":"ModelContextProtocol.AspNetCore.Tests","version":"1.0.0.0"}' capabilities: '{"logging":{},"tools":{"listChanged":true}}'.
 | [2025-10-10T23:37:09] Microsoft.AspNetCore.Routing.EndpointMiddleware Information: Executing endpoint 'MCP Streamable HTTP | HTTP: GET /'
 | [2025-10-10T23:37:09] Microsoft.AspNetCore.Hosting.Diagnostics Information: Request starting HTTP/1.1 POST http://localhost:5000/ - application/json;+charset=utf-8 -
 | [2025-10-10T23:37:09] Microsoft.AspNetCore.Routing.EndpointMiddleware Information: Executing endpoint 'MCP Streamable HTTP | HTTP: POST /'
 | [2025-10-10T23:37:09] Microsoft.AspNetCore.Routing.EndpointMiddleware Information: Executed endpoint 'MCP Streamable HTTP | HTTP: POST /'
 | [2025-10-10T23:37:09] Microsoft.AspNetCore.Hosting.Diagnostics Information: Request finished HTTP/1.1 POST http://localhost:5000/ - 202 0 - 0.4372ms
 | [2025-10-10T23:37:09] ModelContextProtocol.Client.McpClient Information: Client (ModelContextProtocol.AspNetCore.Tests 1.0.0.0) client created and connected.
 | [2025-10-10T23:37:09] Microsoft.AspNetCore.Hosting.Diagnostics Information: Request starting HTTP/1.1 POST http://localhost:5000/ - application/json;+charset=utf-8 -
 | [2025-10-10T23:37:09] Microsoft.AspNetCore.Routing.EndpointMiddleware Information: Executing endpoint 'MCP Streamable HTTP | HTTP: POST /'
 | [2025-10-10T23:37:09] ModelContextProtocol.Server.McpServer Information: Server (ModelContextProtocol.AspNetCore.Tests 1.0.0.0), Client (ModelContextProtocol.AspNetCore.Tests 1.0.0.0) method 'tools/list' request handler called.
 | [2025-10-10T23:37:09] ModelContextProtocol.Server.McpServer Information: Server (ModelContextProtocol.AspNetCore.Tests 1.0.0.0), Client (ModelContextProtocol.AspNetCore.Tests 1.0.0.0) method 'tools/list' request handler completed.
 | [2025-10-10T23:37:09] Microsoft.AspNetCore.Routing.EndpointMiddleware Information: Executed endpoint 'MCP Streamable HTTP | HTTP: POST /'
 | [2025-10-10T23:37:09] Microsoft.AspNetCore.Hosting.Diagnostics Information: Request finished HTTP/1.1 POST http://localhost:5000/ - 200 - text/event-stream 0.6495ms
 | [2025-10-10T23:37:09] Microsoft.Hosting.Lifetime Information: Application is shutting down...
 | [2025-10-10T23:37:09] Microsoft.AspNetCore.Routing.EndpointMiddleware Information: Executed endpoint 'MCP Streamable HTTP | HTTP: GET /'
 | [2025-10-10T23:37:09] Microsoft.AspNetCore.Hosting.Diagnostics Information: Request finished HTTP/1.1 GET http://localhost:5000/ - 200 - text/event-stream 16.9226ms
 | [2025-10-10T23:37:09] ModelContextProtocol.Client.McpClient Information: http://localhost:5000/ message processing canceled.
 | [2025-10-10T23:37:09] ModelContextProtocol.Client.StreamableHttpClientSessionTransport Warning: http://localhost:5000/ shutdown failed.
 System.Net.Http.HttpRequestException: The KestrelInMemoryTransport has been shut down. (localhost:5000)
  ---> System.IO.IOException: The KestrelInMemoryTransport has been shut down.
    at ModelContextProtocol.AspNetCore.Tests.Utils.KestrelInMemoryTransport.CreateConnection(EndPoint endpoint) in /_/tests/ModelContextProtocol.AspNetCore.Tests/Utils/KestrelInMemoryTransport.cs:line 18
    at ModelContextProtocol.AspNetCore.Tests.Utils.KestrelInMemoryTest.<.ctor>b__0_0(SocketsHttpConnectionContext context, CancellationToken token) in /_/tests/ModelContextProtocol.AspNetCore.Tests/Utils/KestrelInMemoryTest.cs:line 23
    at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)
    --- End of inner exception stack trace ---
    at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)
    at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
    at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
    at System.Net.Http.HttpConnectionPool.InjectNewHttp11ConnectionAsync(QueueItem queueItem)
    at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.WaitWithCancellationAsync(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.SendDeleteRequest() in /_/src/ModelContextProtocol.Core/Client/StreamableHttpClientSessionTransport.cs:line 249
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Asp.net core server shutdown - disconnect or notify connected MCP clients

3 participants