Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/ModelContextProtocol.Core/Client/McpClientImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ public async Task ConnectAsync(CancellationToken cancellationToken = default)

_negotiatedProtocolVersion = initializeResponse.ProtocolVersion;

// Update session handler with the negotiated protocol version for telemetry
_sessionHandler.NegotiatedProtocolVersion = _negotiatedProtocolVersion;

// Send initialized notification
await this.SendNotificationAsync(
NotificationMethods.InitializedNotification,
Expand Down Expand Up @@ -230,6 +233,9 @@ internal void ResumeSession(ResumeClientSessionOptions resumeOptions)
?? _options.ProtocolVersion
?? McpSessionHandler.LatestProtocolVersion;

// Update session handler with the negotiated protocol version for telemetry
_sessionHandler.NegotiatedProtocolVersion = _negotiatedProtocolVersion;

LogClientSessionResumed(_endpointName);
}

Expand Down
39 changes: 26 additions & 13 deletions src/ModelContextProtocol.Core/Diagnostics.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using ModelContextProtocol.Protocol;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Metrics;
using System.Text.Json;
using System.Text.Json.Nodes;
Expand All @@ -12,22 +13,14 @@ internal static class Diagnostics

internal static Meter Meter { get; } = new("Experimental.ModelContextProtocol");

internal static Histogram<double> CreateDurationHistogram(string name, string description, bool longBuckets) =>
Meter.CreateHistogram(name, "s", description, advice: longBuckets ? LongSecondsBucketBoundaries : ShortSecondsBucketBoundaries);
internal static Histogram<double> CreateDurationHistogram(string name, string description) =>
Meter.CreateHistogram(name, "s", description, advice: ExplicitBucketBoundaries);

/// <summary>
/// Follows boundaries from http.server.request.duration/http.client.request.duration
/// ExplicitBucketBoundaries specified in MCP semantic conventions for all MCP metrics.
/// See https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/mcp.md#metrics
/// </summary>
private static InstrumentAdvice<double> ShortSecondsBucketBoundaries { get; } = new()
{
HistogramBucketBoundaries = [0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10],
};

/// <summary>
/// Not based on a standard. Larger bucket sizes for longer lasting operations, e.g. HTTP connection duration.
/// See https://github.com/open-telemetry/semantic-conventions/issues/336
/// </summary>
private static InstrumentAdvice<double> LongSecondsBucketBoundaries { get; } = new()
private static InstrumentAdvice<double> ExplicitBucketBoundaries { get; } = new()
{
HistogramBucketBoundaries = [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 30, 60, 120, 300],
};
Expand Down Expand Up @@ -103,5 +96,25 @@ internal static bool ShouldInstrumentMessage(JsonRpcMessage message) =>
_ => false
};

/// <summary>
/// If outer GenAI instrumentation is already tracing the tool execution,
/// MCP instrumentation SHOULD add MCP-specific attributes to the existing tool execution span instead
/// of creating a new one.
/// </summary>
/// <param name="activity">The outer activity for tool execution, if found.</param>
/// <returns>true if an outer tool execution activity was found and can be reused; false otherwise.</returns>
internal static bool TryGetOuterToolExecutionActivity([NotNullWhen(true)] out Activity? activity)
{
if (Activity.Current is { } currentActivity &&
currentActivity.OperationName.StartsWith("execute_tool ", StringComparison.Ordinal))
{
activity = currentActivity;
return true;
}

activity = null;
return false;
}

internal static ActivityLink[] ActivityLinkFromCurrent() => Activity.Current is null ? [] : [new ActivityLink(Activity.Current.Context)];
}
Loading
Loading