Skip to content
This repository was archived by the owner on Sep 4, 2025. It is now read-only.
Merged
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ The Azure MCP Server updates automatically by default whenever a new release com

### Bugs Fixed

- Fixed `ToolExecuted` telemetry activity being created twice. [[#741](https://github.com/Azure/azure-mcp/pull/741)]

### Other Changes

## 0.5.3 (2025-08-05)
Expand Down
3 changes: 2 additions & 1 deletion core/src/AzureMcp.Cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System.CommandLine.Builder;
using System.Diagnostics;
using AzureMcp.Core.Areas;
using AzureMcp.Core.Commands;
using AzureMcp.Core.Services.Azure.ResourceGroup;
Expand Down Expand Up @@ -95,7 +96,7 @@ private static Parser BuildCommandLineParser(IServiceProvider serviceProvider)

builder.AddMiddleware(async (context, next) =>
{
var commandContext = new CommandContext(serviceProvider);
var commandContext = new CommandContext(serviceProvider, Activity.Current);
var command = context.ParseResult.CommandResult.Command;
if (command.Handler is IBaseCommand baseCommand)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,44 @@ public async ValueTask<CallToolResult> CallToolHandler(RequestContext<CallToolRe
};
}

return await _toolLoader.CallToolHandler(request!, cancellationToken);
activity?.AddTag(TagName.ToolName, request.Params.Name);

CallToolResult callTool;
try
{
callTool = await _toolLoader.CallToolHandler(request!, cancellationToken);

var isSuccessful = !callTool.IsError.HasValue || !callTool.IsError.Value;
if (isSuccessful)
{
activity?.SetStatus(ActivityStatusCode.Ok);
return callTool;
}

activity?.SetStatus(ActivityStatusCode.Error);

// In the error case, try to get some details about the error.
// Typically all the error information is stored in the first
// content block and is of type TextContentBlock.
var textContent = callTool.Content
.Where(x => x is TextContentBlock)
.Cast<TextContentBlock>()
.FirstOrDefault();

if (textContent != default)
{
activity?.SetTag(TagName.ErrorDetails, textContent.Text);
}

return callTool;
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error, "Exception occurred calling tool handler")
?.AddTag(TagName.ErrorDetails, ex.Message);

throw;
}
}

/// <summary>
Expand All @@ -84,8 +121,21 @@ public async ValueTask<CallToolResult> CallToolHandler(RequestContext<CallToolRe
/// <returns>A result containing the list of available tools.</returns>
public async ValueTask<ListToolsResult> ListToolsHandler(RequestContext<ListToolsRequestParams> request, CancellationToken cancellationToken)
{
using var activity = await _telemetry.StartActivity(nameof(ListToolsHandler), request?.Server?.ClientInfo);
return await _toolLoader.ListToolsHandler(request!, cancellationToken);
using var activity = await _telemetry.StartActivity(ActivityName.ListToolsHandler, request?.Server?.ClientInfo);

try
{
var result = await _toolLoader.ListToolsHandler(request!, cancellationToken);
activity?.SetStatus(ActivityStatusCode.Ok);

return result;
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error, "Exception occurred calling list tools handler")
?.SetTag(TagName.ErrorDetails, ex.Message);
throw;
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,11 @@
// Licensed under the MIT License.

using System.Diagnostics;
using System.Text.Json.Serialization.Metadata;
using AzureMcp.Core.Areas.Server;
using AzureMcp.Core.Areas.Server.Models;
using AzureMcp.Core.Areas.Server.Options;
using AzureMcp.Core.Commands;
using AzureMcp.Core.Services.Telemetry;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using ModelContextProtocol;
using ModelContextProtocol.Protocol;
using static AzureMcp.Core.Services.Telemetry.TelemetryConstants;

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

Expand All @@ -24,13 +18,10 @@ public sealed class CommandFactoryToolLoader(
IServiceProvider serviceProvider,
CommandFactory commandFactory,
IOptions<ToolLoaderOptions> options,
ITelemetryService telemetry,
ILogger<CommandFactoryToolLoader> logger) : IToolLoader
{
private readonly IServiceProvider _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
private readonly CommandFactory _commandFactory = commandFactory ?? throw new ArgumentNullException(nameof(commandFactory));
private readonly IOptions<ToolLoaderOptions> _options = options;
private readonly ITelemetryService _telemetry = telemetry ?? throw new ArgumentNullException(nameof(telemetry));
private IReadOnlyDictionary<string, IBaseCommand> _toolCommands =
(options.Value.Namespace == null || options.Value.Namespace.Length == 0)
? commandFactory.AllCommands
Expand Down Expand Up @@ -65,17 +56,13 @@ public ValueTask<ListToolsResult> ListToolsHandler(RequestContext<ListToolsReque
/// <returns>The result of the tool call operation.</returns>
public async ValueTask<CallToolResult> CallToolHandler(RequestContext<CallToolRequestParams> request, CancellationToken cancellationToken)
{
using var activity = await _telemetry.StartActivity(ActivityName.ToolExecuted, request.Server.ClientInfo);

if (request.Params == null)
{
var content = new TextContentBlock
{
Text = "Cannot call tools with null parameters.",
};

activity?.SetStatus(ActivityStatusCode.Error)?.AddTag(TagName.ErrorDetails, content.Text);

return new CallToolResult
{
Content = [content],
Expand All @@ -84,8 +71,6 @@ public async ValueTask<CallToolResult> CallToolHandler(RequestContext<CallToolRe
}

var toolName = request.Params.Name;
activity?.AddTag(TagName.ToolName, toolName);

var command = _toolCommands.GetValueOrDefault(toolName);
if (command == null)
{
Expand All @@ -94,15 +79,13 @@ public async ValueTask<CallToolResult> CallToolHandler(RequestContext<CallToolRe
Text = $"Could not find command: {request.Params.Name}",
};

activity?.SetStatus(ActivityStatusCode.Error)?.AddTag(TagName.ErrorDetails, content.Text);

return new CallToolResult
{
Content = [content],
IsError = true,
};
}
var commandContext = new CommandContext(_serviceProvider);
var commandContext = new CommandContext(_serviceProvider, Activity.Current);

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

throw;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Threading;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ public interface IToolLoader : IAsyncDisposable
ValueTask<ListToolsResult> ListToolsHandler(RequestContext<ListToolsRequestParams> request, CancellationToken cancellationToken);

/// <summary>
/// Handles requests to call a specific tool with the provided parameters.
/// </summary>
/// Handles requests to call a specific tool with the provided parameters. If an error occurs while calling the
/// tool, loaders should return a <see cref="TextContentBlock"/> where the contents are details of the exception.
///
/// <param name="request">The request context containing the tool name and parameters.</param>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <returns>A result containing the output of the tool invocation.</returns>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ internal class TagName
internal class ActivityName
{
public const string CommandExecuted = "CommandExecuted";
public const string ListToolsHandler = "ListToolsHandler";
public const string ToolExecuted = "ToolExecuted";
}
}
Loading
Loading