Skip to content

Commit d52a7b1

Browse files
Copilotstephentoub
andauthored
Align telemetry with OpenTelemetry MCP semantic conventions (#1139)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
1 parent 89b67b0 commit d52a7b1

File tree

5 files changed

+226
-77
lines changed

5 files changed

+226
-77
lines changed

src/ModelContextProtocol.Core/Client/McpClientImpl.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ public async Task ConnectAsync(CancellationToken cancellationToken = default)
187187

188188
_negotiatedProtocolVersion = initializeResponse.ProtocolVersion;
189189

190+
// Update session handler with the negotiated protocol version for telemetry
191+
_sessionHandler.NegotiatedProtocolVersion = _negotiatedProtocolVersion;
192+
190193
// Send initialized notification
191194
await this.SendNotificationAsync(
192195
NotificationMethods.InitializedNotification,
@@ -230,6 +233,9 @@ internal void ResumeSession(ResumeClientSessionOptions resumeOptions)
230233
?? _options.ProtocolVersion
231234
?? McpSessionHandler.LatestProtocolVersion;
232235

236+
// Update session handler with the negotiated protocol version for telemetry
237+
_sessionHandler.NegotiatedProtocolVersion = _negotiatedProtocolVersion;
238+
233239
LogClientSessionResumed(_endpointName);
234240
}
235241

src/ModelContextProtocol.Core/Diagnostics.cs

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using ModelContextProtocol.Protocol;
22
using System.Diagnostics;
3+
using System.Diagnostics.CodeAnalysis;
34
using System.Diagnostics.Metrics;
45
using System.Text.Json;
56
using System.Text.Json.Nodes;
@@ -12,22 +13,14 @@ internal static class Diagnostics
1213

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

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

1819
/// <summary>
19-
/// Follows boundaries from http.server.request.duration/http.client.request.duration
20+
/// ExplicitBucketBoundaries specified in MCP semantic conventions for all MCP metrics.
21+
/// See https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/mcp.md#metrics
2022
/// </summary>
21-
private static InstrumentAdvice<double> ShortSecondsBucketBoundaries { get; } = new()
22-
{
23-
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],
24-
};
25-
26-
/// <summary>
27-
/// Not based on a standard. Larger bucket sizes for longer lasting operations, e.g. HTTP connection duration.
28-
/// See https://github.com/open-telemetry/semantic-conventions/issues/336
29-
/// </summary>
30-
private static InstrumentAdvice<double> LongSecondsBucketBoundaries { get; } = new()
23+
private static InstrumentAdvice<double> ExplicitBucketBoundaries { get; } = new()
3124
{
3225
HistogramBucketBoundaries = [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 30, 60, 120, 300],
3326
};
@@ -103,5 +96,25 @@ internal static bool ShouldInstrumentMessage(JsonRpcMessage message) =>
10396
_ => false
10497
};
10598

99+
/// <summary>
100+
/// If outer GenAI instrumentation is already tracing the tool execution,
101+
/// MCP instrumentation SHOULD add MCP-specific attributes to the existing tool execution span instead
102+
/// of creating a new one.
103+
/// </summary>
104+
/// <param name="activity">The outer activity for tool execution, if found.</param>
105+
/// <returns>true if an outer tool execution activity was found and can be reused; false otherwise.</returns>
106+
internal static bool TryGetOuterToolExecutionActivity([NotNullWhen(true)] out Activity? activity)
107+
{
108+
if (Activity.Current is { } currentActivity &&
109+
currentActivity.OperationName.StartsWith("execute_tool ", StringComparison.Ordinal))
110+
{
111+
activity = currentActivity;
112+
return true;
113+
}
114+
115+
activity = null;
116+
return false;
117+
}
118+
106119
internal static ActivityLink[] ActivityLinkFromCurrent() => Activity.Current is null ? [] : [new ActivityLink(Activity.Current.Context)];
107120
}

0 commit comments

Comments
 (0)