Skip to content
This repository was archived by the owner on Sep 4, 2025. It is now read-only.

Commit 3781a2b

Browse files
authored
Telemetry fixes for ToolName and passing context. (#741)
* Add documentation about handling errors that occur in a loader. * Remove Activity handling inside CommandFactoryToolLoader. * Update McpRuntime to set the Activity status depending on result from calling a tool. * Fix constructor change * Add tests for telemetry. * Update CHANGELOG * Add tests for Tagging ToolName * Pass in command context of current activity if available. * Add current context to cli
1 parent 9df41d4 commit 3781a2b

File tree

9 files changed

+183
-33
lines changed

9 files changed

+183
-33
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ The Azure MCP Server updates automatically by default whenever a new release com
1010

1111
### Bugs Fixed
1212

13+
- Fixed `ToolExecuted` telemetry activity being created twice. [[#741](https://github.com/Azure/azure-mcp/pull/741)]
14+
1315
### Other Changes
1416

1517
## 0.5.3 (2025-08-05)

core/src/AzureMcp.Cli/Program.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
using System.CommandLine.Builder;
5+
using System.Diagnostics;
56
using AzureMcp.Core.Areas;
67
using AzureMcp.Core.Commands;
78
using AzureMcp.Core.Services.Azure.ResourceGroup;
@@ -95,7 +96,7 @@ private static Parser BuildCommandLineParser(IServiceProvider serviceProvider)
9596

9697
builder.AddMiddleware(async (context, next) =>
9798
{
98-
var commandContext = new CommandContext(serviceProvider);
99+
var commandContext = new CommandContext(serviceProvider, Activity.Current);
99100
var command = context.ParseResult.CommandResult.Command;
100101
if (command.Handler is IBaseCommand baseCommand)
101102
{

core/src/AzureMcp.Core/Areas/Server/Commands/Runtime/McpRuntime.cs

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,44 @@ public async ValueTask<CallToolResult> CallToolHandler(RequestContext<CallToolRe
7373
};
7474
}
7575

76-
return await _toolLoader.CallToolHandler(request!, cancellationToken);
76+
activity?.AddTag(TagName.ToolName, request.Params.Name);
77+
78+
CallToolResult callTool;
79+
try
80+
{
81+
callTool = await _toolLoader.CallToolHandler(request!, cancellationToken);
82+
83+
var isSuccessful = !callTool.IsError.HasValue || !callTool.IsError.Value;
84+
if (isSuccessful)
85+
{
86+
activity?.SetStatus(ActivityStatusCode.Ok);
87+
return callTool;
88+
}
89+
90+
activity?.SetStatus(ActivityStatusCode.Error);
91+
92+
// In the error case, try to get some details about the error.
93+
// Typically all the error information is stored in the first
94+
// content block and is of type TextContentBlock.
95+
var textContent = callTool.Content
96+
.Where(x => x is TextContentBlock)
97+
.Cast<TextContentBlock>()
98+
.FirstOrDefault();
99+
100+
if (textContent != default)
101+
{
102+
activity?.SetTag(TagName.ErrorDetails, textContent.Text);
103+
}
104+
105+
return callTool;
106+
}
107+
catch (Exception ex)
108+
{
109+
activity?.SetStatus(ActivityStatusCode.Error, "Exception occurred calling tool handler")
110+
?.AddTag(TagName.ErrorDetails, ex.Message);
111+
112+
throw;
113+
}
77114
}
78115

79116
/// <summary>
@@ -84,8 +121,21 @@ public async ValueTask<CallToolResult> CallToolHandler(RequestContext<CallToolRe
84121
/// <returns>A result containing the list of available tools.</returns>
85122
public async ValueTask<ListToolsResult> ListToolsHandler(RequestContext<ListToolsRequestParams> request, CancellationToken cancellationToken)
86123
{
87-
using var activity = await _telemetry.StartActivity(nameof(ListToolsHandler), request?.Server?.ClientInfo);
88-
return await _toolLoader.ListToolsHandler(request!, cancellationToken);
124+
using var activity = await _telemetry.StartActivity(ActivityName.ListToolsHandler, request?.Server?.ClientInfo);
125+
126+
try
127+
{
128+
var result = await _toolLoader.ListToolsHandler(request!, cancellationToken);
129+
activity?.SetStatus(ActivityStatusCode.Ok);
130+
131+
return result;
132+
}
133+
catch (Exception ex)
134+
{
135+
activity?.SetStatus(ActivityStatusCode.Error, "Exception occurred calling list tools handler")
136+
?.SetTag(TagName.ErrorDetails, ex.Message);
137+
throw;
138+
}
89139
}
90140

91141
/// <summary>

core/src/AzureMcp.Core/Areas/Server/Commands/ToolLoading/CommandFactoryToolLoader.cs

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,11 @@
22
// Licensed under the MIT License.
33

44
using System.Diagnostics;
5-
using System.Text.Json.Serialization.Metadata;
6-
using AzureMcp.Core.Areas.Server;
75
using AzureMcp.Core.Areas.Server.Models;
8-
using AzureMcp.Core.Areas.Server.Options;
96
using AzureMcp.Core.Commands;
10-
using AzureMcp.Core.Services.Telemetry;
117
using Microsoft.Extensions.Logging;
128
using Microsoft.Extensions.Options;
13-
using ModelContextProtocol;
149
using ModelContextProtocol.Protocol;
15-
using static AzureMcp.Core.Services.Telemetry.TelemetryConstants;
1610

1711
namespace AzureMcp.Core.Areas.Server.Commands.ToolLoading;
1812

@@ -24,13 +18,10 @@ public sealed class CommandFactoryToolLoader(
2418
IServiceProvider serviceProvider,
2519
CommandFactory commandFactory,
2620
IOptions<ToolLoaderOptions> options,
27-
ITelemetryService telemetry,
2821
ILogger<CommandFactoryToolLoader> logger) : IToolLoader
2922
{
3023
private readonly IServiceProvider _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
31-
private readonly CommandFactory _commandFactory = commandFactory ?? throw new ArgumentNullException(nameof(commandFactory));
3224
private readonly IOptions<ToolLoaderOptions> _options = options;
33-
private readonly ITelemetryService _telemetry = telemetry ?? throw new ArgumentNullException(nameof(telemetry));
3425
private IReadOnlyDictionary<string, IBaseCommand> _toolCommands =
3526
(options.Value.Namespace == null || options.Value.Namespace.Length == 0)
3627
? commandFactory.AllCommands
@@ -65,17 +56,13 @@ public ValueTask<ListToolsResult> ListToolsHandler(RequestContext<ListToolsReque
6556
/// <returns>The result of the tool call operation.</returns>
6657
public async ValueTask<CallToolResult> CallToolHandler(RequestContext<CallToolRequestParams> request, CancellationToken cancellationToken)
6758
{
68-
using var activity = await _telemetry.StartActivity(ActivityName.ToolExecuted, request.Server.ClientInfo);
69-
7059
if (request.Params == null)
7160
{
7261
var content = new TextContentBlock
7362
{
7463
Text = "Cannot call tools with null parameters.",
7564
};
7665

77-
activity?.SetStatus(ActivityStatusCode.Error)?.AddTag(TagName.ErrorDetails, content.Text);
78-
7966
return new CallToolResult
8067
{
8168
Content = [content],
@@ -84,8 +71,6 @@ public async ValueTask<CallToolResult> CallToolHandler(RequestContext<CallToolRe
8471
}
8572

8673
var toolName = request.Params.Name;
87-
activity?.AddTag(TagName.ToolName, toolName);
88-
8974
var command = _toolCommands.GetValueOrDefault(toolName);
9075
if (command == null)
9176
{
@@ -94,15 +79,13 @@ public async ValueTask<CallToolResult> CallToolHandler(RequestContext<CallToolRe
9479
Text = $"Could not find command: {request.Params.Name}",
9580
};
9681

97-
activity?.SetStatus(ActivityStatusCode.Error)?.AddTag(TagName.ErrorDetails, content.Text);
98-
9982
return new CallToolResult
10083
{
10184
Content = [content],
10285
IsError = true,
10386
};
10487
}
105-
var commandContext = new CommandContext(_serviceProvider);
88+
var commandContext = new CommandContext(_serviceProvider, Activity.Current);
10689

10790
var realCommand = command.GetCommand();
10891
var commandOptions = realCommand.ParseFromDictionary(request.Params.Arguments);
@@ -128,7 +111,6 @@ public async ValueTask<CallToolResult> CallToolHandler(RequestContext<CallToolRe
128111
catch (Exception ex)
129112
{
130113
_logger.LogError(ex, "An exception occurred running '{Tool}'. ", realCommand.Name);
131-
activity?.SetStatus(ActivityStatusCode.Error)?.AddTag(TagName.ErrorDetails, ex.Message);
132114

133115
throw;
134116
}

core/src/AzureMcp.Core/Areas/Server/Commands/ToolLoading/CompositeToolLoader.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
using System.Threading;
54
using Microsoft.Extensions.Logging;
65
using ModelContextProtocol.Protocol;
7-
using ModelContextProtocol.Server;
86

97
namespace AzureMcp.Core.Areas.Server.Commands.ToolLoading;
108

core/src/AzureMcp.Core/Areas/Server/Commands/ToolLoading/IToolLoader.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ public interface IToolLoader : IAsyncDisposable
2020
ValueTask<ListToolsResult> ListToolsHandler(RequestContext<ListToolsRequestParams> request, CancellationToken cancellationToken);
2121

2222
/// <summary>
23-
/// Handles requests to call a specific tool with the provided parameters.
24-
/// </summary>
23+
/// Handles requests to call a specific tool with the provided parameters. If an error occurs while calling the
24+
/// tool, loaders should return a <see cref="TextContentBlock"/> where the contents are details of the exception.
25+
///
2526
/// <param name="request">The request context containing the tool name and parameters.</param>
2627
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
2728
/// <returns>A result containing the output of the tool invocation.</returns>

core/src/AzureMcp.Core/Services/Telemetry/TelemetryConstants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ internal class TagName
2525
internal class ActivityName
2626
{
2727
public const string CommandExecuted = "CommandExecuted";
28+
public const string ListToolsHandler = "ListToolsHandler";
2829
public const string ToolExecuted = "ToolExecuted";
2930
}
3031
}

0 commit comments

Comments
 (0)