Add client completion notification and details (#1368)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Stephen Toub 2026-03-02 09:17:22 -05:00 • committed by GitHub
commit b965a7eca6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -153,6 +153,15 @@ internal sealed partial class McpSessionHandler : IAsyncDisposable
/// </summary>
public string? NegotiatedProtocolVersion { get; set; }
/// <summary>
/// Gets a task that completes when the client session has completed, providing details about the closure.
/// Completion details are resolved from the transport's channel completion exception: if a transport
/// completes its channel with a <see cref="TransportClosedException"/>, the wrapped
/// <see cref="ClientCompletionDetails"/> is unwrapped. Otherwise, a default instance is returned.
/// </summary>
internal Task<ClientCompletionDetails> CompletionTask =>
field ??= GetCompletionDetailsAsync(_transport.MessageReader.Completion);
/// <summary>
/// Starts processing messages from the transport. This method will block until the transport is disconnected.
/// This is generally started in a background task or thread from the initialization logic of the derived class.
@ -305,6 +314,28 @@ internal sealed partial class McpSessionHandler : IAsyncDisposable
}
}
/// <summary>
/// Resolves <see cref="ClientCompletionDetails"/> from the transport's channel completion.
/// If the channel was completed with a <see cref="TransportClosedException"/>, the wrapped
/// details are returned. Otherwise a default instance is created from the completion state.
/// </summary>
private static async Task<ClientCompletionDetails> GetCompletionDetailsAsync(Task channelCompletion)
{
try
{
await channelCompletion.ConfigureAwait(false);
return new ClientCompletionDetails();
}
catch (TransportClosedException tce)
{
return tce.Details;
}
catch (Exception ex)
{
return new ClientCompletionDetails { Exception = ex };
}
}
private async Task HandleMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken)
{
Histogram<double> durationMetric = _isServer ? s_serverOperationDuration : s_clientOperationDuration;
@ -885,9 +916,11 @@ internal sealed partial class McpSessionHandler : IAsyncDisposable
{
await _messageProcessingTask.ConfigureAwait(false);
}
catch (OperationCanceledException)
catch
{
// Ignore cancellation
// Ignore exceptions from the message processing loop. It may fault with
// OperationCanceledException on normal shutdown or TransportClosedException
// when the transport's channel completes with an error.
}
}