Skip to content

Commit 5084a79

Browse files
Copilotstephentoub
andauthored
Add ILoggerFactory to StreamableHttpServerTransport (#1213)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> Co-authored-by: Stephen Toub <stoub@microsoft.com>
1 parent e47f884 commit 5084a79

3 files changed

Lines changed: 33 additions & 6 deletions

File tree

src/ModelContextProtocol.AspNetCore/StreamableHttpHandler.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ private async ValueTask<StreamableHttpSession> StartNewSessionAsync(HttpContext
238238
if (!HttpServerTransportOptions.Stateless)
239239
{
240240
sessionId = MakeNewSessionId();
241-
transport = new()
241+
transport = new(loggerFactory)
242242
{
243243
SessionId = sessionId,
244244
FlowExecutionContextFromRequests = !HttpServerTransportOptions.PerSessionExecutionContext,
@@ -252,7 +252,7 @@ private async ValueTask<StreamableHttpSession> StartNewSessionAsync(HttpContext
252252
// If in the future we support resuming stateless requests, we should populate
253253
// the event stream store and retry interval here as well.
254254
sessionId = "";
255-
transport = new()
255+
transport = new(loggerFactory)
256256
{
257257
Stateless = true,
258258
};

src/ModelContextProtocol.Core/Server/StreamableHttpPostTransport.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Microsoft.Extensions.Logging;
12
using ModelContextProtocol.Protocol;
23
using System.Diagnostics;
34
using System.Net.ServerSentEvents;
@@ -10,7 +11,11 @@ namespace ModelContextProtocol.Server;
1011
/// Handles processing the request/response body pairs for the Streamable HTTP transport.
1112
/// This is typically used via <see cref="JsonRpcMessageContext.RelatedTransport"/>.
1213
/// </summary>
13-
internal sealed class StreamableHttpPostTransport(StreamableHttpServerTransport parentTransport, Stream responseStream, CancellationToken sessionCancellationToken) : ITransport
14+
internal sealed partial class StreamableHttpPostTransport(
15+
StreamableHttpServerTransport parentTransport,
16+
Stream responseStream,
17+
CancellationToken sessionCancellationToken,
18+
ILogger logger) : ITransport
1419
{
1520
private readonly SemaphoreSlim _messageLock = new(1, 1);
1621
private readonly TaskCompletionSource<bool> _httpResponseTcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
@@ -199,7 +204,14 @@ async Task HandleStoreStreamDisposalAsync(Task streamTask)
199204
{
200205
using var _ = await _messageLock.LockAsync().ConfigureAwait(false);
201206

202-
await _storeSseWriter!.DisposeAsync().ConfigureAwait(false);
207+
try
208+
{
209+
await _storeSseWriter!.DisposeAsync().ConfigureAwait(false);
210+
}
211+
catch (Exception ex)
212+
{
213+
LogStoreStreamDisposalFailed(ex);
214+
}
203215
}
204216
}
205217
}
@@ -222,4 +234,7 @@ public async ValueTask DisposeAsync()
222234
// Don't dispose the event stream writer here, as we may continue to write to the event store
223235
// after disposal if there are pending messages.
224236
}
237+
238+
[LoggerMessage(Level = LogLevel.Warning, Message = "Failed to dispose SSE event stream writer.")]
239+
private partial void LogStoreStreamDisposalFailed(Exception exception);
225240
}

src/ModelContextProtocol.Core/Server/StreamableHttpServerTransport.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using Microsoft.Extensions.Logging;
2+
using Microsoft.Extensions.Logging.Abstractions;
13
using ModelContextProtocol.Protocol;
24
using System.Diagnostics;
35
using System.Diagnostics.CodeAnalysis;
@@ -21,7 +23,7 @@ namespace ModelContextProtocol.Server;
2123
/// such as when streaming completion results or providing progress updates during long-running operations.
2224
/// </para>
2325
/// </remarks>
24-
public sealed class StreamableHttpServerTransport : ITransport
26+
public sealed partial class StreamableHttpServerTransport : ITransport
2527
{
2628
/// <summary>
2729
/// The stream ID used for unsolicited messages sent via the standalone GET SSE stream.
@@ -35,13 +37,23 @@ public sealed class StreamableHttpServerTransport : ITransport
3537
});
3638
private readonly CancellationTokenSource _transportDisposedCts = new();
3739
private readonly SemaphoreSlim _unsolicitedMessageLock = new(1, 1);
40+
private readonly ILogger _logger;
3841

3942
private SseEventWriter? _httpSseWriter;
4043
private ISseEventStreamWriter? _storeSseWriter;
4144
private TaskCompletionSource<bool>? _httpResponseTcs;
4245
private bool _getHttpRequestStarted;
4346
private bool _getHttpResponseCompleted;
4447

48+
/// <summary>
49+
/// Initializes a new instance of the <see cref="StreamableHttpServerTransport"/> class.
50+
/// </summary>
51+
/// <param name="loggerFactory">Optional logger factory used for logging employed by the transport.</param>
52+
public StreamableHttpServerTransport(ILoggerFactory? loggerFactory = null)
53+
{
54+
_logger = loggerFactory?.CreateLogger<StreamableHttpServerTransport>() ?? NullLogger<StreamableHttpServerTransport>.Instance;
55+
}
56+
4557
/// <inheritdoc/>
4658
public string? SessionId { get; init; }
4759

@@ -161,7 +173,7 @@ public async Task<bool> HandlePostRequestAsync(JsonRpcMessage message, Stream re
161173
Throw.IfNull(message);
162174
Throw.IfNull(responseStream);
163175

164-
var postTransport = new StreamableHttpPostTransport(this, responseStream, _transportDisposedCts.Token);
176+
var postTransport = new StreamableHttpPostTransport(this, responseStream, _transportDisposedCts.Token, _logger);
165177
using var postCts = CancellationTokenSource.CreateLinkedTokenSource(_transportDisposedCts.Token, cancellationToken);
166178
await using (postTransport.ConfigureAwait(false))
167179
{

0 commit comments

Comments
 (0)