Skip to content

Commit 64761d0

Browse files
committed
add request header logging at trace level
1 parent a15163d commit 64761d0

File tree

1 file changed

+45
-1
lines changed

1 file changed

+45
-1
lines changed

src/ModelContextProtocol.AspNetCore/StreamableHttpHandler.cs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
namespace ModelContextProtocol.AspNetCore;
2020

21-
internal sealed class StreamableHttpHandler(
21+
internal sealed partial class StreamableHttpHandler(
2222
IOptions<McpServerOptions> mcpServerOptionsSnapshot,
2323
IOptionsFactory<McpServerOptions> mcpServerOptionsFactory,
2424
IOptions<HttpServerTransportOptions> httpServerTransportOptions,
@@ -28,6 +28,20 @@ internal sealed class StreamableHttpHandler(
2828
{
2929
private const string McpSessionIdHeaderName = "Mcp-Session-Id";
3030
private static readonly JsonTypeInfo<JsonRpcError> s_errorTypeInfo = GetRequiredJsonTypeInfo<JsonRpcError>();
31+
32+
private readonly ILogger _logger = loggerFactory.CreateLogger<StreamableHttpHandler>();
33+
34+
// Headers that are safe and relevant to log for MCP over HTTP
35+
private static readonly HashSet<string> SafeHeadersToLog = new(StringComparer.OrdinalIgnoreCase)
36+
{
37+
"Accept",
38+
"Content-Type",
39+
"Content-Length",
40+
"User-Agent",
41+
McpSessionIdHeaderName,
42+
"X-Request-ID",
43+
"X-Correlation-ID"
44+
};
3145

3246
public ConcurrentDictionary<string, HttpMcpSession<StreamableHttpServerTransport>> Sessions { get; } = new(StringComparer.Ordinal);
3347

@@ -37,6 +51,9 @@ internal sealed class StreamableHttpHandler(
3751

3852
public async Task HandlePostRequestAsync(HttpContext context)
3953
{
54+
// Log request headers for debugging
55+
LogHttpRequestHeadersIfEnabled(context);
56+
4057
// The Streamable HTTP spec mandates the client MUST accept both application/json and text/event-stream.
4158
// ASP.NET Core Minimal APIs mostly try to stay out of the business of response content negotiation,
4259
// so we have to do this manually. The spec doesn't mandate that servers MUST reject these requests,
@@ -83,6 +100,9 @@ await WriteJsonRpcErrorAsync(context,
83100

84101
public async Task HandleGetRequestAsync(HttpContext context)
85102
{
103+
// Log request headers for debugging
104+
LogHttpRequestHeadersIfEnabled(context);
105+
86106
if (!context.Request.GetTypedHeaders().Accept.Any(MatchesTextEventStreamMediaType))
87107
{
88108
await WriteJsonRpcErrorAsync(context,
@@ -118,6 +138,9 @@ await WriteJsonRpcErrorAsync(context,
118138

119139
public async Task HandleDeleteRequestAsync(HttpContext context)
120140
{
141+
// Log request headers for debugging
142+
LogHttpRequestHeadersIfEnabled(context);
143+
121144
var sessionId = context.Request.Headers[McpSessionIdHeaderName].ToString();
122145
if (Sessions.TryRemove(sessionId, out var session))
123146
{
@@ -336,6 +359,27 @@ private static bool MatchesApplicationJsonMediaType(MediaTypeHeaderValue acceptH
336359
private static bool MatchesTextEventStreamMediaType(MediaTypeHeaderValue acceptHeaderValue)
337360
=> acceptHeaderValue.MatchesMediaType("text/event-stream");
338361

362+
private void LogHttpRequestHeadersIfEnabled(HttpContext context)
363+
{
364+
if (_logger.IsEnabled(LogLevel.Trace))
365+
{
366+
var safeHeaders = new Dictionary<string, string>();
367+
368+
foreach (var header in context.Request.Headers)
369+
{
370+
if (SafeHeadersToLog.Contains(header.Key))
371+
{
372+
safeHeaders[header.Key] = header.Value.ToString();
373+
}
374+
}
375+
376+
LogHttpRequestHeaders(context.Request.Method, context.Request.Path, safeHeaders);
377+
}
378+
}
379+
380+
[LoggerMessage(Level = LogLevel.Trace, Message = "HTTP {Method} {Path} - Headers: {Headers}")]
381+
private partial void LogHttpRequestHeaders(string method, string path, Dictionary<string, string> headers);
382+
339383
private sealed class HttpDuplexPipe(HttpContext context) : IDuplexPipe
340384
{
341385
public PipeReader Input => context.Request.BodyReader;

0 commit comments

Comments
 (0)