Skip to content

Commit 21319af

Browse files
Copilotstephentoub
andauthored
Rename TransportClosedException to ClientTransportClosedException
Aligns with naming conventions: the exception carries ClientCompletionDetails, lives in the Client namespace, and follows the pattern of StdioClientTransport, HttpClientTransport, ClientCompletionDetails, etc. Agent-Logs-Url: https://github.com/modelcontextprotocol/csharp-sdk/sessions/5f35eb13-5319-4916-92da-3f9c7c253766 Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
1 parent 2a5e1d7 commit 21319af

File tree

8 files changed

+33
-33
lines changed

8 files changed

+33
-33
lines changed

src/ModelContextProtocol.Core/Client/TransportClosedException.cs renamed to src/ModelContextProtocol.Core/Client/ClientTransportClosedException.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
namespace ModelContextProtocol.Client;
55

66
/// <summary>
7-
/// An <see cref="IOException"/> that indicates the transport was closed, carrying
7+
/// An <see cref="IOException"/> that indicates the client transport was closed, carrying
88
/// structured <see cref="ClientCompletionDetails"/> about why the closure occurred.
99
/// </summary>
1010
/// <remarks>
@@ -25,7 +25,7 @@ namespace ModelContextProtocol.Client;
2525
/// <see cref="ChannelWriter{T}"/> with this exception.
2626
/// </para>
2727
/// </remarks>
28-
public sealed class TransportClosedException(ClientCompletionDetails details) :
28+
public sealed class ClientTransportClosedException(ClientCompletionDetails details) :
2929
IOException(details.Exception?.Message ?? "The transport was closed.", details.Exception)
3030
{
3131
/// <summary>

src/ModelContextProtocol.Core/Client/McpClient.Methods.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public static async Task<McpClient> CreateAsync(
5353
{
5454
await clientSession.ConnectAsync(cancellationToken).ConfigureAwait(false);
5555
}
56-
catch (Exception ex) when (ex is not OperationCanceledException and not TransportClosedException)
56+
catch (Exception ex) when (ex is not OperationCanceledException and not ClientTransportClosedException)
5757
{
5858
// ConnectAsync already disposed the session (which includes awaiting Completion).
5959
// Check if the transport provided structured completion details indicating
@@ -63,14 +63,14 @@ public static async Task<McpClient> CreateAsync(
6363

6464
// If the transport closed with a non-graceful error (e.g., server process exited)
6565
// and the completion details carry an exception that's NOT already in the original
66-
// exception chain, throw a TransportClosedException with the structured details so
66+
// exception chain, throw a ClientTransportClosedException with the structured details so
6767
// callers can programmatically inspect the closure reason (exit code, stderr, etc.).
6868
// When the same exception is already in the chain (e.g., HttpRequestException from
6969
// an HTTP transport), the original exception is more appropriate to re-throw.
7070
if (completionDetails.Exception is { } detailsException &&
7171
!ExceptionChainContains(ex, detailsException))
7272
{
73-
throw new TransportClosedException(completionDetails);
73+
throw new ClientTransportClosedException(completionDetails);
7474
}
7575

7676
throw;

src/ModelContextProtocol.Core/Client/SseClientSessionTransport.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ private async Task CloseAsync()
129129
}
130130
finally
131131
{
132-
SetDisconnected(new TransportClosedException(new HttpClientCompletionDetails()));
132+
SetDisconnected(new ClientTransportClosedException(new HttpClientCompletionDetails()));
133133
}
134134
}
135135

@@ -190,7 +190,7 @@ private async Task ReceiveMessagesAsync(CancellationToken cancellationToken)
190190
}
191191
else
192192
{
193-
SetDisconnected(new TransportClosedException(new HttpClientCompletionDetails
193+
SetDisconnected(new ClientTransportClosedException(new HttpClientCompletionDetails
194194
{
195195
HttpStatusCode = failureStatusCode,
196196
Exception = ex,
@@ -203,7 +203,7 @@ private async Task ReceiveMessagesAsync(CancellationToken cancellationToken)
203203
}
204204
finally
205205
{
206-
SetDisconnected(new TransportClosedException(new HttpClientCompletionDetails()));
206+
SetDisconnected(new ClientTransportClosedException(new HttpClientCompletionDetails()));
207207
}
208208
}
209209

src/ModelContextProtocol.Core/Client/StdioClientSessionTransport.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,12 @@ protected override async ValueTask CleanupAsync(Exception? error = null, Cancell
6464
_process,
6565
processRunning: true,
6666
_options.ShutdownTimeout,
67-
beforeDispose: () => SetDisconnected(new TransportClosedException(BuildCompletionDetails(error))));
67+
beforeDispose: () => SetDisconnected(new ClientTransportClosedException(BuildCompletionDetails(error))));
6868
}
6969
catch (Exception ex)
7070
{
7171
LogTransportShutdownFailed(Name, ex);
72-
SetDisconnected(new TransportClosedException(BuildCompletionDetails(error)));
72+
SetDisconnected(new ClientTransportClosedException(BuildCompletionDetails(error)));
7373
}
7474

7575
// And handle cleanup in the base type. SetDisconnected has already been

src/ModelContextProtocol.Core/Client/StreamableHttpClientSessionTransport.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ internal sealed partial class StreamableHttpClientSessionTransport : TransportBa
2525

2626
private string? _negotiatedProtocolVersion;
2727
private Task? _getReceiveTask;
28-
private volatile TransportClosedException? _disconnectError;
28+
private volatile ClientTransportClosedException? _disconnectError;
2929

3030
private readonly SemaphoreSlim _disposeLock = new(1, 1);
3131
private bool _disposed;
@@ -200,7 +200,7 @@ public override async ValueTask DisposeAsync()
200200
{
201201
// _disconnectError is set when the server returns 404 indicating session expiry.
202202
// When null, this is a graceful client-initiated closure (no error).
203-
SetDisconnected(_disconnectError ?? new TransportClosedException(new HttpClientCompletionDetails()));
203+
SetDisconnected(_disconnectError ?? new ClientTransportClosedException(new HttpClientCompletionDetails()));
204204
}
205205
}
206206
}
@@ -491,7 +491,7 @@ private void SetSessionExpired()
491491
{
492492
// Store the error before canceling so DisposeAsync can use it if it races us, especially
493493
// after the call to Cancel below, to invoke SetDisconnected.
494-
_disconnectError = new TransportClosedException(new HttpClientCompletionDetails
494+
_disconnectError = new ClientTransportClosedException(new HttpClientCompletionDetails
495495
{
496496
HttpStatusCode = HttpStatusCode.NotFound,
497497
Exception = new McpException(

src/ModelContextProtocol.Core/McpSessionHandler.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ public McpSessionHandler(
156156
/// <summary>
157157
/// Gets a task that completes when the client session has completed, providing details about the closure.
158158
/// Completion details are resolved from the transport's channel completion exception: if a transport
159-
/// completes its channel with a <see cref="TransportClosedException"/>, the wrapped
159+
/// completes its channel with a <see cref="ClientTransportClosedException"/>, the wrapped
160160
/// <see cref="ClientCompletionDetails"/> is unwrapped. Otherwise, a default instance is returned.
161161
/// </summary>
162162
internal Task<ClientCompletionDetails> CompletionTask =>
@@ -325,7 +325,7 @@ ex is OperationCanceledException &&
325325
}
326326

327327
// Fail any pending requests, as they'll never be satisfied.
328-
// If the transport's channel was completed with a TransportClosedException,
328+
// If the transport's channel was completed with a ClientTransportClosedException,
329329
// propagate it so callers can access the structured completion details.
330330
Exception pendingException =
331331
_transport.MessageReader.Completion is { IsCompleted: true, IsFaulted: true } completion &&
@@ -341,7 +341,7 @@ ex is OperationCanceledException &&
341341

342342
/// <summary>
343343
/// Resolves <see cref="ClientCompletionDetails"/> from the transport's channel completion.
344-
/// If the channel was completed with a <see cref="TransportClosedException"/>, the wrapped
344+
/// If the channel was completed with a <see cref="ClientTransportClosedException"/>, the wrapped
345345
/// details are returned. Otherwise a default instance is created from the completion state.
346346
/// </summary>
347347
private static async Task<ClientCompletionDetails> GetCompletionDetailsAsync(Task channelCompletion)
@@ -351,7 +351,7 @@ private static async Task<ClientCompletionDetails> GetCompletionDetailsAsync(Tas
351351
await channelCompletion.ConfigureAwait(false);
352352
return new ClientCompletionDetails();
353353
}
354-
catch (TransportClosedException tce)
354+
catch (ClientTransportClosedException tce)
355355
{
356356
return tce.Details;
357357
}
@@ -944,7 +944,7 @@ public async ValueTask DisposeAsync()
944944
catch
945945
{
946946
// Ignore exceptions from the message processing loop. It may fault with
947-
// OperationCanceledException on normal shutdown or TransportClosedException
947+
// OperationCanceledException on normal shutdown or ClientTransportClosedException
948948
// when the transport's channel completes with an error.
949949
}
950950
}

tests/ModelContextProtocol.Tests/Client/ClientCompletionDetailsTests.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace ModelContextProtocol.Tests.Client;
55
public class ClientCompletionDetailsTests
66
{
77
[Fact]
8-
public void TransportClosedException_ExposesDetails()
8+
public void ClientTransportClosedException_ExposesDetails()
99
{
1010
var details = new StdioClientCompletionDetails
1111
{
@@ -15,7 +15,7 @@ public void TransportClosedException_ExposesDetails()
1515
Exception = new IOException("process exited"),
1616
};
1717

18-
var exception = new TransportClosedException(details);
18+
var exception = new ClientTransportClosedException(details);
1919

2020
Assert.IsType<StdioClientCompletionDetails>(exception.Details);
2121
var stdioDetails = (StdioClientCompletionDetails)exception.Details;
@@ -27,23 +27,23 @@ public void TransportClosedException_ExposesDetails()
2727
}
2828

2929
[Fact]
30-
public void TransportClosedException_WithNullException_HasDefaultMessage()
30+
public void ClientTransportClosedException_WithNullException_HasDefaultMessage()
3131
{
3232
var details = new ClientCompletionDetails();
3333

34-
var exception = new TransportClosedException(details);
34+
var exception = new ClientTransportClosedException(details);
3535

3636
Assert.Equal("The transport was closed.", exception.Message);
3737
Assert.Null(exception.InnerException);
3838
Assert.Same(details, exception.Details);
3939
}
4040

4141
[Fact]
42-
public void TransportClosedException_IsIOException()
42+
public void ClientTransportClosedException_IsIOException()
4343
{
4444
var details = new ClientCompletionDetails();
45-
IOException exception = new TransportClosedException(details);
46-
Assert.IsType<TransportClosedException>(exception);
45+
IOException exception = new ClientTransportClosedException(details);
46+
Assert.IsType<ClientTransportClosedException>(exception);
4747
}
4848

4949
[Fact]

tests/ModelContextProtocol.Tests/Client/McpClientCreationTests.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -104,16 +104,16 @@ public async Task CreateAsync_WithCapabilitiesOptions(Type transportType)
104104
}
105105

106106
[Fact]
107-
public async Task CreateAsync_TransportChannelClosed_ThrowsTransportClosedException()
107+
public async Task CreateAsync_TransportChannelClosed_ThrowsClientTransportClosedException()
108108
{
109-
// Arrange - transport completes its read channel with TransportClosedException
109+
// Arrange - transport completes its read channel with ClientTransportClosedException
110110
// when the client tries to send the initialize request (simulating a server process
111111
// exit detected by the reader loop). SendMessageAsync returns successfully —
112112
// only the read side fails.
113113
var transport = new ChannelClosedDuringInitTransport();
114114

115115
// Act & Assert
116-
var ex = await Assert.ThrowsAsync<TransportClosedException>(
116+
var ex = await Assert.ThrowsAsync<ClientTransportClosedException>(
117117
() => McpClient.CreateAsync(transport, loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken));
118118

119119
var details = Assert.IsType<StdioClientCompletionDetails>(ex.Details);
@@ -132,8 +132,8 @@ public async Task CreateAsync_TransportChannelClosed_ThrowsTransportClosedExcept
132132
public async Task CreateAsync_SendFails_PropagatesOriginalIOException()
133133
{
134134
// Arrange - transport throws IOException from SendMessageAsync, but the channel
135-
// is not completed with TransportClosedException. The original IOException should
136-
// propagate without being wrapped in TransportClosedException.
135+
// is not completed with ClientTransportClosedException. The original IOException should
136+
// propagate without being wrapped in ClientTransportClosedException.
137137
var transport = new SendFailsDuringInitTransport();
138138

139139
// Act & Assert
@@ -230,7 +230,7 @@ public ValueTask DisposeAsync()
230230
public Task SendMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default)
231231
{
232232
// Simulate the server process exiting: complete the channel with a
233-
// TransportClosedException carrying structured completion details.
233+
// ClientTransportClosedException carrying structured completion details.
234234
// The send itself succeeds — the failure comes from the read side.
235235
var details = new StdioClientCompletionDetails
236236
{
@@ -240,14 +240,14 @@ public Task SendMessageAsync(JsonRpcMessage message, CancellationToken cancellat
240240
Exception = new IOException("MCP server process exited unexpectedly (exit code: 42)"),
241241
};
242242

243-
_channel.Writer.TryComplete(new TransportClosedException(details));
243+
_channel.Writer.TryComplete(new ClientTransportClosedException(details));
244244
return Task.CompletedTask;
245245
}
246246
}
247247

248248
/// <summary>
249249
/// Simulates a transport where SendMessageAsync throws IOException but the channel
250-
/// doesn't carry a TransportClosedException (e.g., a write pipe break without structured details).
250+
/// doesn't carry a ClientTransportClosedException (e.g., a write pipe break without structured details).
251251
/// </summary>
252252
private sealed class SendFailsDuringInitTransport : NopTransport
253253
{

0 commit comments

Comments
 (0)