From a48c57837ce72b377c16e118090db3055af7083a Mon Sep 17 00:00:00 2001 From: Connie Yau Date: Tue, 4 Nov 2025 14:28:42 -0800 Subject: [PATCH 01/26] Change AzureMcpServerConfiguration to McpServerConfiguration. Add default required keywords for runtime option checks. --- .../AzureMcpServerConfiguration.cs | 38 +++++++++++++++++-- .../src/Extensions/OpenTelemetryExtensions.cs | 2 +- .../Services/Telemetry/TelemetryService.cs | 2 +- .../Telemetry/TelemetryServiceTests.cs | 30 +++++++-------- 4 files changed, 52 insertions(+), 20 deletions(-) diff --git a/core/Azure.Mcp.Core/src/Configuration/AzureMcpServerConfiguration.cs b/core/Azure.Mcp.Core/src/Configuration/AzureMcpServerConfiguration.cs index aa2c922a33..0dbe82c9b5 100644 --- a/core/Azure.Mcp.Core/src/Configuration/AzureMcpServerConfiguration.cs +++ b/core/Azure.Mcp.Core/src/Configuration/AzureMcpServerConfiguration.cs @@ -1,15 +1,47 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.ComponentModel.DataAnnotations; + namespace Azure.Mcp.Core.Configuration; +/// +/// Configuration settings for the MCP server. +/// public class AzureMcpServerConfiguration { - public const string DefaultName = "Azure.Mcp.Server"; + /// + /// The default prefix for the MCP server commands and help menus. + /// + [Required] + public required string Prefix { get; set; } + + /// + /// The name of the MCP server. (i.e. Azure.Mcp.Server) + /// + [Required] + public required string Name { get; set; } - public string Name { get; set; } = DefaultName; + /// + /// The display name of the MCP server. + /// + [Required] + public required string DisplayName { get; set; } - public string Version { get; set; } = "1.0.0-beta"; + /// + /// The version of the MCP server. + /// + [Required] + public required string Version { get; set; } + /// + /// Indicates whether telemetry is enabled. + /// + [Required] public bool IsTelemetryEnabled { get; set; } = true; + + /// + /// The application insights connection string to use if is true. + /// + public string? ApplicationInsightsConnectionString { get; set; } } diff --git a/core/Azure.Mcp.Core/src/Extensions/OpenTelemetryExtensions.cs b/core/Azure.Mcp.Core/src/Extensions/OpenTelemetryExtensions.cs index cbb7fc19fa..b8d8394987 100644 --- a/core/Azure.Mcp.Core/src/Extensions/OpenTelemetryExtensions.cs +++ b/core/Azure.Mcp.Core/src/Extensions/OpenTelemetryExtensions.cs @@ -98,7 +98,7 @@ private static void EnableAzureMonitor(this IServiceCollection services) services.ConfigureOpenTelemetryTracerProvider((sp, builder) => { - var serverConfig = sp.GetRequiredService>(); + var serverConfig = sp.GetRequiredService>(); if (!serverConfig.Value.IsTelemetryEnabled) { return; diff --git a/core/Azure.Mcp.Core/src/Services/Telemetry/TelemetryService.cs b/core/Azure.Mcp.Core/src/Services/Telemetry/TelemetryService.cs index 9f67329095..9eb7e8034b 100644 --- a/core/Azure.Mcp.Core/src/Services/Telemetry/TelemetryService.cs +++ b/core/Azure.Mcp.Core/src/Services/Telemetry/TelemetryService.cs @@ -35,7 +35,7 @@ internal class TelemetryService : ITelemetryService internal ActivitySource Parent { get; } public TelemetryService(IMachineInformationProvider informationProvider, - IOptions options, + IOptions options, IOptions? serverOptions, ILogger logger) { diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Services/Telemetry/TelemetryServiceTests.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Services/Telemetry/TelemetryServiceTests.cs index b75dab5126..0062d4df0b 100644 --- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Services/Telemetry/TelemetryServiceTests.cs +++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Services/Telemetry/TelemetryServiceTests.cs @@ -16,20 +16,20 @@ public class TelemetryServiceTests { private const string TestDeviceId = "test-device-id"; private const string TestMacAddressHash = "test-hash"; - private readonly AzureMcpServerConfiguration _testConfiguration = new() + private readonly McpServerConfiguration _testConfiguration = new() { Name = "TestService", Version = "1.0.0", IsTelemetryEnabled = true }; - private readonly IOptions _mockOptions; + private readonly IOptions _mockOptions; private readonly IMachineInformationProvider _mockInformationProvider; private readonly IOptions _mockServiceOptions; private readonly ILogger _logger; public TelemetryServiceTests() { - _mockOptions = Substitute.For>(); + _mockOptions = Substitute.For>(); _mockOptions.Value.Returns(_testConfiguration); _mockServiceOptions = Substitute.For>(); @@ -98,8 +98,8 @@ public void Constructor_WithNullOptions_ShouldThrowArgumentNullException() public void Constructor_WithNullConfiguration_ShouldThrowNullReferenceException() { // Arrange - var mockOptions = Substitute.For>(); - mockOptions.Value.Returns((AzureMcpServerConfiguration)null!); + var mockOptions = Substitute.For>(); + mockOptions.Value.Returns((McpServerConfiguration)null!); // Act & Assert Assert.Throws(() => new TelemetryService(_mockInformationProvider, mockOptions, _mockServiceOptions, _logger)); @@ -145,14 +145,14 @@ public void GetDefaultTags_ReturnsEmptyOnDisabled() public async Task StartActivity_WithInvalidActivityId_ShouldHandleGracefully(string activityId) { // Arrange - var configuration = new AzureMcpServerConfiguration + var configuration = new McpServerConfiguration { Name = "TestService", Version = "1.0.0", IsTelemetryEnabled = true }; - var mockOptions = Substitute.For>(); + var mockOptions = Substitute.For>(); mockOptions.Value.Returns(configuration); using var service = new TelemetryService(_mockInformationProvider, mockOptions, _mockServiceOptions, _logger); @@ -175,14 +175,14 @@ public async Task StartActivity_WithInvalidActivityId_ShouldHandleGracefully(str public void StartActivity_WithoutInitialization_Throws() { // Arrange - var configuration = new AzureMcpServerConfiguration + var configuration = new McpServerConfiguration { Name = "TestService", Version = "1.0.0", IsTelemetryEnabled = true }; - var mockOptions = Substitute.For>(); + var mockOptions = Substitute.For>(); mockOptions.Value.Returns(configuration); using var service = new TelemetryService(_mockInformationProvider, mockOptions, _mockServiceOptions, _logger); @@ -206,14 +206,14 @@ public async Task StartActivity_WhenInitializationFails_Throws() // Arrange var informationProvider = new ExceptionalInformationProvider(); - var configuration = new AzureMcpServerConfiguration + var configuration = new McpServerConfiguration { Name = "TestService", Version = "1.0.0", IsTelemetryEnabled = true }; - var mockOptions = Substitute.For>(); + var mockOptions = Substitute.For>(); mockOptions.Value.Returns(configuration); var implementation = new Implementation @@ -243,14 +243,14 @@ public async Task StartActivity_ReturnsActivityWhenEnabled() }; _mockServiceOptions.Value.Returns(serviceStartOptions); - var configuration = new AzureMcpServerConfiguration + var configuration = new McpServerConfiguration { Name = "TestService", Version = "1.0.0", IsTelemetryEnabled = true }; var operationName = "an-activity-id"; - var mockOptions = Substitute.For>(); + var mockOptions = Substitute.For>(); mockOptions.Value.Returns(configuration); using var service = new TelemetryService(_mockInformationProvider, mockOptions, _mockServiceOptions, _logger); @@ -275,14 +275,14 @@ public async Task StartActivity_ReturnsActivityWhenEnabled() public async Task InitializeAsync_InvokedOnce() { // Arrange - var configuration = new AzureMcpServerConfiguration + var configuration = new McpServerConfiguration { Name = "TestService", Version = "1.0.0", IsTelemetryEnabled = true }; - var mockOptions = Substitute.For>(); + var mockOptions = Substitute.For>(); mockOptions.Value.Returns(configuration); using var service = new TelemetryService(_mockInformationProvider, mockOptions, _mockServiceOptions, _logger); From 2677f9d5282eb51db88d5953b91205fe4f22b0c6 Mon Sep 17 00:00:00 2001 From: Connie Yau Date: Fri, 7 Nov 2025 10:37:17 -0800 Subject: [PATCH 02/26] Move dependencies to constructor. --- .../ConsolidatedToolDiscoveryStrategy.cs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/core/Azure.Mcp.Core/src/Areas/Server/Commands/Discovery/ConsolidatedToolDiscoveryStrategy.cs b/core/Azure.Mcp.Core/src/Areas/Server/Commands/Discovery/ConsolidatedToolDiscoveryStrategy.cs index 7a63675226..da07446e5a 100644 --- a/core/Azure.Mcp.Core/src/Areas/Server/Commands/Discovery/ConsolidatedToolDiscoveryStrategy.cs +++ b/core/Azure.Mcp.Core/src/Areas/Server/Commands/Discovery/ConsolidatedToolDiscoveryStrategy.cs @@ -7,6 +7,7 @@ using Azure.Mcp.Core.Areas.Server.Models; using Azure.Mcp.Core.Areas.Server.Options; using Azure.Mcp.Core.Commands; +using Azure.Mcp.Core.Configuration; using Azure.Mcp.Core.Services.Telemetry; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -21,11 +22,21 @@ namespace Azure.Mcp.Core.Areas.Server.Commands.Discovery; /// The command factory used to access available command groups. /// Options for configuring the service behavior. /// Logger instance for this discovery strategy. -public sealed class ConsolidatedToolDiscoveryStrategy(CommandFactory commandFactory, IServiceProvider serviceProvider, IOptions options, ILogger logger) : BaseDiscoveryStrategy(logger) +public sealed class ConsolidatedToolDiscoveryStrategy( + CommandFactory commandFactory, + IServiceProvider serviceProvider, + ITelemetryService telemetryService, + IOptions options, + IOptions serverConfigurationOptions, + ILogger commandFactoryLogger, + ILogger logger) : BaseDiscoveryStrategy(logger) { private readonly CommandFactory _commandFactory = commandFactory; private readonly IServiceProvider _serviceProvider = serviceProvider; + private readonly ITelemetryService _telemetryService = telemetryService; private readonly IOptions _options = options; + private readonly IOptions _serverConfigurationOptions = serverConfigurationOptions; + private readonly ILogger _commandFactoryLogger = commandFactoryLogger; private CommandFactory? _consolidatedCommandFactory; /// @@ -146,14 +157,12 @@ public CommandFactory CreateConsolidatedCommandFactory() #endif // Create a new CommandFactory with all consolidated areas - var telemetryService = _serviceProvider.GetRequiredService(); - var factoryLogger = _serviceProvider.GetRequiredService>(); - _consolidatedCommandFactory = new CommandFactory( _serviceProvider, consolidatedAreas, - telemetryService, - factoryLogger + _telemetryService, + _serverConfigurationOptions, + _commandFactoryLogger ); return _consolidatedCommandFactory; From 6055d8e9719fa71740ada43f9084c30b3d237068 Mon Sep 17 00:00:00 2001 From: Connie Yau Date: Fri, 7 Nov 2025 10:40:46 -0800 Subject: [PATCH 03/26] Split Discovery and ToolLoading into another method. --- .../Commands/ServiceCollectionExtensions.cs | 151 ++++++++++++------ 1 file changed, 106 insertions(+), 45 deletions(-) diff --git a/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs b/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs index 7c5713c364..cedd1dbd3e 100644 --- a/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs +++ b/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs @@ -8,7 +8,9 @@ using Azure.Mcp.Core.Areas.Server.Commands.ToolLoading; using Azure.Mcp.Core.Areas.Server.Options; using Azure.Mcp.Core.Commands; +using Azure.Mcp.Core.Configuration; using Azure.Mcp.Core.Helpers; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -25,8 +27,6 @@ namespace Azure.Mcp.Core.Areas.Server.Commands; /// public static class AzureMcpServiceCollectionExtensions { - private const string DefaultServerName = "Azure MCP Server"; - /// /// Adds the Azure MCP server services to the specified . /// @@ -42,26 +42,52 @@ public static IServiceCollection AddAzureMcpServer(this IServiceCollection servi services.AddSingleton(serviceStartOptions); services.AddSingleton(Options.Create(serviceStartOptions)); - // Register default tool loader options from service start options - var defaultToolLoaderOptions = new ToolLoaderOptions - { - Namespace = serviceStartOptions.Namespace, - ReadOnly = serviceStartOptions.ReadOnly ?? false, - InsecureDisableElicitation = serviceStartOptions.InsecureDisableElicitation, - Tool = serviceStartOptions.Tool, - }; + // Register MCP runtimes + services.AddSingleton(); - if (serviceStartOptions.Mode == ModeTypes.NamespaceProxy) + ConfigureToolLoadersAndDiscoveryStrategies(services, serviceStartOptions); + + var mcpServerOptions = services.AddOptions() + .Configure>( + (mcpServerOptions, mcpRuntime, serverConfig) => + { + var mcpServerOptionsBuilder = services.AddOptions(); + var entryAssembly = Assembly.GetEntryAssembly(); + var assemblyName = entryAssembly?.GetName(); + + mcpServerOptions.ProtocolVersion = "2024-11-05"; + mcpServerOptions.ServerInfo = new Implementation + { + Name = serverConfig.Value.Name, + Version = serverConfig.Value.Version + }; + + mcpServerOptions.Handlers = new() + { + CallToolHandler = mcpRuntime.CallToolHandler, + ListToolsHandler = mcpRuntime.ListToolsHandler, + }; + + // Add instructions for the server + mcpServerOptions.ServerInstructions = GetServerInstructions(); + }); + + var mcpServerBuilder = services.AddMcpServer(); + + if (serviceStartOptions.Transport == TransportTypes.Http) { - if (defaultToolLoaderOptions.Namespace == null || defaultToolLoaderOptions.Namespace.Length == 0) - { - defaultToolLoaderOptions = defaultToolLoaderOptions with { Namespace = ["extension"] }; - } + mcpServerBuilder.WithHttpTransport(); + } + else + { + mcpServerBuilder.WithStdioServerTransport(); } - services.AddSingleton(defaultToolLoaderOptions); - services.AddSingleton(Options.Create(defaultToolLoaderOptions)); + return services; + } + internal static void ConfigureToolLoadersAndDiscoveryStrategies(IServiceCollection services, ServiceStartOptions serviceStartOptions) + { // Register tool loader strategies services.AddSingleton(); services.AddSingleton(); @@ -77,8 +103,25 @@ public static IServiceCollection AddAzureMcpServer(this IServiceCollection servi services.AddSingleton(); services.AddSingleton(); - // Register MCP runtimes - services.AddSingleton(); + // Register default tool loader options from service start options + var defaultToolLoaderOptions = new ToolLoaderOptions + { + Namespace = serviceStartOptions.Namespace, + ReadOnly = serviceStartOptions.ReadOnly ?? false, + InsecureDisableElicitation = serviceStartOptions.InsecureDisableElicitation, + Tool = serviceStartOptions.Tool, + }; + + if (serviceStartOptions.Mode == ModeTypes.NamespaceProxy) + { + if (defaultToolLoaderOptions.Namespace == null || defaultToolLoaderOptions.Namespace.Length == 0) + { + defaultToolLoaderOptions = defaultToolLoaderOptions with { Namespace = ["extension"] }; + } + } + + services.AddSingleton(defaultToolLoaderOptions); + services.AddSingleton(Options.Create(defaultToolLoaderOptions)); // Register MCP discovery strategies based on proxy mode if (serviceStartOptions.Mode == ModeTypes.SingleToolProxy) @@ -207,45 +250,63 @@ public static IServiceCollection AddAzureMcpServer(this IServiceCollection servi return new CompositeToolLoader(toolLoaders, loggerFactory.CreateLogger()); }); } + } - var mcpServerOptions = services - .AddOptions() - .Configure((mcpServerOptions, mcpRuntime) => + public static void ConfigureMcpServerOptions(this IServiceCollection services) + { + services.AddOptions() + .BindConfiguration(string.Empty) + .Configure>((options, rootConfiguration, serviceStartOptions) => { - var mcpServerOptionsBuilder = services.AddOptions(); - var entryAssembly = Assembly.GetEntryAssembly(); - var assemblyName = entryAssembly?.GetName(); - var serverName = entryAssembly?.GetCustomAttribute()?.Title ?? DefaultServerName; + var collectTelemetry = rootConfiguration.GetValue("AZURE_MCP_COLLECT_TELEMETRY"); + var applicationInsightsString = rootConfiguration.GetValue("APPLICATIONINSIGHTS_CONNECTION_STRING"); - mcpServerOptions.ProtocolVersion = "2024-11-05"; - mcpServerOptions.ServerInfo = new Implementation - { - Name = serverName, - Version = assemblyName?.Version?.ToString() ?? "1.0.0-beta" - }; + options.Version = GetServerVersion(Assembly.GetEntryAssembly()); - mcpServerOptions.Handlers = new() - { - CallToolHandler = mcpRuntime.CallToolHandler, - ListToolsHandler = mcpRuntime.ListToolsHandler, - }; + var transport = serviceStartOptions.Value.Transport; + + bool isTelemetryEnabledEnvironment = string.IsNullOrEmpty(collectTelemetry) || (bool.TryParse(collectTelemetry, out var shouldCollect) && shouldCollect); - // Add instructions for the server - mcpServerOptions.ServerInstructions = GetServerInstructions(); + bool isStdioTransport = string.IsNullOrEmpty(transport) || string.Equals(transport, TransportTypes.StdIo, StringComparison.OrdinalIgnoreCase); + + // if transport is not set (default to stdio) or is set to stdio, enable telemetry + // telemetry is disabled for HTTP transport + options.IsTelemetryEnabled = isTelemetryEnabledEnvironment && isStdioTransport; }); - var mcpServerBuilder = services.AddMcpServer(); + services.AddSingleton, AzureMcpServerConfigurationValidator>(); + } - if (serviceStartOptions.Transport == TransportTypes.Http) + /// + /// Gets the version information for the server. Uses logic from Azure SDK for .NET to generate the same version string. + /// https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/core/System.ClientModel/src/Pipeline/UserAgentPolicy.cs#L91 + /// For example, an informational version of "6.14.0-rc.116+54d611f7" will return "6.14.0-rc.116" + /// + /// The caller assembly to extract name and version information from. + /// A version string. + internal static string GetServerVersion(Assembly? callerAssembly) + { + if (callerAssembly == null) { - mcpServerBuilder.WithHttpTransport(); + throw new InvalidOperationException("Should be a managed assembly as entry assembly."); } - else + + AssemblyInformationalVersionAttribute? versionAttribute = callerAssembly.GetCustomAttribute(); + if (versionAttribute == null) { - mcpServerBuilder.WithStdioServerTransport(); + throw new InvalidOperationException( + $"{nameof(AssemblyInformationalVersionAttribute)} is required on client SDK assembly '{callerAssembly.FullName}'."); } - return services; + string version = versionAttribute.InformationalVersion; + + int hashSeparator = version.IndexOf('+'); + if (hashSeparator != -1) + { + version = version.Substring(0, hashSeparator); + } + + return version; } /// From b1d0d387a6232dd4b6004ac7514bf4e3c165901b Mon Sep 17 00:00:00 2001 From: Connie Yau Date: Fri, 7 Nov 2025 10:41:07 -0800 Subject: [PATCH 04/26] Use ApplicationBuilder --- .../Server/Commands/ServiceStartCommand.cs | 72 ++++++++++--------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceStartCommand.cs b/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceStartCommand.cs index 84ba7d534d..9156e15724 100644 --- a/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceStartCommand.cs +++ b/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceStartCommand.cs @@ -17,6 +17,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Console; using Microsoft.Extensions.Options; using Microsoft.Identity.Web; using OpenTelemetry.Logs; @@ -55,7 +56,7 @@ public sealed class ServiceStartCommand : BaseCommand /// public override ToolMetadata Metadata => new() { Destructive = false, ReadOnly = true }; - public static Action ConfigureServices { get; set; } = _ => { }; + public static Action ConfigureServices { get; set; } = (_,__) => { }; public static Func InitializeServicesAsync { get; set; } = _ => Task.CompletedTask; @@ -332,41 +333,42 @@ private IHost CreateHost(ServiceStartOptions serverOptions) /// An IHost instance configured for STDIO transport. private IHost CreateStdioHost(ServiceStartOptions serverOptions) { - return Host.CreateDefaultBuilder() - .ConfigureLogging(logging => - { - logging.ClearProviders(); - logging.ConfigureOpenTelemetryLogger(); - logging.AddEventSourceLogger(); + var builder = Host.CreateApplicationBuilder(); - if (serverOptions.Debug) - { - // Configure console logger to emit Debug+ to stderr so tests can capture logs from StandardError - logging.AddConsole(options => - { - options.LogToStandardErrorThreshold = LogLevel.Debug; - options.FormatterName = Microsoft.Extensions.Logging.Console.ConsoleFormatterNames.Simple; - }); - logging.AddSimpleConsole(simple => - { - simple.ColorBehavior = Microsoft.Extensions.Logging.Console.LoggerColorBehavior.Disabled; - simple.IncludeScopes = false; - simple.SingleLine = true; - simple.TimestampFormat = "[HH:mm:ss] "; - }); - logging.AddFilter("Microsoft.Extensions.Logging.Console.ConsoleLoggerProvider", LogLevel.Debug); - logging.SetMinimumLevel(LogLevel.Debug); - } - }) - .ConfigureServices(services => + var logging = builder.Logging; + + logging.ClearProviders(); + logging.ConfigureOpenTelemetryLogger(); + logging.AddEventSourceLogger(); + + if (builder.Environment.IsDevelopment() && serverOptions.Debug) + { + // Configure console logger to emit Debug+ to stderr so tests can capture logs from StandardError + logging.AddConsole(options => { - // Configure the outgoing authentication strategy. - services.AddSingleIdentityTokenCredentialProvider(); + options.LogToStandardErrorThreshold = LogLevel.Debug; + options.FormatterName = ConsoleFormatterNames.Simple; + }); + logging.AddSimpleConsole(simple => + { + simple.ColorBehavior = LoggerColorBehavior.Disabled; + simple.IncludeScopes = false; + simple.SingleLine = true; + simple.TimestampFormat = "[HH:mm:ss] "; + }); + logging.AddFilter("Microsoft.Extensions.Logging.Console.ConsoleLoggerProvider", LogLevel.Debug); + logging.SetMinimumLevel(LogLevel.Debug); + } + + var services = builder.Services; + + // Configure the outgoing authentication strategy. + services.AddSingleIdentityTokenCredentialProvider(); + + ConfigureServices(services, builder.Environment); + ConfigureMcpServer(services, serverOptions); - ConfigureServices(services); - ConfigureMcpServer(services, serverOptions); - }) - .Build(); + return builder.Build(); } /// @@ -465,7 +467,7 @@ private IHost CreateHttpHost(ServiceStartOptions serverOptions) }); // Configure services - ConfigureServices(services); // Our static callback hook + ConfigureServices(services, builder.Environment); // Our static callback hook ConfigureMcpServer(services, serverOptions); WebApplication app = builder.Build(); @@ -579,7 +581,7 @@ private IHost CreateIncomingAuthDisabledHttpHost(ServiceStartOptions serverOptio }); // Configure services - ConfigureServices(services); // Our static callback hook + ConfigureServices(services, builder.Environment); // Our static callback hook ConfigureMcpServer(services, serverOptions); // We still use the multi-user, HTTP context-aware caching strategy here From a1757cc1e88c1394e2461167185cf5a03b966afe Mon Sep 17 00:00:00 2001 From: Connie Yau Date: Fri, 7 Nov 2025 10:41:29 -0800 Subject: [PATCH 05/26] Replace hardcoded values in CommandFactory with configured ones. --- .../src/Commands/CommandFactory.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/core/Azure.Mcp.Core/src/Commands/CommandFactory.cs b/core/Azure.Mcp.Core/src/Commands/CommandFactory.cs index 6e54a20056..fd2163f9ca 100644 --- a/core/Azure.Mcp.Core/src/Commands/CommandFactory.cs +++ b/core/Azure.Mcp.Core/src/Commands/CommandFactory.cs @@ -8,8 +8,10 @@ using System.Text.Encodings.Web; using System.Text.Json.Serialization; using Azure.Mcp.Core.Areas; +using Azure.Mcp.Core.Configuration; using Azure.Mcp.Core.Services.Telemetry; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using static Azure.Mcp.Core.Services.Telemetry.TelemetryConstants; namespace Azure.Mcp.Core.Commands; @@ -22,6 +24,7 @@ public class CommandFactory private readonly RootCommand _rootCommand; private readonly CommandGroup _rootGroup; private readonly ModelsJsonContext _srcGenWithOptions; + private readonly string _serverName; public const char Separator = '_'; @@ -47,14 +50,17 @@ public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOp } } - internal const string RootCommandGroupName = "azmcp"; - - public CommandFactory(IServiceProvider serviceProvider, IEnumerable serviceAreas, ITelemetryService telemetryService, ILogger logger) + public CommandFactory(IServiceProvider serviceProvider, + IEnumerable serviceAreas, + ITelemetryService telemetryService, + IOptions serverConfig, + ILogger logger) { _serviceAreas = serviceAreas?.ToArray() ?? throw new ArgumentNullException(nameof(serviceAreas)); _serviceProvider = serviceProvider; _logger = logger; - _rootGroup = new CommandGroup(RootCommandGroupName, "Azure MCP Server"); + _serverName = serverConfig.Value.Name; + _rootGroup = new CommandGroup(_serverName, serverConfig.Value.DisplayName); _rootCommand = CreateRootCommand(); _commandMap = CreateCommandDictionary(_rootGroup); _telemetryService = telemetryService; @@ -136,7 +142,7 @@ private void RegisterCommandGroup() // Create a temporary root node to register all the area's subgroups and commands to. // Use this to create the mapping of all commands to that area. - var tempRoot = new CommandGroup(RootCommandGroupName, string.Empty); + var tempRoot = new CommandGroup(_serverName, string.Empty); tempRoot.AddSubGroup(commandTree); var commandDictionary = CreateCommandDictionary(tempRoot); From 8aa39c36da2b9d4b9b7f34fdf8c81b2d1d1d30c9 Mon Sep 17 00:00:00 2001 From: Connie Yau Date: Fri, 7 Nov 2025 10:41:51 -0800 Subject: [PATCH 06/26] Add Options Validation. --- .../AzureMcpServerConfigurationValidator.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 core/Azure.Mcp.Core/src/Configuration/AzureMcpServerConfigurationValidator.cs diff --git a/core/Azure.Mcp.Core/src/Configuration/AzureMcpServerConfigurationValidator.cs b/core/Azure.Mcp.Core/src/Configuration/AzureMcpServerConfigurationValidator.cs new file mode 100644 index 0000000000..adca039315 --- /dev/null +++ b/core/Azure.Mcp.Core/src/Configuration/AzureMcpServerConfigurationValidator.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Extensions.Options; + +namespace Azure.Mcp.Core.Configuration; + +[OptionsValidator] +public partial class AzureMcpServerConfigurationValidator : IValidateOptions +{ +} From 864d38bb65960496c8b3e78b765d643a671e444a Mon Sep 17 00:00:00 2001 From: Connie Yau Date: Fri, 7 Nov 2025 10:42:17 -0800 Subject: [PATCH 07/26] Remove Server Configuration logic from Open Telemetry. --- .../src/Extensions/OpenTelemetryExtensions.cs | 47 +++++++++---------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/core/Azure.Mcp.Core/src/Extensions/OpenTelemetryExtensions.cs b/core/Azure.Mcp.Core/src/Extensions/OpenTelemetryExtensions.cs index b8d8394987..f45ab074ba 100644 --- a/core/Azure.Mcp.Core/src/Extensions/OpenTelemetryExtensions.cs +++ b/core/Azure.Mcp.Core/src/Extensions/OpenTelemetryExtensions.cs @@ -3,12 +3,12 @@ using System.Reflection; using System.Runtime.InteropServices; -using Azure.Mcp.Core.Areas.Server.Options; using Azure.Mcp.Core.Configuration; using Azure.Mcp.Core.Services.Telemetry; using Azure.Monitor.OpenTelemetry.Exporter; // Don't believe this is unused, it is needed for UseAzureMonitorExporter using Microsoft.Extensions.Azure; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using OpenTelemetry.Logs; @@ -20,9 +20,7 @@ namespace Azure.Mcp.Core.Extensions; public static class OpenTelemetryExtensions { - private const string DefaultAppInsights = "InstrumentationKey=21e003c0-efee-4d3f-8a98-1868515aa2c9;IngestionEndpoint=https://centralus-2.in.applicationinsights.azure.com/;LiveEndpoint=https://centralus.livediagnostics.monitor.azure.com/;ApplicationId=f14f6a2d-6405-4f88-bd58-056f25fe274f"; - - public static void ConfigureOpenTelemetry(this IServiceCollection services) + public static void ConfigureOpenTelemetry(this IServiceCollection services, IHostEnvironment hostEnvironment) { services.AddOptions() .Configure>((options, serviceStartOptions) => @@ -67,7 +65,7 @@ public static void ConfigureOpenTelemetry(this IServiceCollection services) services.AddSingleton(); } - EnableAzureMonitor(services); + EnableAzureMonitor(services, hostEnvironment); } public static void ConfigureOpenTelemetryLogger(this ILoggingBuilder builder) @@ -78,27 +76,21 @@ public static void ConfigureOpenTelemetryLogger(this ILoggingBuilder builder) }); } - private static void EnableAzureMonitor(this IServiceCollection services) + private static void EnableAzureMonitor(this IServiceCollection services, IHostEnvironment hostEnvironment) { -#if DEBUG - services.AddSingleton(sp => - { - var forwarder = new AzureEventSourceLogForwarder(sp.GetRequiredService()); - forwarder.Start(); - return forwarder; - }); -#endif - - var appInsightsConnectionString = Environment.GetEnvironmentVariable("APPLICATIONINSIGHTS_CONNECTION_STRING"); - - if (string.IsNullOrEmpty(appInsightsConnectionString)) + if (hostEnvironment.IsDevelopment()) { - appInsightsConnectionString = DefaultAppInsights; + services.AddSingleton(sp => + { + var forwarder = new AzureEventSourceLogForwarder(sp.GetRequiredService()); + forwarder.Start(); + return forwarder; + }); } services.ConfigureOpenTelemetryTracerProvider((sp, builder) => { - var serverConfig = sp.GetRequiredService>(); + var serverConfig = sp.GetRequiredService>(); if (!serverConfig.Value.IsTelemetryEnabled) { return; @@ -116,14 +108,19 @@ private static void EnableAzureMonitor(this IServiceCollection services) .AddTelemetrySdk(); }); -#if RELEASE - otelBuilder.UseAzureMonitorExporter(options => + if (hostEnvironment.IsProduction()) { - options.ConnectionString = appInsightsConnectionString; - }); + + } + else if(hostEnvironment.IsDevelopment()) + { + + } +#if RELEASE + #endif - var enableOtlp = Environment.GetEnvironmentVariable("AZURE_MCP_ENABLE_OTLP_EXPORTER"); + var enableOtlp = Environment.GetEnvironmentVariable("AZURE_MCP_ENABLE_OTLP_EXPORTER"); if (!string.IsNullOrEmpty(enableOtlp) && bool.TryParse(enableOtlp, out var shouldEnable) && shouldEnable) { otelBuilder.WithTracing(tracing => tracing.AddOtlpExporter()) From 7b612ce3ad9d6f23366142538b2df9a00faeb6db Mon Sep 17 00:00:00 2001 From: Connie Yau Date: Fri, 7 Nov 2025 10:42:37 -0800 Subject: [PATCH 08/26] Fix Renamed back to AzureMcpServerConfiguration,. --- core/Azure.Mcp.Core/src/Services/Telemetry/TelemetryService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/Azure.Mcp.Core/src/Services/Telemetry/TelemetryService.cs b/core/Azure.Mcp.Core/src/Services/Telemetry/TelemetryService.cs index 9eb7e8034b..9f67329095 100644 --- a/core/Azure.Mcp.Core/src/Services/Telemetry/TelemetryService.cs +++ b/core/Azure.Mcp.Core/src/Services/Telemetry/TelemetryService.cs @@ -35,7 +35,7 @@ internal class TelemetryService : ITelemetryService internal ActivitySource Parent { get; } public TelemetryService(IMachineInformationProvider informationProvider, - IOptions options, + IOptions options, IOptions? serverOptions, ILogger logger) { From 70f140c67cec51491cc12a992f0441a27283c173 Mon Sep 17 00:00:00 2001 From: Connie Yau Date: Fri, 7 Nov 2025 10:43:06 -0800 Subject: [PATCH 09/26] Enabled Strongly typed Configuration Binding for AOT support. --- core/Azure.Mcp.Core/src/Azure.Mcp.Core.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/Azure.Mcp.Core/src/Azure.Mcp.Core.csproj b/core/Azure.Mcp.Core/src/Azure.Mcp.Core.csproj index 094e8ce931..53ed202815 100644 --- a/core/Azure.Mcp.Core/src/Azure.Mcp.Core.csproj +++ b/core/Azure.Mcp.Core/src/Azure.Mcp.Core.csproj @@ -1,6 +1,8 @@ true + + true true From 48a0daacbf9652f8b6b54398215fd2d11a910e1d Mon Sep 17 00:00:00 2001 From: Connie Yau Date: Fri, 7 Nov 2025 10:43:21 -0800 Subject: [PATCH 10/26] Fix test breaks. --- .../Areas/Server/CommandFactoryHelpers.cs | 16 ++++++- .../ConsolidatedToolDiscoveryStrategyTests.cs | 17 ++++++-- .../Tools/UnitTests/ToolsListCommandTests.cs | 6 ++- .../Commands/CommandFactoryTests.cs | 25 +++++++++-- .../Telemetry/TelemetryServiceTests.cs | 43 ++++++++++++------- 5 files changed, 81 insertions(+), 26 deletions(-) diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/CommandFactoryHelpers.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/CommandFactoryHelpers.cs index 311ebc196a..420f8a966b 100644 --- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/CommandFactoryHelpers.cs +++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/CommandFactoryHelpers.cs @@ -5,9 +5,11 @@ using Azure.Mcp.Core.Areas; using Azure.Mcp.Core.Areas.Group; using Azure.Mcp.Core.Areas.Server; +using Azure.Mcp.Core.Areas.Server.Options; using Azure.Mcp.Core.Areas.Subscription; using Azure.Mcp.Core.Areas.Tools; using Azure.Mcp.Core.Commands; +using Azure.Mcp.Core.Configuration; using Azure.Mcp.Core.Services.Telemetry; using Azure.Mcp.Tools.Acr; using Azure.Mcp.Tools.Aks; @@ -21,6 +23,7 @@ using Azure.Mcp.Tools.CloudArchitect; using Azure.Mcp.Tools.Cosmos; using Azure.Mcp.Tools.Deploy; +using Azure.Mcp.Tools.Deploy.Services.Util; using Azure.Mcp.Tools.EventGrid; using Azure.Mcp.Tools.Extension; using Azure.Mcp.Tools.Foundry; @@ -45,6 +48,7 @@ using Azure.Mcp.Tools.Workbooks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using ModelContextProtocol.Protocol; namespace Azure.Mcp.Core.UnitTests.Areas.Server; @@ -98,7 +102,8 @@ public static CommandFactory CreateCommandFactory(IServiceProvider? serviceProvi var services = serviceProvider ?? CreateDefaultServiceProvider(); var logger = services.GetRequiredService>(); var telemetryService = services.GetService() ?? new NoOpTelemetryService(); - var commandFactory = new CommandFactory(services, areaSetups, telemetryService, logger); + var serviceOptions = services.GetRequiredService>(); + var commandFactory = new CommandFactory(services, areaSetups, telemetryService, serviceOptions, logger); return commandFactory; } @@ -110,6 +115,14 @@ public static IServiceProvider CreateDefaultServiceProvider() public static IServiceCollection SetupCommonServices() { + var mcpServerConfiguration = new AzureMcpServerConfiguration + { + DisplayName = "Test Display Name", + Name = "Test.Mcp.Server", + Prefix = "test-azmcp", + Version = "0.0.1-beta.1", + IsTelemetryEnabled = true, + }; IAreaSetup[] areaSetups = [ // Core areas new SubscriptionSetup(), @@ -154,6 +167,7 @@ public static IServiceCollection SetupCommonServices() var builder = new ServiceCollection() .AddLogging() + .AddSingleton(Microsoft.Extensions.Options.Options.Create(mcpServerConfiguration)) .AddSingleton(); foreach (var area in areaSetups) diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/Discovery/ConsolidatedToolDiscoveryStrategyTests.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/Discovery/ConsolidatedToolDiscoveryStrategyTests.cs index 3166f295bd..1ae1fafef6 100644 --- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/Discovery/ConsolidatedToolDiscoveryStrategyTests.cs +++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/Discovery/ConsolidatedToolDiscoveryStrategyTests.cs @@ -1,12 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Azure.Mcp.Core.Areas; using Azure.Mcp.Core.Areas.Server.Commands.Discovery; -using Azure.Mcp.Core.Areas.Server.Models; using Azure.Mcp.Core.Areas.Server.Options; using Azure.Mcp.Core.Commands; +using Azure.Mcp.Core.Configuration; +using Azure.Mcp.Core.Services.Telemetry; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Xunit; namespace Azure.Mcp.Core.UnitTests.Areas.Server.Commands.Discovery; @@ -21,8 +23,15 @@ private static ConsolidatedToolDiscoveryStrategy CreateStrategy( var factory = commandFactory ?? CommandFactoryHelpers.CreateCommandFactory(); var serviceProvider = CommandFactoryHelpers.SetupCommonServices().BuildServiceProvider(); var startOptions = Microsoft.Extensions.Options.Options.Create(options ?? new ServiceStartOptions()); - var logger = NSubstitute.Substitute.For>(); - var strategy = new ConsolidatedToolDiscoveryStrategy(factory, serviceProvider, startOptions, logger); + + var strategy = new ConsolidatedToolDiscoveryStrategy(factory, + serviceProvider, + serviceProvider.GetRequiredService(), + startOptions, + serviceProvider.GetRequiredService>(), + serviceProvider.GetRequiredService>(), + serviceProvider.GetRequiredService>()); + if (entryPoint != null) { strategy.EntryPoint = entryPoint; diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Tools/UnitTests/ToolsListCommandTests.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Tools/UnitTests/ToolsListCommandTests.cs index ccbad378da..db21e9eed5 100644 --- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Tools/UnitTests/ToolsListCommandTests.cs +++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Tools/UnitTests/ToolsListCommandTests.cs @@ -9,12 +9,14 @@ using Azure.Mcp.Core.Areas.Tools.Commands; using Azure.Mcp.Core.Areas.Tools.Options; using Azure.Mcp.Core.Commands; +using Azure.Mcp.Core.Configuration; using Azure.Mcp.Core.Extensions; using Azure.Mcp.Core.Models.Command; using Azure.Mcp.Core.Services.Telemetry; using Azure.Mcp.Core.UnitTests.Areas.Server; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using NSubstitute; using Xunit; @@ -396,12 +398,14 @@ public async Task ExecuteAsync_WithEmptyCommandFactory_ReturnsEmptyResults() var logger = tempServiceProvider.GetRequiredService>(); var telemetryService = Substitute.For(); var emptyAreaSetups = Array.Empty(); + var serverConfig = Substitute.For>(); // Create a NEW service collection just for the empty command factory var finalCollection = new ServiceCollection(); finalCollection.AddLogging(); - var emptyCommandFactory = new CommandFactory(tempServiceProvider, emptyAreaSetups, telemetryService, logger); + var emptyCommandFactory = new CommandFactory(tempServiceProvider, emptyAreaSetups, + telemetryService, serverConfig, logger); finalCollection.AddSingleton(emptyCommandFactory); var emptyServiceProvider = finalCollection.BuildServiceProvider(); diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Commands/CommandFactoryTests.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Commands/CommandFactoryTests.cs index 3a66b95f27..100d15274f 100644 --- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Commands/CommandFactoryTests.cs +++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Commands/CommandFactoryTests.cs @@ -4,9 +4,11 @@ using System.CommandLine; using Azure.Mcp.Core.Areas; using Azure.Mcp.Core.Commands; +using Azure.Mcp.Core.Configuration; using Azure.Mcp.Core.Services.Telemetry; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using NSubstitute; using Xunit; @@ -22,6 +24,8 @@ public class CommandFactoryTests private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; private readonly ITelemetryService _telemetryService; + private readonly IOptions _defaultServerConfigurationOptions; + private readonly AzureMcpServerConfiguration _defaultServerConfiguration; public CommandFactoryTests() { @@ -30,6 +34,18 @@ public CommandFactoryTests() _serviceProvider = services.BuildServiceProvider(); _logger = Substitute.For>(); _telemetryService = Substitute.For(); + + _defaultServerConfiguration = new AzureMcpServerConfiguration + { + DisplayName = "Command Factory Test Display Name", + Name = "Test.Mcp.Server.CommandFactory", + Prefix = "test-azmcp-command-factory", + Version = "0.0.1-beta.1", + IsTelemetryEnabled = false + }; + + _defaultServerConfigurationOptions = Substitute.For>(); + _defaultServerConfigurationOptions.Value.Returns(_defaultServerConfiguration); } [Fact] @@ -123,7 +139,7 @@ public void Constructor_Throws_AreaSetups_Duplicate() // Act & Assert Assert.Throws(() => - new CommandFactory(_serviceProvider, serviceAreas, _telemetryService, _logger)); + new CommandFactory(_serviceProvider, serviceAreas, _telemetryService, _defaultServerConfigurationOptions, _logger)); } [Fact] @@ -138,7 +154,7 @@ public void Constructor_Throws_AreaSetups_EmptyName() // Act & Assert Assert.Throws(() => - new CommandFactory(_serviceProvider, serviceAreas, _telemetryService, _logger)); + new CommandFactory(_serviceProvider, serviceAreas, _telemetryService, _defaultServerConfigurationOptions, _logger)); } [Theory] @@ -153,7 +169,7 @@ public void GetServiceArea_Existing_SetupArea(string commandName, string expecte var area3 = CreateIAreaSetup("name3"); var serviceAreas = new List { area1, area3, area2 }; - var factory = new CommandFactory(_serviceProvider, serviceAreas, _telemetryService, _logger); + var factory = new CommandFactory(_serviceProvider, serviceAreas, _telemetryService, _defaultServerConfigurationOptions, _logger); // Act // Try in the case that the root prefix is not used. This is in the case that the tool @@ -173,7 +189,8 @@ public void GetServiceArea_DoesNotExist() var area3 = CreateIAreaSetup("name3"); var serviceAreas = new List { area1, area2, area3 }; - var factory = new CommandFactory(_serviceProvider, serviceAreas, _telemetryService, _logger); + var factory = new CommandFactory(_serviceProvider, serviceAreas, _telemetryService, + _defaultServerConfigurationOptions, _logger); // All commands created in command factory are prefixed with the root command group, "azmcp". var commandNameToTry = "azmcp" + CommandFactory.Separator + "name0_subgroup2_directCommand4"; diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Services/Telemetry/TelemetryServiceTests.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Services/Telemetry/TelemetryServiceTests.cs index 0062d4df0b..87c8918ff4 100644 --- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Services/Telemetry/TelemetryServiceTests.cs +++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Services/Telemetry/TelemetryServiceTests.cs @@ -16,20 +16,22 @@ public class TelemetryServiceTests { private const string TestDeviceId = "test-device-id"; private const string TestMacAddressHash = "test-hash"; - private readonly McpServerConfiguration _testConfiguration = new() + private readonly AzureMcpServerConfiguration _testConfiguration = new() { + Prefix = "temp-prefix", + DisplayName = "Telemetry service display name", Name = "TestService", Version = "1.0.0", IsTelemetryEnabled = true }; - private readonly IOptions _mockOptions; + private readonly IOptions _mockOptions; private readonly IMachineInformationProvider _mockInformationProvider; private readonly IOptions _mockServiceOptions; private readonly ILogger _logger; public TelemetryServiceTests() { - _mockOptions = Substitute.For>(); + _mockOptions = Substitute.For>(); _mockOptions.Value.Returns(_testConfiguration); _mockServiceOptions = Substitute.For>(); @@ -98,8 +100,8 @@ public void Constructor_WithNullOptions_ShouldThrowArgumentNullException() public void Constructor_WithNullConfiguration_ShouldThrowNullReferenceException() { // Arrange - var mockOptions = Substitute.For>(); - mockOptions.Value.Returns((McpServerConfiguration)null!); + var mockOptions = Substitute.For>(); + mockOptions.Value.Returns((AzureMcpServerConfiguration)null!); // Act & Assert Assert.Throws(() => new TelemetryService(_mockInformationProvider, mockOptions, _mockServiceOptions, _logger)); @@ -145,14 +147,16 @@ public void GetDefaultTags_ReturnsEmptyOnDisabled() public async Task StartActivity_WithInvalidActivityId_ShouldHandleGracefully(string activityId) { // Arrange - var configuration = new McpServerConfiguration + var configuration = new AzureMcpServerConfiguration { + Prefix = "temp-prefix-a", + DisplayName = "Telemetry service display name A", Name = "TestService", Version = "1.0.0", IsTelemetryEnabled = true }; - var mockOptions = Substitute.For>(); + var mockOptions = Substitute.For>(); mockOptions.Value.Returns(configuration); using var service = new TelemetryService(_mockInformationProvider, mockOptions, _mockServiceOptions, _logger); @@ -175,14 +179,16 @@ public async Task StartActivity_WithInvalidActivityId_ShouldHandleGracefully(str public void StartActivity_WithoutInitialization_Throws() { // Arrange - var configuration = new McpServerConfiguration + var configuration = new AzureMcpServerConfiguration { + Prefix = "temp-prefix-a", + DisplayName = "Telemetry service display name A", Name = "TestService", Version = "1.0.0", IsTelemetryEnabled = true }; - var mockOptions = Substitute.For>(); + var mockOptions = Substitute.For>(); mockOptions.Value.Returns(configuration); using var service = new TelemetryService(_mockInformationProvider, mockOptions, _mockServiceOptions, _logger); @@ -206,14 +212,15 @@ public async Task StartActivity_WhenInitializationFails_Throws() // Arrange var informationProvider = new ExceptionalInformationProvider(); - var configuration = new McpServerConfiguration + var configuration = new AzureMcpServerConfiguration { + Prefix = "temp-prefix-a", + DisplayName = "Telemetry service display name A", Name = "TestService", Version = "1.0.0", IsTelemetryEnabled = true }; - - var mockOptions = Substitute.For>(); + var mockOptions = Substitute.For>(); mockOptions.Value.Returns(configuration); var implementation = new Implementation @@ -243,14 +250,16 @@ public async Task StartActivity_ReturnsActivityWhenEnabled() }; _mockServiceOptions.Value.Returns(serviceStartOptions); - var configuration = new McpServerConfiguration + var configuration = new AzureMcpServerConfiguration { + Prefix = "temp-prefix-a", + DisplayName = "Telemetry service display name A", Name = "TestService", Version = "1.0.0", IsTelemetryEnabled = true }; var operationName = "an-activity-id"; - var mockOptions = Substitute.For>(); + var mockOptions = Substitute.For>(); mockOptions.Value.Returns(configuration); using var service = new TelemetryService(_mockInformationProvider, mockOptions, _mockServiceOptions, _logger); @@ -275,14 +284,16 @@ public async Task StartActivity_ReturnsActivityWhenEnabled() public async Task InitializeAsync_InvokedOnce() { // Arrange - var configuration = new McpServerConfiguration + var configuration = new AzureMcpServerConfiguration { + Prefix = "temp-prefix-a", + DisplayName = "Telemetry service display name A", Name = "TestService", Version = "1.0.0", IsTelemetryEnabled = true }; - var mockOptions = Substitute.For>(); + var mockOptions = Substitute.For>(); mockOptions.Value.Returns(configuration); using var service = new TelemetryService(_mockInformationProvider, mockOptions, _mockServiceOptions, _logger); From d70f4a44b2b67b9d323a0d49fb2132868d21b6a7 Mon Sep 17 00:00:00 2001 From: Connie Yau Date: Fri, 7 Nov 2025 10:44:19 -0800 Subject: [PATCH 11/26] Add Local Development launch settings. --- .../src/Properties/launchSettings.json | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/servers/Azure.Mcp.Server/src/Properties/launchSettings.json b/servers/Azure.Mcp.Server/src/Properties/launchSettings.json index 6702182ee8..038a12f744 100644 --- a/servers/Azure.Mcp.Server/src/Properties/launchSettings.json +++ b/servers/Azure.Mcp.Server/src/Properties/launchSettings.json @@ -1,17 +1,25 @@ { - "$schema": "https://json.schemastore.org/launchsettings.json", - "profiles": { - "debug-remotemcp": { - "commandName": "Project", - "commandLineArgs": "server start --transport http --outgoing-auth-strategy UseHostingEnvironmentIdentity", - "dotnetRunMessages": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "ASPNETCORE_URLS": "http://localhost:1031", - "AzureAd__TenantId": "70a036f6-8e4d-4615-bad6-149c02e7720d", - "AzureAd__ClientId": "ca1e0302-d50a-47d7-b5e6-7aff49884bce", - "AzureAd__Instance": "https://login.microsoftonline.com/" - } - } + "profiles": { + "debug-remotemcp": { + "commandName": "Project", + "commandLineArgs": "server start --transport http --outgoing-auth-strategy UseHostingEnvironmentIdentity", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_URLS": "http://localhost:1031", + "AzureAd__TenantId": "70a036f6-8e4d-4615-bad6-149c02e7720d", + "AzureAd__ClientId": "ca1e0302-d50a-47d7-b5e6-7aff49884bce", + "AzureAd__Instance": "https://login.microsoftonline.com/" + }, + "dotnetRunMessages": true + }, + "local-stdio": { + "commandName": "Project", + "commandLineArgs": "server start", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true } -} + }, + "$schema": "https://json.schemastore.org/launchsettings.json" +} \ No newline at end of file From 78ac6dbff9e355610c76261ba8914b81ffe4c968 Mon Sep 17 00:00:00 2001 From: Connie Yau Date: Fri, 7 Nov 2025 10:46:03 -0800 Subject: [PATCH 12/26] Use ApplicationBuilder to Support environments. --- .../src/Azure.Mcp.Server.csproj | 14 +++++++++++ servers/Azure.Mcp.Server/src/Program.cs | 25 ++++++++++++------- .../src/appsettings.Development.json | 22 ++++++++++++++++ .../src/appsettings.Production.json | 3 +++ servers/Azure.Mcp.Server/src/appsettings.json | 10 ++++++++ 5 files changed, 65 insertions(+), 9 deletions(-) create mode 100644 servers/Azure.Mcp.Server/src/appsettings.Development.json create mode 100644 servers/Azure.Mcp.Server/src/appsettings.Production.json create mode 100644 servers/Azure.Mcp.Server/src/appsettings.json diff --git a/servers/Azure.Mcp.Server/src/Azure.Mcp.Server.csproj b/servers/Azure.Mcp.Server/src/Azure.Mcp.Server.csproj index 63cb4138c7..ad697f4cdf 100644 --- a/servers/Azure.Mcp.Server/src/Azure.Mcp.Server.csproj +++ b/servers/Azure.Mcp.Server/src/Azure.Mcp.Server.csproj @@ -58,6 +58,20 @@ + + + + + + + + PreserveNewest + + + PreserveNewest + + + diff --git a/servers/Azure.Mcp.Server/src/Program.cs b/servers/Azure.Mcp.Server/src/Program.cs index 7fcfe8250c..3edc311451 100644 --- a/servers/Azure.Mcp.Server/src/Program.cs +++ b/servers/Azure.Mcp.Server/src/Program.cs @@ -3,6 +3,7 @@ using System.Net; using Azure.Mcp.Core.Areas; +using Azure.Mcp.Core.Areas.Server.Commands; using Azure.Mcp.Core.Commands; using Azure.Mcp.Core.Services.Azure.ResourceGroup; using Azure.Mcp.Core.Services.Azure.Subscription; @@ -12,6 +13,7 @@ using Azure.Mcp.Core.Services.Telemetry; using Azure.Mcp.Core.Services.Time; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using ServiceStartCommand = Azure.Mcp.Core.Areas.Server.Commands.ServiceStartCommand; @@ -27,20 +29,24 @@ private static async Task Main(string[] args) ServiceStartCommand.ConfigureServices = ConfigureServices; ServiceStartCommand.InitializeServicesAsync = InitializeServicesAsync; - ServiceCollection services = new(); - ConfigureServices(services); + var builder = Host.CreateApplicationBuilder(args); + ConfigureServices(builder.Services, builder.Environment); - services.AddLogging(builder => + builder.Services.AddLogging(builder => { builder.ConfigureOpenTelemetryLogger(); builder.AddConsole(); builder.SetMinimumLevel(LogLevel.Information); }); - var serviceProvider = services.BuildServiceProvider(); - await InitializeServicesAsync(serviceProvider); + using var host = builder.Build(); - var commandFactory = serviceProvider.GetRequiredService(); + await InitializeServicesAsync(host.Services); + + // Starts any IHostedServices + await host.StartAsync(); + + var commandFactory = host.Services.GetRequiredService(); var rootCommand = commandFactory.RootCommand; var parseResult = rootCommand.Parse(args); var status = await parseResult.InvokeAsync(); @@ -58,9 +64,9 @@ private static async Task Main(string[] args) return 1; } } + private static IAreaSetup[] RegisterAreas() { - return [ // Register core areas new Azure.Mcp.Tools.AzureAIBestPractices.AzureAIBestPracticesSetup(), @@ -177,9 +183,10 @@ private static void WriteResponse(CommandResponse response) /// /// /// A service collection. - internal static void ConfigureServices(IServiceCollection services) + internal static void ConfigureServices(IServiceCollection services, IHostEnvironment hostEnvironment) { - services.ConfigureOpenTelemetry(); + services.ConfigureOpenTelemetry(hostEnvironment); + services.ConfigureMcpServerOptions(); services.AddMemoryCache(); services.AddSingleton(); diff --git a/servers/Azure.Mcp.Server/src/appsettings.Development.json b/servers/Azure.Mcp.Server/src/appsettings.Development.json new file mode 100644 index 0000000000..aca8f26f24 --- /dev/null +++ b/servers/Azure.Mcp.Server/src/appsettings.Development.json @@ -0,0 +1,22 @@ +{ + "AZURE_MCP_COLLECT_TELEMETRY": "false", + "Logging": { + "LogLevel": { + "Default": "Debug", + "Microsoft.Hosting.Lifetime": "Information" + }, + "Console": { + "LogToStandardErrorThreshold": "Debug", + "FormatterName": "simple", + "FormatterOptions": { + "ColorBehavior": "Disabled", + "SingleLine": true, + "TimestampFormat": "[HH:mm:ss] ", + "UseUtcTimestamp": true + }, + "LogLevel": { + "Azure.Mcp.Core.Commands.CommandFactory": "Information" + } + } + } +} diff --git a/servers/Azure.Mcp.Server/src/appsettings.Production.json b/servers/Azure.Mcp.Server/src/appsettings.Production.json new file mode 100644 index 0000000000..5b63320791 --- /dev/null +++ b/servers/Azure.Mcp.Server/src/appsettings.Production.json @@ -0,0 +1,3 @@ +{ + "APPLICATIONINSIGHTS_CONNECTION_STRING": "InstrumentationKey=21e003c0-efee-4d3f-8a98-1868515aa2c9;IngestionEndpoint=https://centralus-2.in.applicationinsights.azure.com/;LiveEndpoint=https://centralus.livediagnostics.monitor.azure.com/;ApplicationId=f14f6a2d-6405-4f88-bd58-056f25fe274f" +} diff --git a/servers/Azure.Mcp.Server/src/appsettings.json b/servers/Azure.Mcp.Server/src/appsettings.json new file mode 100644 index 0000000000..418cc40bb5 --- /dev/null +++ b/servers/Azure.Mcp.Server/src/appsettings.json @@ -0,0 +1,10 @@ +{ + "Prefix": "azmcp", + "Name": "Azure.Mcp.Server", + "DisplayName": "Azure MCP Server", + "Logging": { + "LogLevel": { + "Default": "Warning" + } + } +} From 3c84ce930ee073fdfd98f7eddb53d8ad7e2980a0 Mon Sep 17 00:00:00 2001 From: Connie Yau Date: Fri, 7 Nov 2025 10:47:00 -0800 Subject: [PATCH 13/26] Add template files. --- servers/Template.Mcp.Server/src/Program.cs | 13 +++++++------ .../src/Template.Mcp.Server.csproj | 9 +++++++++ .../src/appsettings.Development.json | 18 ++++++++++++++++++ .../Template.Mcp.Server/src/appsettings.json | 10 ++++++++++ 4 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 servers/Template.Mcp.Server/src/appsettings.Development.json create mode 100644 servers/Template.Mcp.Server/src/appsettings.json diff --git a/servers/Template.Mcp.Server/src/Program.cs b/servers/Template.Mcp.Server/src/Program.cs index b58d4adcf0..1aadbbf9e1 100644 --- a/servers/Template.Mcp.Server/src/Program.cs +++ b/servers/Template.Mcp.Server/src/Program.cs @@ -13,6 +13,7 @@ using Azure.Mcp.Core.Services.Telemetry; using Azure.Mcp.Core.Services.Time; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using ServiceStartCommand = Azure.Mcp.Core.Areas.Server.Commands.ServiceStartCommand; @@ -28,17 +29,17 @@ private static async Task Main(string[] args) ServiceStartCommand.ConfigureServices = ConfigureServices; ServiceStartCommand.InitializeServicesAsync = InitializeServicesAsync; - ServiceCollection services = new(); - ConfigureServices(services); + var builder = Host.CreateApplicationBuilder(); + ConfigureServices(builder.Services, builder.Environment); - services.AddLogging(builder => + builder.Services.AddLogging(builder => { builder.ConfigureOpenTelemetryLogger(); builder.AddConsole(); builder.SetMinimumLevel(LogLevel.Information); }); - var serviceProvider = services.BuildServiceProvider(); + var serviceProvider = builder.Services.BuildServiceProvider(); await InitializeServicesAsync(serviceProvider); var commandFactory = serviceProvider.GetRequiredService(); @@ -126,9 +127,9 @@ private static void WriteResponse(CommandResponse response) /// /// /// A service collection. - internal static void ConfigureServices(IServiceCollection services) + internal static void ConfigureServices(IServiceCollection services, IHostEnvironment hostEnvironment) { - services.ConfigureOpenTelemetry(); + services.ConfigureOpenTelemetry(hostEnvironment); services.AddMemoryCache(); services.AddSingleton(); diff --git a/servers/Template.Mcp.Server/src/Template.Mcp.Server.csproj b/servers/Template.Mcp.Server/src/Template.Mcp.Server.csproj index ee9c3a2d31..2f13aa2599 100644 --- a/servers/Template.Mcp.Server/src/Template.Mcp.Server.csproj +++ b/servers/Template.Mcp.Server/src/Template.Mcp.Server.csproj @@ -65,6 +65,15 @@ + + + PreserveNewest + + + PreserveNewest + + + diff --git a/servers/Template.Mcp.Server/src/appsettings.Development.json b/servers/Template.Mcp.Server/src/appsettings.Development.json new file mode 100644 index 0000000000..c001896125 --- /dev/null +++ b/servers/Template.Mcp.Server/src/appsettings.Development.json @@ -0,0 +1,18 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "Microsoft.Hosting.Lifetime": "Information" + }, + "Console": { + "LogToStandardErrorThreshold": "Debug", + "FormatterName": "simple", + "FormatterOptions": { + "ColorBehavior": "Disabled", + "SingleLine": true, + "TimestampFormat": "[HH:mm:ss] ", + "UseUtcTimestamp": true + } + } + } +} diff --git a/servers/Template.Mcp.Server/src/appsettings.json b/servers/Template.Mcp.Server/src/appsettings.json new file mode 100644 index 0000000000..ebe8df30bf --- /dev/null +++ b/servers/Template.Mcp.Server/src/appsettings.json @@ -0,0 +1,10 @@ +{ + "Prefix": "template-mcp", + "Name": "Template.Mcp.Server", + "DisplayName": "Template MCP Server", + "Logging": { + "LogLevel": { + "Default": "Warning" + } + } +} From 0c09eb8e74e0b3c674ffe1fb08013fa1864d9ad1 Mon Sep 17 00:00:00 2001 From: Connie Yau Date: Fri, 7 Nov 2025 10:47:25 -0800 Subject: [PATCH 14/26] Update to ApplicationBuilder in Fabric. --- servers/Fabric.Mcp.Server/src/Program.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/servers/Fabric.Mcp.Server/src/Program.cs b/servers/Fabric.Mcp.Server/src/Program.cs index 208cdaa89d..8517b32148 100644 --- a/servers/Fabric.Mcp.Server/src/Program.cs +++ b/servers/Fabric.Mcp.Server/src/Program.cs @@ -16,6 +16,7 @@ using Azure.Mcp.Core.Services.Telemetry; using Azure.Mcp.Core.Services.Time; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using ServiceStartCommand = Azure.Mcp.Core.Areas.Server.Commands.ServiceStartCommand; @@ -31,17 +32,17 @@ private static async Task Main(string[] args) ServiceStartCommand.ConfigureServices = ConfigureServices; ServiceStartCommand.InitializeServicesAsync = InitializeServicesAsync; - ServiceCollection services = new(); - ConfigureServices(services); + var builder = Host.CreateApplicationBuilder(); + ConfigureServices(builder.Services, builder.Environment); - services.AddLogging(builder => + builder.Services.AddLogging(builder => { builder.ConfigureOpenTelemetryLogger(); builder.AddConsole(); builder.SetMinimumLevel(LogLevel.Information); }); - var serviceProvider = services.BuildServiceProvider(); + var serviceProvider = builder.Services.BuildServiceProvider(); await InitializeServicesAsync(serviceProvider); var commandFactory = serviceProvider.GetRequiredService(); @@ -132,9 +133,9 @@ private static void WriteResponse(CommandResponse response) /// /// /// A service collection. - internal static void ConfigureServices(IServiceCollection services) + internal static void ConfigureServices(IServiceCollection services, IHostEnvironment hostEnvironment) { - services.ConfigureOpenTelemetry(); + services.ConfigureOpenTelemetry(hostEnvironment); services.AddMemoryCache(); services.AddSingleton(); From 8386e311df1b1474efa99bad78ab6e080b7c7a67 Mon Sep 17 00:00:00 2001 From: Connie Yau Date: Fri, 7 Nov 2025 10:52:32 -0800 Subject: [PATCH 15/26] Rename to release. --- .../src/{appsettings.Production.json => appsettings.Release.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename servers/Azure.Mcp.Server/src/{appsettings.Production.json => appsettings.Release.json} (100%) diff --git a/servers/Azure.Mcp.Server/src/appsettings.Production.json b/servers/Azure.Mcp.Server/src/appsettings.Release.json similarity index 100% rename from servers/Azure.Mcp.Server/src/appsettings.Production.json rename to servers/Azure.Mcp.Server/src/appsettings.Release.json From 03aca18ef390cec70ec4a46807bb05d49d2f4826 Mon Sep 17 00:00:00 2001 From: Connie Yau Date: Fri, 7 Nov 2025 10:59:30 -0800 Subject: [PATCH 16/26] Optionally copy release and use appsettings. --- servers/Azure.Mcp.Server/src/Azure.Mcp.Server.csproj | 12 ++++++++---- servers/Azure.Mcp.Server/src/Program.cs | 3 +++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/servers/Azure.Mcp.Server/src/Azure.Mcp.Server.csproj b/servers/Azure.Mcp.Server/src/Azure.Mcp.Server.csproj index ad697f4cdf..677b498652 100644 --- a/servers/Azure.Mcp.Server/src/Azure.Mcp.Server.csproj +++ b/servers/Azure.Mcp.Server/src/Azure.Mcp.Server.csproj @@ -59,15 +59,19 @@ - - + + PreserveNewest + - + PreserveNewest - + + + + PreserveNewest diff --git a/servers/Azure.Mcp.Server/src/Program.cs b/servers/Azure.Mcp.Server/src/Program.cs index 3edc311451..a66336f755 100644 --- a/servers/Azure.Mcp.Server/src/Program.cs +++ b/servers/Azure.Mcp.Server/src/Program.cs @@ -12,6 +12,7 @@ using Azure.Mcp.Core.Services.ProcessExecution; using Azure.Mcp.Core.Services.Telemetry; using Azure.Mcp.Core.Services.Time; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -32,6 +33,8 @@ private static async Task Main(string[] args) var builder = Host.CreateApplicationBuilder(args); ConfigureServices(builder.Services, builder.Environment); + builder.Configuration.AddJsonFile("appsettings.Release.json", optional: true); + builder.Services.AddLogging(builder => { builder.ConfigureOpenTelemetryLogger(); From 598b57e0b62549f3c871de180ffb2ba5b64611f3 Mon Sep 17 00:00:00 2001 From: Connie Yau Date: Sat, 8 Nov 2025 21:04:32 -0800 Subject: [PATCH 17/26] Move Otel and rest of McpServerConfiguration into one method. --- .../Commands/ServiceCollectionExtensions.cs | 21 ++++++++++++++----- .../AzureMcpServerConfiguration.cs | 6 ++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs b/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs index cedd1dbd3e..1b0b591c04 100644 --- a/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs +++ b/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs @@ -252,26 +252,37 @@ internal static void ConfigureToolLoadersAndDiscoveryStrategies(IServiceCollecti } } + /// + /// Using configures . + /// + /// Service Collection to add configuration logic to. public static void ConfigureMcpServerOptions(this IServiceCollection services) { services.AddOptions() .BindConfiguration(string.Empty) .Configure>((options, rootConfiguration, serviceStartOptions) => { - var collectTelemetry = rootConfiguration.GetValue("AZURE_MCP_COLLECT_TELEMETRY"); + var collectTelemetry = rootConfiguration.GetValue("AZURE_MCP_COLLECT_TELEMETRY"); + var isOtelExporterEnabled = rootConfiguration.GetValue("AZURE_MCP_ENABLE_OTLP_EXPORTER"); var applicationInsightsString = rootConfiguration.GetValue("APPLICATIONINSIGHTS_CONNECTION_STRING"); - options.Version = GetServerVersion(Assembly.GetEntryAssembly()); - var transport = serviceStartOptions.Value.Transport; + var isTelemetryEnabledEnvironment = collectTelemetry.HasValue + ? collectTelemetry.Value + : true; - bool isTelemetryEnabledEnvironment = string.IsNullOrEmpty(collectTelemetry) || (bool.TryParse(collectTelemetry, out var shouldCollect) && shouldCollect); + var isStdioTransport = string.IsNullOrEmpty(transport) + || string.Equals(transport, TransportTypes.StdIo, StringComparison.OrdinalIgnoreCase); - bool isStdioTransport = string.IsNullOrEmpty(transport) || string.Equals(transport, TransportTypes.StdIo, StringComparison.OrdinalIgnoreCase); + options.Version = GetServerVersion(Assembly.GetEntryAssembly()); // if transport is not set (default to stdio) or is set to stdio, enable telemetry // telemetry is disabled for HTTP transport options.IsTelemetryEnabled = isTelemetryEnabledEnvironment && isStdioTransport; + + options.IsOtelExporterEnabled = isOtelExporterEnabled.HasValue + ? isOtelExporterEnabled.Value + : false; }); services.AddSingleton, AzureMcpServerConfigurationValidator>(); diff --git a/core/Azure.Mcp.Core/src/Configuration/AzureMcpServerConfiguration.cs b/core/Azure.Mcp.Core/src/Configuration/AzureMcpServerConfiguration.cs index 0dbe82c9b5..83812e4c0a 100644 --- a/core/Azure.Mcp.Core/src/Configuration/AzureMcpServerConfiguration.cs +++ b/core/Azure.Mcp.Core/src/Configuration/AzureMcpServerConfiguration.cs @@ -40,6 +40,12 @@ public class AzureMcpServerConfiguration [Required] public bool IsTelemetryEnabled { get; set; } = true; + /// + /// Indicates whether to enable Open Telemetry Exporter when is true. + /// /// The application insights connection string to use if is true. /// From accc29027d7cdb65b87c77ae623fe46f6b6966d7 Mon Sep 17 00:00:00 2001 From: Connie Yau Date: Sat, 8 Nov 2025 21:05:31 -0800 Subject: [PATCH 18/26] Add resource detector to support DI. --- .../AzureMcpServerResourceDetector.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 core/Azure.Mcp.Core/src/Services/Telemetry/AzureMcpServerResourceDetector.cs diff --git a/core/Azure.Mcp.Core/src/Services/Telemetry/AzureMcpServerResourceDetector.cs b/core/Azure.Mcp.Core/src/Services/Telemetry/AzureMcpServerResourceDetector.cs new file mode 100644 index 0000000000..f59c8c25b4 --- /dev/null +++ b/core/Azure.Mcp.Core/src/Services/Telemetry/AzureMcpServerResourceDetector.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Azure.Mcp.Core.Configuration; +using Microsoft.Extensions.Options; +using OpenTelemetry.Resources; + +namespace Azure.Mcp.Core.Services.Telemetry; + +/// +/// Adds the MCP server as an application to gather telemetry from. +/// +/// MCP server configuration +public class AzureMcpServerResourceDetector(IOptions serverConfiguration) + : IResourceDetector +{ + private readonly AzureMcpServerConfiguration _serverConfiguration = serverConfiguration.Value; + + public Resource Detect() + { + return ResourceBuilder.CreateDefault() + .AddService(_serverConfiguration.Prefix, serviceVersion: _serverConfiguration.Version) + .AddTelemetrySdk() + .Build(); + } +} From 9e35aaf7aadb5456b1b1faf35938c9961ddc6b79 Mon Sep 17 00:00:00 2001 From: Connie Yau Date: Sat, 8 Nov 2025 21:07:41 -0800 Subject: [PATCH 19/26] Configure OpenTelemetry exporter for logging, tracing, and metrics. --- .../src/Extensions/OpenTelemetryExtensions.cs | 111 ++++++++++++------ 1 file changed, 75 insertions(+), 36 deletions(-) diff --git a/core/Azure.Mcp.Core/src/Extensions/OpenTelemetryExtensions.cs b/core/Azure.Mcp.Core/src/Extensions/OpenTelemetryExtensions.cs index f45ab074ba..d872f8e386 100644 --- a/core/Azure.Mcp.Core/src/Extensions/OpenTelemetryExtensions.cs +++ b/core/Azure.Mcp.Core/src/Extensions/OpenTelemetryExtensions.cs @@ -7,6 +7,7 @@ using Azure.Mcp.Core.Services.Telemetry; using Azure.Monitor.OpenTelemetry.Exporter; // Don't believe this is unused, it is needed for UseAzureMonitorExporter using Microsoft.Extensions.Azure; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -20,7 +21,8 @@ namespace Azure.Mcp.Core.Extensions; public static class OpenTelemetryExtensions { - public static void ConfigureOpenTelemetry(this IServiceCollection services, IHostEnvironment hostEnvironment) + public static void ConfigureTelemetryServices(this IServiceCollection services, + IHostEnvironment hostEnvironment, IConfiguration configuration) { services.AddOptions() .Configure>((options, serviceStartOptions) => @@ -65,68 +67,105 @@ public static void ConfigureOpenTelemetry(this IServiceCollection services, IHos services.AddSingleton(); } - EnableAzureMonitor(services, hostEnvironment); + ConfigureOpenTelemetry(services, hostEnvironment, configuration); } - public static void ConfigureOpenTelemetryLogger(this ILoggingBuilder builder) - { - builder.AddOpenTelemetry(logger => - { - logger.AddProcessor(new TelemetryLogRecordEraser()); - }); - } - - private static void EnableAzureMonitor(this IServiceCollection services, IHostEnvironment hostEnvironment) + private static void ConfigureOpenTelemetry(this IServiceCollection services, + IHostEnvironment hostEnvironment, + IConfiguration configuration) { if (hostEnvironment.IsDevelopment()) { services.AddSingleton(sp => { - var forwarder = new AzureEventSourceLogForwarder(sp.GetRequiredService()); + var logger = sp.GetRequiredService(); + var forwarder = new AzureEventSourceLogForwarder(logger); forwarder.Start(); return forwarder; }); } - services.ConfigureOpenTelemetryTracerProvider((sp, builder) => + services.AddSingleton(); + + // Note: Turn on all the signals for metrics, tracing, and logging. + // The configuration for each of these components is done further down + // because we need to resolve classes using IServiceProvider. + // + // There is a single resource we gather information from. + var otelBuilder = services.AddOpenTelemetry() + .ConfigureResource(builder => + { + builder.AddDetector(sp => sp.GetRequiredService()); + }) + .WithMetrics() + .WithTracing() + .WithLogging(builder => + { + builder.AddProcessor(new TelemetryLogRecordEraser()); + }); + + // Metrics configuration + services.ConfigureOpenTelemetryMeterProvider((sp, builder) => { - var serverConfig = sp.GetRequiredService>(); - if (!serverConfig.Value.IsTelemetryEnabled) + var config = sp.GetRequiredService>().Value; + if (!config.IsTelemetryEnabled) { return; } - builder.AddSource(serverConfig.Value.Name); + builder.AddAzureMonitorMetricExporter(options => + { + options.ConnectionString = config.ApplicationInsightsConnectionString; + }); + + if (config.IsOtelExporterEnabled) + { + builder.AddOtlpExporter(); + } }); - var otelBuilder = services.AddOpenTelemetry() - .ConfigureResource(r => + // Tracer configuration + services.ConfigureOpenTelemetryTracerProvider((sp, builder) => + { + var config = sp.GetRequiredService>().Value; + if (!config.IsTelemetryEnabled) { - var version = Assembly.GetExecutingAssembly()?.GetName()?.Version?.ToString(); + return; + } + + // Matches the ActivitySource created in ITelemetryService. + builder.AddSource(config.Name); - r.AddService("azmcp", version) - .AddTelemetrySdk(); + builder.AddAzureMonitorTraceExporter(options => + { + options.ConnectionString = config.ApplicationInsightsConnectionString; }); - if (hostEnvironment.IsProduction()) - { + if (config.IsOtelExporterEnabled) + { + builder.AddOtlpExporter(); + } + }); - } - else if(hostEnvironment.IsDevelopment()) + // Tracer configuration + services.ConfigureOpenTelemetryLoggerProvider((sp, builder) => { + var config = sp.GetRequiredService>().Value; + if (!config.IsTelemetryEnabled) + { + return; + } - } -#if RELEASE - -#endif + builder.AddAzureMonitorLogExporter(options => + { + options.ConnectionString = config.ApplicationInsightsConnectionString; + }); - var enableOtlp = Environment.GetEnvironmentVariable("AZURE_MCP_ENABLE_OTLP_EXPORTER"); - if (!string.IsNullOrEmpty(enableOtlp) && bool.TryParse(enableOtlp, out var shouldEnable) && shouldEnable) - { - otelBuilder.WithTracing(tracing => tracing.AddOtlpExporter()) - .WithMetrics(metrics => metrics.AddOtlpExporter()) - .WithLogging(logging => logging.AddOtlpExporter()); - } + if (config.IsOtelExporterEnabled) + { + builder.AddOtlpExporter(); + } + }); } /// From c609acb70c141699957da7f4a22fe0253826823e Mon Sep 17 00:00:00 2001 From: Connie Yau Date: Sat, 8 Nov 2025 21:08:24 -0800 Subject: [PATCH 20/26] Remove unneeded ConfigureOpenTelemetryLogger() --- .../src/Areas/Server/Commands/ServiceStartCommand.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceStartCommand.cs b/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceStartCommand.cs index 9156e15724..a0640c173e 100644 --- a/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceStartCommand.cs +++ b/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceStartCommand.cs @@ -338,7 +338,6 @@ private IHost CreateStdioHost(ServiceStartOptions serverOptions) var logging = builder.Logging; logging.ClearProviders(); - logging.ConfigureOpenTelemetryLogger(); logging.AddEventSourceLogger(); if (builder.Environment.IsDevelopment() && serverOptions.Debug) @@ -382,7 +381,6 @@ private IHost CreateHttpHost(ServiceStartOptions serverOptions) // Configure logging builder.Logging.ClearProviders(); - builder.Logging.ConfigureOpenTelemetryLogger(); builder.Logging.AddEventSourceLogger(); builder.Logging.AddConsole(); @@ -557,7 +555,6 @@ private IHost CreateIncomingAuthDisabledHttpHost(ServiceStartOptions serverOptio // Configure logging builder.Logging.ClearProviders(); - builder.Logging.ConfigureOpenTelemetryLogger(); builder.Logging.AddEventSourceLogger(); builder.Logging.AddConsole(); From 4eb836ffa61028986c7a53f3a362ab53f0e739e6 Mon Sep 17 00:00:00 2001 From: Connie Yau Date: Sat, 8 Nov 2025 21:35:42 -0800 Subject: [PATCH 21/26] Fix build breaks. --- .../Server/Commands/ServiceStartCommand.cs | 17 ++++++++--------- servers/Azure.Mcp.Server/src/Program.cs | 9 +++++---- servers/Fabric.Mcp.Server/src/Program.cs | 10 ++++++---- servers/Template.Mcp.Server/src/Program.cs | 9 +++++---- .../src/Azure.Mcp.Tools.MySql.csproj | 2 ++ 5 files changed, 26 insertions(+), 21 deletions(-) diff --git a/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceStartCommand.cs b/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceStartCommand.cs index a0640c173e..1ae8927e9d 100644 --- a/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceStartCommand.cs +++ b/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceStartCommand.cs @@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -56,7 +57,7 @@ public sealed class ServiceStartCommand : BaseCommand /// public override ToolMetadata Metadata => new() { Destructive = false, ReadOnly = true }; - public static Action ConfigureServices { get; set; } = (_,__) => { }; + public static Action ConfigureServices { get; set; } = (_) => { }; public static Func InitializeServicesAsync { get; set; } = _ => Task.CompletedTask; @@ -340,7 +341,7 @@ private IHost CreateStdioHost(ServiceStartOptions serverOptions) logging.ClearProviders(); logging.AddEventSourceLogger(); - if (builder.Environment.IsDevelopment() && serverOptions.Debug) + if (serverOptions.Debug) { // Configure console logger to emit Debug+ to stderr so tests can capture logs from StandardError logging.AddConsole(options => @@ -359,13 +360,11 @@ private IHost CreateStdioHost(ServiceStartOptions serverOptions) logging.SetMinimumLevel(LogLevel.Debug); } - var services = builder.Services; - // Configure the outgoing authentication strategy. - services.AddSingleIdentityTokenCredentialProvider(); + builder.Services.AddSingleIdentityTokenCredentialProvider(); - ConfigureServices(services, builder.Environment); - ConfigureMcpServer(services, serverOptions); + ConfigureServices(builder); + ConfigureMcpServer(builder.Services, serverOptions); return builder.Build(); } @@ -465,7 +464,7 @@ private IHost CreateHttpHost(ServiceStartOptions serverOptions) }); // Configure services - ConfigureServices(services, builder.Environment); // Our static callback hook + ConfigureServices(builder); // Our static callback hook ConfigureMcpServer(services, serverOptions); WebApplication app = builder.Build(); @@ -578,7 +577,7 @@ private IHost CreateIncomingAuthDisabledHttpHost(ServiceStartOptions serverOptio }); // Configure services - ConfigureServices(services, builder.Environment); // Our static callback hook + ConfigureServices(builder); // Our static callback hook ConfigureMcpServer(services, serverOptions); // We still use the multi-user, HTTP context-aware caching strategy here diff --git a/servers/Azure.Mcp.Server/src/Program.cs b/servers/Azure.Mcp.Server/src/Program.cs index a66336f755..e5f89c0359 100644 --- a/servers/Azure.Mcp.Server/src/Program.cs +++ b/servers/Azure.Mcp.Server/src/Program.cs @@ -31,17 +31,17 @@ private static async Task Main(string[] args) ServiceStartCommand.InitializeServicesAsync = InitializeServicesAsync; var builder = Host.CreateApplicationBuilder(args); - ConfigureServices(builder.Services, builder.Environment); builder.Configuration.AddJsonFile("appsettings.Release.json", optional: true); builder.Services.AddLogging(builder => { - builder.ConfigureOpenTelemetryLogger(); builder.AddConsole(); builder.SetMinimumLevel(LogLevel.Information); }); + ConfigureServices(builder); + using var host = builder.Build(); await InitializeServicesAsync(host.Services); @@ -186,9 +186,10 @@ private static void WriteResponse(CommandResponse response) /// /// /// A service collection. - internal static void ConfigureServices(IServiceCollection services, IHostEnvironment hostEnvironment) + internal static void ConfigureServices(IHostApplicationBuilder builder) { - services.ConfigureOpenTelemetry(hostEnvironment); + var services = builder.Services; + services.ConfigureTelemetryServices(builder.Environment, builder.Configuration); services.ConfigureMcpServerOptions(); services.AddMemoryCache(); diff --git a/servers/Fabric.Mcp.Server/src/Program.cs b/servers/Fabric.Mcp.Server/src/Program.cs index 8517b32148..67559604cd 100644 --- a/servers/Fabric.Mcp.Server/src/Program.cs +++ b/servers/Fabric.Mcp.Server/src/Program.cs @@ -15,6 +15,7 @@ using Azure.Mcp.Core.Services.ProcessExecution; using Azure.Mcp.Core.Services.Telemetry; using Azure.Mcp.Core.Services.Time; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -33,11 +34,10 @@ private static async Task Main(string[] args) ServiceStartCommand.InitializeServicesAsync = InitializeServicesAsync; var builder = Host.CreateApplicationBuilder(); - ConfigureServices(builder.Services, builder.Environment); + ConfigureServices(builder); builder.Services.AddLogging(builder => { - builder.ConfigureOpenTelemetryLogger(); builder.AddConsole(); builder.SetMinimumLevel(LogLevel.Information); }); @@ -133,9 +133,11 @@ private static void WriteResponse(CommandResponse response) /// /// /// A service collection. - internal static void ConfigureServices(IServiceCollection services, IHostEnvironment hostEnvironment) + internal static void ConfigureServices(IHostApplicationBuilder builder) { - services.ConfigureOpenTelemetry(hostEnvironment); + var services = builder.Services; + + services.ConfigureTelemetryServices(builder.Environment, builder.Configuration); services.AddMemoryCache(); services.AddSingleton(); diff --git a/servers/Template.Mcp.Server/src/Program.cs b/servers/Template.Mcp.Server/src/Program.cs index 1aadbbf9e1..b456e7c1e7 100644 --- a/servers/Template.Mcp.Server/src/Program.cs +++ b/servers/Template.Mcp.Server/src/Program.cs @@ -12,6 +12,7 @@ using Azure.Mcp.Core.Services.ProcessExecution; using Azure.Mcp.Core.Services.Telemetry; using Azure.Mcp.Core.Services.Time; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -30,11 +31,10 @@ private static async Task Main(string[] args) ServiceStartCommand.InitializeServicesAsync = InitializeServicesAsync; var builder = Host.CreateApplicationBuilder(); - ConfigureServices(builder.Services, builder.Environment); + ConfigureServices(builder); builder.Services.AddLogging(builder => { - builder.ConfigureOpenTelemetryLogger(); builder.AddConsole(); builder.SetMinimumLevel(LogLevel.Information); }); @@ -127,9 +127,10 @@ private static void WriteResponse(CommandResponse response) /// /// /// A service collection. - internal static void ConfigureServices(IServiceCollection services, IHostEnvironment hostEnvironment) + internal static void ConfigureServices(IHostApplicationBuilder builder) { - services.ConfigureOpenTelemetry(hostEnvironment); + var services = builder.Services; + services.ConfigureTelemetryServices(builder.Environment, builder.Configuration); services.AddMemoryCache(); services.AddSingleton(); diff --git a/tools/Azure.Mcp.Tools.MySql/src/Azure.Mcp.Tools.MySql.csproj b/tools/Azure.Mcp.Tools.MySql/src/Azure.Mcp.Tools.MySql.csproj index d6da9f0219..5a8daed4e2 100644 --- a/tools/Azure.Mcp.Tools.MySql/src/Azure.Mcp.Tools.MySql.csproj +++ b/tools/Azure.Mcp.Tools.MySql/src/Azure.Mcp.Tools.MySql.csproj @@ -1,5 +1,7 @@ + + true true From b9177cdbc53f8aabfc6d2b17799a749d2619f3dd Mon Sep 17 00:00:00 2001 From: Connie Yau Date: Mon, 10 Nov 2025 04:13:09 -0800 Subject: [PATCH 22/26] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/Areas/Server/Commands/ServiceCollectionExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs b/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs index 1b0b591c04..55eba52f90 100644 --- a/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs +++ b/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs @@ -51,9 +51,9 @@ public static IServiceCollection AddAzureMcpServer(this IServiceCollection servi .Configure>( (mcpServerOptions, mcpRuntime, serverConfig) => { - var mcpServerOptionsBuilder = services.AddOptions(); + var entryAssembly = Assembly.GetEntryAssembly(); - var assemblyName = entryAssembly?.GetName(); + mcpServerOptions.ProtocolVersion = "2024-11-05"; mcpServerOptions.ServerInfo = new Implementation From 402b8b64518d4340c6ed4f7bf33f610dbb490da8 Mon Sep 17 00:00:00 2001 From: Connie Yau Date: Wed, 12 Nov 2025 14:37:15 -0800 Subject: [PATCH 23/26] Use APplkicationInsights string --- .../src/Areas/Server/Commands/ServiceCollectionExtensions.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs b/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs index 55eba52f90..98e58f2fe4 100644 --- a/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs +++ b/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs @@ -54,7 +54,6 @@ public static IServiceCollection AddAzureMcpServer(this IServiceCollection servi var entryAssembly = Assembly.GetEntryAssembly(); - mcpServerOptions.ProtocolVersion = "2024-11-05"; mcpServerOptions.ServerInfo = new Implementation { @@ -270,16 +269,15 @@ public static void ConfigureMcpServerOptions(this IServiceCollection services) var isTelemetryEnabledEnvironment = collectTelemetry.HasValue ? collectTelemetry.Value : true; - var isStdioTransport = string.IsNullOrEmpty(transport) || string.Equals(transport, TransportTypes.StdIo, StringComparison.OrdinalIgnoreCase); options.Version = GetServerVersion(Assembly.GetEntryAssembly()); + options.ApplicationInsightsConnectionString = applicationInsightsString; // if transport is not set (default to stdio) or is set to stdio, enable telemetry // telemetry is disabled for HTTP transport options.IsTelemetryEnabled = isTelemetryEnabledEnvironment && isStdioTransport; - options.IsOtelExporterEnabled = isOtelExporterEnabled.HasValue ? isOtelExporterEnabled.Value : false; From f7abb2b97385102083f4c623023ccb8186a9d7ad Mon Sep 17 00:00:00 2001 From: Connie Yau Date: Wed, 12 Nov 2025 16:03:59 -0800 Subject: [PATCH 24/26] Add suppressions for IL2026 IL3050 --- eng/scripts/Analyze-Code.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/eng/scripts/Analyze-Code.ps1 b/eng/scripts/Analyze-Code.ps1 index b729c5faab..d3f0dea3e3 100644 --- a/eng/scripts/Analyze-Code.ps1 +++ b/eng/scripts/Analyze-Code.ps1 @@ -7,7 +7,10 @@ Push-Location $RepoRoot try { Write-Host "Running dotnet format to check for formatting issues..." $solutionFile = Get-ChildItem -Path . -Filter *.sln | Select-Object -First 1 - dotnet format $solutionFile --verify-no-changes + + # Excluding diagnostics IL2026 and IL3050 due to known issues with source generator + # Can be removed when https://github.com/dotnet/sdk/issues/45054 is resolved + dotnet format $solutionFile --verify-no-changes --exclude-diagnostics IL2026 IL3050 # Run dotnet format if ($LASTEXITCODE) { From 5c33b014c9c8e709bc51def295e185e81eb7b9f9 Mon Sep 17 00:00:00 2001 From: Connie Yau Date: Wed, 12 Nov 2025 16:08:05 -0800 Subject: [PATCH 25/26] Fix build breaks from rebase. --- .../Commands/ServiceCollectionExtensions.cs | 21 ++++---- .../src/Extensions/OpenTelemetryExtensions.cs | 52 ------------------- 2 files changed, 11 insertions(+), 62 deletions(-) diff --git a/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs b/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs index 98e58f2fe4..37e198f816 100644 --- a/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs +++ b/core/Azure.Mcp.Core/src/Areas/Server/Commands/ServiceCollectionExtensions.cs @@ -272,7 +272,13 @@ public static void ConfigureMcpServerOptions(this IServiceCollection services) var isStdioTransport = string.IsNullOrEmpty(transport) || string.Equals(transport, TransportTypes.StdIo, StringComparison.OrdinalIgnoreCase); - options.Version = GetServerVersion(Assembly.GetEntryAssembly()); + var entryAssembly = Assembly.GetEntryAssembly(); + if (entryAssembly == null) + { + throw new InvalidOperationException("Should be a managed assembly as entry assembly."); + } + + options.Version = GetServerVersion(entryAssembly); options.ApplicationInsightsConnectionString = applicationInsightsString; // if transport is not set (default to stdio) or is set to stdio, enable telemetry @@ -291,20 +297,15 @@ public static void ConfigureMcpServerOptions(this IServiceCollection services) /// https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/core/System.ClientModel/src/Pipeline/UserAgentPolicy.cs#L91 /// For example, an informational version of "6.14.0-rc.116+54d611f7" will return "6.14.0-rc.116" /// - /// The caller assembly to extract name and version information from. + /// The caller assembly to extract name and version information from. /// A version string. - internal static string GetServerVersion(Assembly? callerAssembly) + internal static string GetServerVersion(Assembly entryAssembly) { - if (callerAssembly == null) - { - throw new InvalidOperationException("Should be a managed assembly as entry assembly."); - } - - AssemblyInformationalVersionAttribute? versionAttribute = callerAssembly.GetCustomAttribute(); + AssemblyInformationalVersionAttribute? versionAttribute = entryAssembly.GetCustomAttribute(); if (versionAttribute == null) { throw new InvalidOperationException( - $"{nameof(AssemblyInformationalVersionAttribute)} is required on client SDK assembly '{callerAssembly.FullName}'."); + $"{nameof(AssemblyInformationalVersionAttribute)} is required on client SDK assembly '{entryAssembly.FullName}'."); } string version = versionAttribute.InformationalVersion; diff --git a/core/Azure.Mcp.Core/src/Extensions/OpenTelemetryExtensions.cs b/core/Azure.Mcp.Core/src/Extensions/OpenTelemetryExtensions.cs index d872f8e386..e3bc8c8c14 100644 --- a/core/Azure.Mcp.Core/src/Extensions/OpenTelemetryExtensions.cs +++ b/core/Azure.Mcp.Core/src/Extensions/OpenTelemetryExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Reflection; using System.Runtime.InteropServices; using Azure.Mcp.Core.Configuration; using Azure.Mcp.Core.Services.Telemetry; @@ -24,30 +23,6 @@ public static class OpenTelemetryExtensions public static void ConfigureTelemetryServices(this IServiceCollection services, IHostEnvironment hostEnvironment, IConfiguration configuration) { - services.AddOptions() - .Configure>((options, serviceStartOptions) => - { - // Assembly.GetEntryAssembly is used to retrieve the version of the server application as that is - // the assembly that will run the tool calls. - var entryAssembly = Assembly.GetEntryAssembly(); - if (entryAssembly != null) - { - options.Version = GetServerVersion(entryAssembly); - } - - var collectTelemetry = Environment.GetEnvironmentVariable("AZURE_MCP_COLLECT_TELEMETRY"); - - var transport = serviceStartOptions.Value.Transport; - - bool isTelemetryEnabledEnvironment = string.IsNullOrEmpty(collectTelemetry) || (bool.TryParse(collectTelemetry, out var shouldCollect) && shouldCollect); - - bool isStdioTransport = string.IsNullOrEmpty(transport) || string.Equals(transport, "stdio", StringComparison.OrdinalIgnoreCase); - - // if transport is not set (default to stdio) or is set to stdio, enable telemetry - // telemetry is disabled for HTTP transport - options.IsTelemetryEnabled = isTelemetryEnabledEnvironment && isStdioTransport; - }); - services.AddSingleton(); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -167,31 +142,4 @@ private static void ConfigureOpenTelemetry(this IServiceCollection services, } }); } - - /// - /// Gets the version information for the server. Uses logic from Azure SDK for .NET to generate the same version string. - /// https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/core/System.ClientModel/src/Pipeline/UserAgentPolicy.cs#L91 - /// For example, an informational version of "6.14.0-rc.116+54d611f7" will return "6.14.0-rc.116" - /// - /// The entry assembly to extract name and version information from. - /// A version string. - internal static string GetServerVersion(Assembly entryAssembly) - { - AssemblyInformationalVersionAttribute? versionAttribute = entryAssembly.GetCustomAttribute(); - if (versionAttribute == null) - { - throw new InvalidOperationException( - $"{nameof(AssemblyInformationalVersionAttribute)} is required on client SDK assembly '{entryAssembly.FullName}'."); - } - - string version = versionAttribute.InformationalVersion; - - int hashSeparator = version.IndexOf('+'); - if (hashSeparator != -1) - { - version = version.Substring(0, hashSeparator); - } - - return version; - } } From 5c4518b77697d7ae91d93b6846d90f05194ba83f Mon Sep 17 00:00:00 2001 From: Connie Yau Date: Wed, 12 Nov 2025 16:21:31 -0800 Subject: [PATCH 26/26] Undo fix --- tools/Azure.Mcp.Tools.MySql/src/Azure.Mcp.Tools.MySql.csproj | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/Azure.Mcp.Tools.MySql/src/Azure.Mcp.Tools.MySql.csproj b/tools/Azure.Mcp.Tools.MySql/src/Azure.Mcp.Tools.MySql.csproj index 5a8daed4e2..d6da9f0219 100644 --- a/tools/Azure.Mcp.Tools.MySql/src/Azure.Mcp.Tools.MySql.csproj +++ b/tools/Azure.Mcp.Tools.MySql/src/Azure.Mcp.Tools.MySql.csproj @@ -1,7 +1,5 @@ - - true true