@@ -95,6 +95,7 @@ internal static bool SupportsNaturalOutputSchemas(string? protocolVersion)
9595 /// </summary>
9696 private readonly ConcurrentDictionary < RequestId , CancellationTokenSource > _handlingRequests = new ( ) ;
9797 private readonly ILogger _logger ;
98+ private readonly Func < Exception , string > ? _exceptionSummarizer ;
9899
99100 // This _sessionId is solely used to identify the session in telemetry and logs.
100101 private readonly string _sessionId = Guid . NewGuid ( ) . ToString ( "N" ) ;
@@ -115,6 +116,10 @@ internal static bool SupportsNaturalOutputSchemas(string? protocolVersion)
115116 /// <param name="incomingMessageFilter">A filter that wraps incoming message processing. Takes the next handler and returns a wrapped handler. If null, a passthrough filter is used.</param>
116117 /// <param name="outgoingMessageFilter">A filter that wraps outgoing message processing. Takes the next handler and returns a wrapped handler. If null, a passthrough filter is used.</param>
117118 /// <param name="logger">The logger.</param>
119+ /// <param name="exceptionSummarizer">
120+ /// An optional callback that produces a sanitized description of an exception. When non-<see langword="null"/>,
121+ /// exception logging callsites log that description instead of the raw <see cref="Exception"/>.
122+ /// </param>
118123 public McpSessionHandler (
119124 bool isServer ,
120125 ITransport transport ,
@@ -123,7 +128,8 @@ public McpSessionHandler(
123128 NotificationHandlers notificationHandlers ,
124129 JsonRpcMessageFilter ? incomingMessageFilter ,
125130 JsonRpcMessageFilter ? outgoingMessageFilter ,
126- ILogger logger )
131+ ILogger logger ,
132+ Func < Exception , string > ? exceptionSummarizer = null )
127133 {
128134 Throw . IfNull ( transport ) ;
129135
@@ -144,6 +150,7 @@ public McpSessionHandler(
144150 _incomingMessageFilter = incomingMessageFilter ?? ( next => next ) ;
145151 _outgoingMessageFilter = outgoingMessageFilter ?? ( next => next ) ;
146152 _logger = logger ;
153+ _exceptionSummarizer = exceptionSummarizer ;
147154
148155 // ping was removed in the 2026-07-28 protocol revision (SEP-2575). On the 2026-07-28 or later version,
149156 // return MethodNotFound; on an older version, the per-spec behavior is to always answer
@@ -323,14 +330,7 @@ ex is OperationCanceledException &&
323330 }
324331 else if ( ex is not OperationCanceledException )
325332 {
326- if ( _logger . IsEnabled ( LogLevel . Trace ) )
327- {
328- LogMessageHandlerExceptionSensitive ( EndpointName , message . GetType ( ) . Name , JsonSerializer . Serialize ( message , McpJsonUtilities . JsonContext . Default . JsonRpcMessage ) , ex ) ;
329- }
330- else
331- {
332- LogMessageHandlerException ( EndpointName , message . GetType ( ) . Name , ex ) ;
333- }
333+ LogMessageHandlerFailure ( message , ex ) ;
334334 }
335335 }
336336 finally
@@ -470,7 +470,7 @@ await _incomingMessageFilter(async (msg, ct) =>
470470 }
471471 catch ( Exception ex )
472472 {
473- LogRequestHandlerException ( EndpointName , request . Method , GetElapsed ( requestStartingTimestamp ) . TotalMilliseconds , ex ) ;
473+ LogRequestHandlerFailure ( EndpointName , request . Method , GetElapsed ( requestStartingTimestamp ) . TotalMilliseconds , ex ) ;
474474 throw ;
475475 }
476476
@@ -1280,9 +1280,68 @@ internal static McpProtocolException CreateRemoteProtocolExceptionFromError(Json
12801280 [ LoggerMessage ( Level = LogLevel . Information , Message = "{EndpointName} method '{Method}' request handler completed in {ElapsedMilliseconds}ms." ) ]
12811281 private partial void LogRequestHandlerCompleted ( string endpointName , string method , double elapsedMilliseconds ) ;
12821282
1283+ /// <summary>
1284+ /// Logs a failed request handler, substituting a sanitized description for the raw exception when a
1285+ /// summarizer is configured. The summarizer only runs when the event's level is enabled, matching the
1286+ /// enabled-check the generated logging methods perform internally.
1287+ /// </summary>
1288+ private void LogRequestHandlerFailure ( string endpointName , string method , double elapsedMilliseconds , Exception exception )
1289+ {
1290+ if ( _exceptionSummarizer is not null &&
1291+ _logger. IsEnabled( LogLevel . Warning ) &&
1292+ ExceptionSummaryHelper . TrySummarize ( _exceptionSummarizer , exception , out string ? exceptionSummary ) )
1293+ {
1294+ LogRequestHandlerExceptionSummarized ( endpointName , method , elapsedMilliseconds , exceptionSummary ) ;
1295+ }
1296+ else
1297+ {
1298+ LogRequestHandlerException ( endpointName , method , elapsedMilliseconds , exception ) ;
1299+ }
1300+ }
1301+
1302+ /// <summary>
1303+ /// Logs a failed message handler. The trace-vs-warning selection is unchanged from the non-summarizing
1304+ /// path; only the payload differs. The summarizer runs only when the selected event's level is enabled.
1305+ /// </summary>
1306+ private void LogMessageHandlerFailure( JsonRpcMessage message , Exception exception )
1307+ {
1308+ string messageType = message . GetType ( ) . Name ;
1309+
1310+ if ( _logger . IsEnabled ( LogLevel . Trace ) )
1311+ {
1312+ if ( _exceptionSummarizer is not null &&
1313+ ExceptionSummaryHelper . TrySummarize ( _exceptionSummarizer , exception , out string ? exceptionSummary ) )
1314+ {
1315+ LogMessageHandlerExceptionSensitiveSummarized( EndpointName , messageType , exceptionSummary , JsonSerializer . Serialize ( message , McpJsonUtilities . JsonContext . Default . JsonRpcMessage ) ) ;
1316+ }
1317+ else
1318+ {
1319+ LogMessageHandlerExceptionSensitive ( EndpointName , messageType , JsonSerializer . Serialize ( message , McpJsonUtilities . JsonContext . Default . JsonRpcMessage ) , exception ) ;
1320+ }
1321+ }
1322+ else if ( _exceptionSummarizer is not null &&
1323+ _logger . IsEnabled ( LogLevel . Warning ) &&
1324+ ExceptionSummaryHelper . TrySummarize ( _exceptionSummarizer , exception , out string ? exceptionSummary ) )
1325+ {
1326+ LogMessageHandlerExceptionSummarized( EndpointName , messageType , exceptionSummary ) ;
1327+ }
1328+ else
1329+ {
1330+ LogMessageHandlerException( EndpointName , messageType , exception ) ;
1331+ }
1332+ }
1333+
1334+ // Each summarized variant names its raw counterpart as its EventName so the pair emits one EventId and
1335+ // one event name, keeping consumers that filter on EventId working when a summarizer is configured. The
1336+ // generator derives the numeric id from the event name, which defaults to the method name, so only the
1337+ // summarized variant declares EventName: declaring it on both trips SYSLIB1025.
1338+
12831339 [ LoggerMessage ( Level = LogLevel . Warning , Message = "{EndpointName} method '{Method}' request handler failed in {ElapsedMilliseconds}ms." ) ]
12841340 private partial void LogRequestHandlerException( string endpointName , string method , double elapsedMilliseconds , Exception exception ) ;
12851341
1342+ [ LoggerMessage ( Level = LogLevel . Warning , EventName = nameof ( LogRequestHandlerException ) , Message = "{EndpointName} method '{Method}' request handler failed in {ElapsedMilliseconds}ms: {ExceptionSummary}." ) ]
1343+ private partial void LogRequestHandlerExceptionSummarized( string endpointName , string method , double elapsedMilliseconds , string exceptionSummary ) ;
1344+
12861345 [ LoggerMessage ( Level = LogLevel . Information , Message = "{EndpointName} received request for unknown request ID '{RequestId}'." ) ]
12871346 private partial void LogNoRequestFoundForMessageWithId( string endpointName , RequestId requestId ) ;
12881347
@@ -1313,9 +1372,15 @@ internal static McpProtocolException CreateRemoteProtocolExceptionFromError(Json
13131372 [ LoggerMessage ( Level = LogLevel . Warning , Message = "{EndpointName} message handler {MessageType} failed." ) ]
13141373 private partial void LogMessageHandlerException( string endpointName , string messageType , Exception exception ) ;
13151374
1375+ [ LoggerMessage ( Level = LogLevel . Warning , EventName = nameof ( LogMessageHandlerException ) , Message = "{EndpointName} message handler {MessageType} failed: {ExceptionSummary}." ) ]
1376+ private partial void LogMessageHandlerExceptionSummarized( string endpointName , string messageType , string exceptionSummary ) ;
1377+
13161378 [ LoggerMessage ( Level = LogLevel . Trace , Message = "{EndpointName} message handler {MessageType} failed. Message: '{Message}'." ) ]
13171379 private partial void LogMessageHandlerExceptionSensitive( string endpointName , string messageType , string message , Exception exception ) ;
13181380
1381+ [ LoggerMessage ( Level = LogLevel . Trace , EventName = nameof ( LogMessageHandlerExceptionSensitive ) , Message = "{EndpointName} message handler {MessageType} failed: {ExceptionSummary}. Message: '{Message}'." ) ]
1382+ private partial void LogMessageHandlerExceptionSensitiveSummarized( string endpointName , string messageType , string exceptionSummary , string message ) ;
1383+
13191384 [ LoggerMessage ( Level = LogLevel . Warning , Message = "{EndpointName} received unexpected {MessageType} message type." ) ]
13201385 private partial void LogEndpointHandlerUnexpectedMessageType( string endpointName , string messageType ) ;
13211386
0 commit comments