Skip to content

Fix: swallow _receiveTask exceptions in SseClientSessionTransport.CloseAsync#1432

Merged
jeffhandley merged 5 commits into
modelcontextprotocol:mainfrom
xue-cai:fix/sse-connect-close-exception-swallow
Jul 26, 2026
Merged

Fix: swallow _receiveTask exceptions in SseClientSessionTransport.CloseAsync#1432
jeffhandley merged 5 commits into
modelcontextprotocol:mainfrom
xue-cai:fix/sse-connect-close-exception-swallow

Conversation

@xue-cai

@xue-cai xue-cai commented Mar 14, 2026

Copy link
Copy Markdown
Contributor

When SseClientSessionTransport.ConnectAsync fails (for example, an SSE GET returns 405), ReceiveMessagesAsync reports the failure through _connectionEstablished and the connection path closes the transport.

Previously, ReceiveMessagesAsync rethrew after reporting that failure. This faulted _receiveTask, so CloseAsync could rethrow the same exception while awaiting it and preempt the original connection-failure path.

The fix leaves the receive task completed after recording and logging the failure, allowing CloseAsync to await it normally. ConnectAsync then rethrows the original exception after cleanup rather than adding an inaccurate wrapper.

The regression coverage verifies direct SSE failures preserve their original exception, and that auto-detection preserves the initial Streamable HTTP error while retaining the SSE fallback failure as its inner exception.

…seAsync

When SseClientSessionTransport.ConnectAsync fails (e.g. server returns 405),
the catch block calls CloseAsync() before wrapping the error in
InvalidOperationException. However, CloseAsync() awaits the already-faulted
_receiveTask without a try-catch, causing the original HttpRequestException
to propagate out of CloseAsync and preempt the InvalidOperationException
wrapping. Callers then receive a bare HttpRequestException instead of the
documented InvalidOperationException("Failed to connect transport", ex).

The fix wraps `await _receiveTask` in CloseAsync with a try-catch to
swallow already-observed exceptions from the faulted task.

Code references:
  SseClientSessionTransport.ConnectAsync (catch block calls CloseAsync):
  https://github.com/modelcontextprotocol/csharp-sdk/blob/v0.9.0-preview.2/src/ModelContextProtocol.Core/Client/SseClientSessionTransport.cs#L52-L67

  SseClientSessionTransport.CloseAsync (awaits _receiveTask without try-catch):
  https://github.com/modelcontextprotocol/csharp-sdk/blob/v0.9.0-preview.2/src/ModelContextProtocol.Core/Client/SseClientSessionTransport.cs#L108-L130

Testing:
  # Build all frameworks (only netstandard2.0 fails — pre-existing on main):
  dotnet build tests/ModelContextProtocol.Tests/ '/p:NoWarn=NU1903%3BMCPEXP001'

  # Run full transport suite (74 tests × 3 frameworks = 222 total, 0 failures):
  dotnet test tests/ModelContextProtocol.Tests/ '/p:NoWarn=NU1903%3BMCPEXP001' --no-build --filter "FullyQualifiedName~Transport" --framework net10.0
  dotnet test tests/ModelContextProtocol.Tests/ '/p:NoWarn=NU1903%3BMCPEXP001' --no-build --filter "FullyQualifiedName~Transport" --framework net9.0
  dotnet test tests/ModelContextProtocol.Tests/ '/p:NoWarn=NU1903%3BMCPEXP001' --no-build --filter "FullyQualifiedName~Transport" --framework net8.0

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@xue-cai
xue-cai force-pushed the fix/sse-connect-close-exception-swallow branch from 9b21522 to 056f1f5 Compare March 17, 2026 01:29
Comment thread src/ModelContextProtocol.Core/Client/SseClientSessionTransport.cs Outdated
@halter73

Copy link
Copy Markdown
Contributor

Not exactly related to this PR, but I can't unsee the incorrect InvalidOperationException we throw from line 65. Can we turn that into just a throw; while we're at it?

throw new InvalidOperationException("Failed to connect transport", ex);

jeffhandley and others added 4 commits July 26, 2026 00:26
…-exception-swallow

# Conflicts:
#	tests/ModelContextProtocol.Tests/Transport/HttpClientTransportAutoDetectTests.cs
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@jeffhandley

Copy link
Copy Markdown
Contributor

This will be a subtle breaking change because of changing the exception types thrown. We'll capture it in the release notes.

Thanks for getting this PR started, @xue-cai.

@jeffhandley jeffhandley added the breaking-change This issue or PR introduces a breaking change label Jul 26, 2026
@jeffhandley jeffhandley changed the title Fix: swallow _receiveTask exceptions in SseClientSessionTransport.Clo… Fix: swallow _receiveTask exceptions in SseClientSessionTransport.CloseAsync Jul 26, 2026
@jeffhandley
jeffhandley merged commit ec216b5 into modelcontextprotocol:main Jul 26, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-change This issue or PR introduces a breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants