77using ModelContextProtocol . Utils ;
88using ModelContextProtocol . Utils . Json ;
99using System . Collections . Concurrent ;
10+ using System . Diagnostics ;
11+ using System . Diagnostics . Metrics ;
1012using System . Text . Json ;
1113
1214namespace ModelContextProtocol . Shared ;
@@ -16,6 +18,13 @@ namespace ModelContextProtocol.Shared;
1618/// </summary>
1719internal sealed class McpSession : IDisposable
1820{
21+ private static readonly ActivitySource s_activitySource = new ( "ModelContextProtocol" ) ;
22+ private static readonly Meter s_meter = new ( "ModelContextProtocol" ) ;
23+ private static readonly Histogram < double > s_operationDurationHistogram = s_meter . CreateHistogram < double > (
24+ "modelcontextprotocol.operation.duration" ,
25+ "s" ,
26+ "Measures the duration of an operation in seconds." ) ;
27+
1928 private readonly ITransport _transport ;
2029 private readonly RequestHandlers _requestHandlers ;
2130 private readonly NotificationHandlers _notificationHandlers ;
@@ -152,23 +161,89 @@ await _transport.SendMessageAsync(new JsonRpcError
152161
153162 private async Task HandleMessageAsync ( IJsonRpcMessage message , CancellationToken cancellationToken )
154163 {
155- switch ( message )
164+ using Activity ? activity = s_activitySource . StartActivity ( "HandlingMessage" ) ;
165+ TagList tags = default ;
166+
167+ Stopwatch ? measuring = s_operationDurationHistogram . Enabled ? Stopwatch . StartNew ( ) : null ;
168+ bool addTags = activity is not null || measuring is not null ;
169+
170+ try
156171 {
157- case JsonRpcRequest request :
158- await HandleRequest ( request , cancellationToken ) . ConfigureAwait ( false ) ;
159- break ;
172+ if ( addTags )
173+ {
174+ tags . Add ( "mcp.session.id" , _id ) ;
175+ }
176+
177+ switch ( message )
178+ {
179+ case JsonRpcRequest request :
180+ if ( addTags )
181+ {
182+ tags . Add ( "mcp.request.id" , request . Id . ToString ( ) ) ;
183+ tags . Add ( "mcp.request.method" , request . Method ) ;
184+
185+ if ( request . Params is JsonElement je )
186+ {
187+ switch ( request . Method )
188+ {
189+ case RequestMethods . ToolsCall :
190+ case RequestMethods . PromptsGet :
191+ if ( je . TryGetProperty ( "name" , out var prop ) && prop . ValueKind == JsonValueKind . String )
192+ {
193+ tags . Add ( "mcp.request.params.name" , prop . GetString ( ) ) ;
194+ }
195+ break ;
196+
197+ case RequestMethods . ResourcesRead :
198+ if ( je . TryGetProperty ( "uri" , out prop ) && prop . ValueKind == JsonValueKind . String )
199+ {
200+ tags . Add ( "mcp.request.params.uri" , prop . GetString ( ) ) ;
201+ }
202+ break ;
203+ }
204+ }
205+ }
206+
207+ await HandleRequest ( request , cancellationToken ) . ConfigureAwait ( false ) ;
208+ break ;
160209
161- case IJsonRpcMessageWithId messageWithId :
162- HandleMessageWithId ( message , messageWithId ) ;
163- break ;
210+ case JsonRpcNotification notification :
211+ if ( addTags )
212+ {
213+ tags . Add ( "mcp.notification.method" , notification . Method ) ;
214+ }
215+
216+ await HandleNotification ( notification ) . ConfigureAwait ( false ) ;
217+ break ;
164218
165- case JsonRpcNotification notification :
166- await HandleNotification ( notification ) . ConfigureAwait ( false ) ;
167- break ;
219+ case IJsonRpcMessageWithId messageWithId :
220+ HandleMessageWithId ( message , messageWithId ) ;
221+ break ;
168222
169- default :
170- _logger . EndpointHandlerUnexpectedMessageType ( EndpointName , message . GetType ( ) . Name ) ;
171- break ;
223+ default :
224+ _logger . EndpointHandlerUnexpectedMessageType ( EndpointName , message . GetType ( ) . Name ) ;
225+ break ;
226+ }
227+ }
228+ catch ( Exception e ) when ( addTags )
229+ {
230+ tags . Add ( "error.type" , e . GetType ( ) . FullName ) ;
231+ throw ;
232+ }
233+ finally
234+ {
235+ if ( activity is not null )
236+ {
237+ foreach ( var tag in tags )
238+ {
239+ activity . AddTag ( tag . Key , tag . Value ) ;
240+ }
241+ }
242+
243+ if ( measuring is not null )
244+ {
245+ s_operationDurationHistogram . Record ( measuring . Elapsed . TotalSeconds , tags ) ;
246+ }
172247 }
173248 }
174249
@@ -264,12 +339,20 @@ public async Task<TResult> SendRequestAsync<TResult>(JsonRpcRequest request, Can
264339 throw new McpClientException ( "Transport is not connected" ) ;
265340 }
266341
342+ using Activity ? activity = s_activitySource . StartActivity ( "SendingRequest" ) ;
343+
267344 // Set request ID
268345 if ( request . Id . IsDefault )
269346 {
270347 request . Id = new RequestId ( $ "{ _id } -{ Interlocked . Increment ( ref _nextRequestId ) } ") ;
271348 }
272349
350+ if ( activity is not null )
351+ {
352+ activity . SetTag ( "mcp.request.id" , request . Id . ToString ( ) ) ;
353+ activity . SetTag ( "mcp.request.method" , request . Method ) ;
354+ }
355+
273356 var tcs = new TaskCompletionSource < IJsonRpcMessage > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
274357 _pendingRequests [ request . Id ] = tcs ;
275358
@@ -319,6 +402,11 @@ public async Task<TResult> SendRequestAsync<TResult>(JsonRpcRequest request, Can
319402 _logger . RequestInvalidResponseType ( EndpointName , request . Method ) ;
320403 throw new McpClientException ( "Invalid response type" ) ;
321404 }
405+ catch ( Exception ex ) when ( activity is not null )
406+ {
407+ activity . AddTag ( "error.type" , ex . GetType ( ) . FullName ) ;
408+ throw ;
409+ }
322410 finally
323411 {
324412 _pendingRequests . TryRemove ( request . Id , out _ ) ;
@@ -335,6 +423,8 @@ public async Task SendMessageAsync(IJsonRpcMessage message, CancellationToken ca
335423 throw new McpClientException ( "Transport is not connected" ) ;
336424 }
337425
426+ using Activity ? activity = s_activitySource . StartActivity ( "SendingMessage" ) ;
427+
338428 if ( _logger . IsEnabled ( LogLevel . Debug ) )
339429 {
340430 _logger . SendingMessage ( EndpointName , JsonSerializer . Serialize ( message , _jsonOptions . GetTypeInfo < IJsonRpcMessage > ( ) ) ) ;
0 commit comments