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; 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..37e198f816 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,51 @@ 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 entryAssembly = Assembly.GetEntryAssembly(); + + 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 +102,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 +249,74 @@ public static IServiceCollection AddAzureMcpServer(this IServiceCollection servi return new CompositeToolLoader(toolLoaders, loggerFactory.CreateLogger()); }); } + } - var mcpServerOptions = services - .AddOptions() - .Configure((mcpServerOptions, mcpRuntime) => + /// + /// 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 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 isOtelExporterEnabled = rootConfiguration.GetValue("AZURE_MCP_ENABLE_OTLP_EXPORTER"); + 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" - }; + var transport = serviceStartOptions.Value.Transport; + var isTelemetryEnabledEnvironment = collectTelemetry.HasValue + ? collectTelemetry.Value + : true; + var isStdioTransport = string.IsNullOrEmpty(transport) + || string.Equals(transport, TransportTypes.StdIo, StringComparison.OrdinalIgnoreCase); - mcpServerOptions.Handlers = new() + var entryAssembly = Assembly.GetEntryAssembly(); + if (entryAssembly == null) { - CallToolHandler = mcpRuntime.CallToolHandler, - ListToolsHandler = mcpRuntime.ListToolsHandler, - }; + throw new InvalidOperationException("Should be a managed assembly as entry assembly."); + } + + options.Version = GetServerVersion(entryAssembly); + options.ApplicationInsightsConnectionString = applicationInsightsString; - // Add instructions for the server - mcpServerOptions.ServerInstructions = GetServerInstructions(); + // 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; }); - 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 entryAssembly) + { + AssemblyInformationalVersionAttribute? versionAttribute = entryAssembly.GetCustomAttribute(); + if (versionAttribute == null) { - mcpServerBuilder.WithHttpTransport(); + throw new InvalidOperationException( + $"{nameof(AssemblyInformationalVersionAttribute)} is required on client SDK assembly '{entryAssembly.FullName}'."); } - else + + string version = versionAttribute.InformationalVersion; + + int hashSeparator = version.IndexOf('+'); + if (hashSeparator != -1) { - mcpServerBuilder.WithStdioServerTransport(); + version = version.Substring(0, hashSeparator); } - return services; + return version; } /// 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..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,9 +14,11 @@ 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; +using Microsoft.Extensions.Logging.Console; using Microsoft.Extensions.Options; using Microsoft.Identity.Web; using OpenTelemetry.Logs; @@ -55,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; @@ -332,41 +334,39 @@ 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.AddEventSourceLogger(); + + 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 = ConsoleFormatterNames.Simple; + }); + logging.AddSimpleConsole(simple => { - // Configure the outgoing authentication strategy. - services.AddSingleIdentityTokenCredentialProvider(); + 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); + } + + // Configure the outgoing authentication strategy. + builder.Services.AddSingleIdentityTokenCredentialProvider(); + + ConfigureServices(builder); + ConfigureMcpServer(builder.Services, serverOptions); - ConfigureServices(services); - ConfigureMcpServer(services, serverOptions); - }) - .Build(); + return builder.Build(); } /// @@ -380,7 +380,6 @@ private IHost CreateHttpHost(ServiceStartOptions serverOptions) // Configure logging builder.Logging.ClearProviders(); - builder.Logging.ConfigureOpenTelemetryLogger(); builder.Logging.AddEventSourceLogger(); builder.Logging.AddConsole(); @@ -465,7 +464,7 @@ private IHost CreateHttpHost(ServiceStartOptions serverOptions) }); // Configure services - ConfigureServices(services); // Our static callback hook + ConfigureServices(builder); // Our static callback hook ConfigureMcpServer(services, serverOptions); WebApplication app = builder.Build(); @@ -555,7 +554,6 @@ private IHost CreateIncomingAuthDisabledHttpHost(ServiceStartOptions serverOptio // Configure logging builder.Logging.ClearProviders(); - builder.Logging.ConfigureOpenTelemetryLogger(); builder.Logging.AddEventSourceLogger(); builder.Logging.AddConsole(); @@ -579,7 +577,7 @@ private IHost CreateIncomingAuthDisabledHttpHost(ServiceStartOptions serverOptio }); // Configure services - ConfigureServices(services); // 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/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 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); diff --git a/core/Azure.Mcp.Core/src/Configuration/AzureMcpServerConfiguration.cs b/core/Azure.Mcp.Core/src/Configuration/AzureMcpServerConfiguration.cs index aa2c922a33..83812e4c0a 100644 --- a/core/Azure.Mcp.Core/src/Configuration/AzureMcpServerConfiguration.cs +++ b/core/Azure.Mcp.Core/src/Configuration/AzureMcpServerConfiguration.cs @@ -1,15 +1,53 @@ // 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; + + /// + /// Indicates whether to enable Open Telemetry Exporter when is true. + /// + /// The application insights connection string to use if is true. + /// + public string? ApplicationInsightsConnectionString { get; set; } } 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 +{ +} diff --git a/core/Azure.Mcp.Core/src/Extensions/OpenTelemetryExtensions.cs b/core/Azure.Mcp.Core/src/Extensions/OpenTelemetryExtensions.cs index cbb7fc19fa..e3bc8c8c14 100644 --- a/core/Azure.Mcp.Core/src/Extensions/OpenTelemetryExtensions.cs +++ b/core/Azure.Mcp.Core/src/Extensions/OpenTelemetryExtensions.cs @@ -1,14 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -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.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using OpenTelemetry.Logs; @@ -20,34 +20,9 @@ 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 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)) @@ -67,95 +42,104 @@ public static void ConfigureOpenTelemetry(this IServiceCollection services) services.AddSingleton(); } - EnableAzureMonitor(services); + ConfigureOpenTelemetry(services, hostEnvironment, configuration); } - public static void ConfigureOpenTelemetryLogger(this ILoggingBuilder builder) + private static void ConfigureOpenTelemetry(this IServiceCollection services, + IHostEnvironment hostEnvironment, + IConfiguration configuration) { - builder.AddOpenTelemetry(logger => + if (hostEnvironment.IsDevelopment()) { - logger.AddProcessor(new TelemetryLogRecordEraser()); - }); - } + services.AddSingleton(sp => + { + var logger = sp.GetRequiredService(); + var forwarder = new AzureEventSourceLogForwarder(logger); + forwarder.Start(); + return forwarder; + }); + } - private static void EnableAzureMonitor(this IServiceCollection services) - { -#if DEBUG - services.AddSingleton(sp => - { - var forwarder = new AzureEventSourceLogForwarder(sp.GetRequiredService()); - forwarder.Start(); - return forwarder; - }); -#endif + services.AddSingleton(); - var appInsightsConnectionString = Environment.GetEnvironmentVariable("APPLICATIONINSIGHTS_CONNECTION_STRING"); + // 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()); + }); - if (string.IsNullOrEmpty(appInsightsConnectionString)) + // Metrics configuration + services.ConfigureOpenTelemetryMeterProvider((sp, builder) => { - appInsightsConnectionString = DefaultAppInsights; - } + var config = sp.GetRequiredService>().Value; + if (!config.IsTelemetryEnabled) + { + return; + } + + builder.AddAzureMonitorMetricExporter(options => + { + options.ConnectionString = config.ApplicationInsightsConnectionString; + }); + if (config.IsOtelExporterEnabled) + { + builder.AddOtlpExporter(); + } + }); + + // Tracer configuration services.ConfigureOpenTelemetryTracerProvider((sp, builder) => { - var serverConfig = sp.GetRequiredService>(); - if (!serverConfig.Value.IsTelemetryEnabled) + var config = sp.GetRequiredService>().Value; + if (!config.IsTelemetryEnabled) { return; } - builder.AddSource(serverConfig.Value.Name); - }); + // Matches the ActivitySource created in ITelemetryService. + builder.AddSource(config.Name); - var otelBuilder = services.AddOpenTelemetry() - .ConfigureResource(r => + builder.AddAzureMonitorTraceExporter(options => { - var version = Assembly.GetExecutingAssembly()?.GetName()?.Version?.ToString(); - - r.AddService("azmcp", version) - .AddTelemetrySdk(); + options.ConnectionString = config.ApplicationInsightsConnectionString; }); -#if RELEASE - otelBuilder.UseAzureMonitorExporter(options => - { - options.ConnectionString = appInsightsConnectionString; + if (config.IsOtelExporterEnabled) + { + builder.AddOtlpExporter(); + } }); -#endif - var enableOtlp = Environment.GetEnvironmentVariable("AZURE_MCP_ENABLE_OTLP_EXPORTER"); - if (!string.IsNullOrEmpty(enableOtlp) && bool.TryParse(enableOtlp, out var shouldEnable) && shouldEnable) + // Tracer configuration + services.ConfigureOpenTelemetryLoggerProvider((sp, builder) => { - otelBuilder.WithTracing(tracing => tracing.AddOtlpExporter()) - .WithMetrics(metrics => metrics.AddOtlpExporter()) - .WithLogging(logging => logging.AddOtlpExporter()); - } - } - - /// - /// 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; + var config = sp.GetRequiredService>().Value; + if (!config.IsTelemetryEnabled) + { + return; + } - int hashSeparator = version.IndexOf('+'); - if (hashSeparator != -1) - { - version = version.Substring(0, hashSeparator); - } + builder.AddAzureMonitorLogExporter(options => + { + options.ConnectionString = config.ApplicationInsightsConnectionString; + }); - return version; + if (config.IsOtelExporterEnabled) + { + builder.AddOtlpExporter(); + } + }); } } 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(); + } +} 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 b75dab5126..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 @@ -18,6 +18,8 @@ public class TelemetryServiceTests private const string TestMacAddressHash = "test-hash"; private readonly AzureMcpServerConfiguration _testConfiguration = new() { + Prefix = "temp-prefix", + DisplayName = "Telemetry service display name", Name = "TestService", Version = "1.0.0", IsTelemetryEnabled = true @@ -147,6 +149,8 @@ public async Task StartActivity_WithInvalidActivityId_ShouldHandleGracefully(str // Arrange var configuration = new AzureMcpServerConfiguration { + Prefix = "temp-prefix-a", + DisplayName = "Telemetry service display name A", Name = "TestService", Version = "1.0.0", IsTelemetryEnabled = true @@ -177,6 +181,8 @@ public void StartActivity_WithoutInitialization_Throws() // Arrange var configuration = new AzureMcpServerConfiguration { + Prefix = "temp-prefix-a", + DisplayName = "Telemetry service display name A", Name = "TestService", Version = "1.0.0", IsTelemetryEnabled = true @@ -208,11 +214,12 @@ public async Task StartActivity_WhenInitializationFails_Throws() 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>(); mockOptions.Value.Returns(configuration); @@ -245,6 +252,8 @@ public async Task StartActivity_ReturnsActivityWhenEnabled() var configuration = new AzureMcpServerConfiguration { + Prefix = "temp-prefix-a", + DisplayName = "Telemetry service display name A", Name = "TestService", Version = "1.0.0", IsTelemetryEnabled = true @@ -277,6 +286,8 @@ public async Task InitializeAsync_InvokedOnce() // Arrange var configuration = new AzureMcpServerConfiguration { + Prefix = "temp-prefix-a", + DisplayName = "Telemetry service display name A", Name = "TestService", Version = "1.0.0", IsTelemetryEnabled = true 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) { diff --git a/servers/Azure.Mcp.Server/src/Azure.Mcp.Server.csproj b/servers/Azure.Mcp.Server/src/Azure.Mcp.Server.csproj index 63cb4138c7..677b498652 100644 --- a/servers/Azure.Mcp.Server/src/Azure.Mcp.Server.csproj +++ b/servers/Azure.Mcp.Server/src/Azure.Mcp.Server.csproj @@ -58,6 +58,24 @@ + + + PreserveNewest + + + + + + PreserveNewest + + + + + + PreserveNewest + + + diff --git a/servers/Azure.Mcp.Server/src/Program.cs b/servers/Azure.Mcp.Server/src/Program.cs index 7fcfe8250c..e5f89c0359 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; @@ -11,7 +12,9 @@ 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; using ServiceStartCommand = Azure.Mcp.Core.Areas.Server.Commands.ServiceStartCommand; @@ -27,20 +30,26 @@ private static async Task Main(string[] args) ServiceStartCommand.ConfigureServices = ConfigureServices; ServiceStartCommand.InitializeServicesAsync = InitializeServicesAsync; - ServiceCollection services = new(); - ConfigureServices(services); + var builder = Host.CreateApplicationBuilder(args); - services.AddLogging(builder => + builder.Configuration.AddJsonFile("appsettings.Release.json", optional: true); + + builder.Services.AddLogging(builder => { - builder.ConfigureOpenTelemetryLogger(); builder.AddConsole(); builder.SetMinimumLevel(LogLevel.Information); }); - var serviceProvider = services.BuildServiceProvider(); - await InitializeServicesAsync(serviceProvider); + ConfigureServices(builder); + + using var host = builder.Build(); + + await InitializeServicesAsync(host.Services); - var commandFactory = serviceProvider.GetRequiredService(); + // 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 +67,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 +186,11 @@ private static void WriteResponse(CommandResponse response) /// /// /// A service collection. - internal static void ConfigureServices(IServiceCollection services) + internal static void ConfigureServices(IHostApplicationBuilder builder) { - services.ConfigureOpenTelemetry(); + var services = builder.Services; + services.ConfigureTelemetryServices(builder.Environment, builder.Configuration); + services.ConfigureMcpServerOptions(); services.AddMemoryCache(); services.AddSingleton(); 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 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.Release.json b/servers/Azure.Mcp.Server/src/appsettings.Release.json new file mode 100644 index 0000000000..5b63320791 --- /dev/null +++ b/servers/Azure.Mcp.Server/src/appsettings.Release.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" + } + } +} diff --git a/servers/Fabric.Mcp.Server/src/Program.cs b/servers/Fabric.Mcp.Server/src/Program.cs index 208cdaa89d..67559604cd 100644 --- a/servers/Fabric.Mcp.Server/src/Program.cs +++ b/servers/Fabric.Mcp.Server/src/Program.cs @@ -15,7 +15,9 @@ 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; using ServiceStartCommand = Azure.Mcp.Core.Areas.Server.Commands.ServiceStartCommand; @@ -31,17 +33,16 @@ 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.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,11 @@ private static void WriteResponse(CommandResponse response) /// /// /// A service collection. - internal static void ConfigureServices(IServiceCollection services) + internal static void ConfigureServices(IHostApplicationBuilder builder) { - services.ConfigureOpenTelemetry(); + 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 b58d4adcf0..b456e7c1e7 100644 --- a/servers/Template.Mcp.Server/src/Program.cs +++ b/servers/Template.Mcp.Server/src/Program.cs @@ -12,7 +12,9 @@ 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; using ServiceStartCommand = Azure.Mcp.Core.Areas.Server.Commands.ServiceStartCommand; @@ -28,17 +30,16 @@ 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.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,10 @@ private static void WriteResponse(CommandResponse response) /// /// /// A service collection. - internal static void ConfigureServices(IServiceCollection services) + internal static void ConfigureServices(IHostApplicationBuilder builder) { - services.ConfigureOpenTelemetry(); + var services = builder.Services; + services.ConfigureTelemetryServices(builder.Environment, builder.Configuration); 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" + } + } +}