Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a48c578
Change AzureMcpServerConfiguration to McpServerConfiguration. Add de…
conniey Nov 4, 2025
2677f9d
Move dependencies to constructor.
conniey Nov 7, 2025
6055d8e
Split Discovery and ToolLoading into another method.
conniey Nov 7, 2025
b1d0d38
Use ApplicationBuilder
conniey Nov 7, 2025
a1757cc
Replace hardcoded values in CommandFactory with configured ones.
conniey Nov 7, 2025
8aa39c3
Add Options Validation.
conniey Nov 7, 2025
864d38b
Remove Server Configuration logic from Open Telemetry.
conniey Nov 7, 2025
7b612ce
Fix Renamed back to AzureMcpServerConfiguration,.
conniey Nov 7, 2025
70f140c
Enabled Strongly typed Configuration Binding for AOT support.
conniey Nov 7, 2025
48a0daa
Fix test breaks.
conniey Nov 7, 2025
d70f4a4
Add Local Development launch settings.
conniey Nov 7, 2025
78ac6db
Use ApplicationBuilder to Support environments.
conniey Nov 7, 2025
3c84ce9
Add template files.
conniey Nov 7, 2025
0c09eb8
Update to ApplicationBuilder in Fabric.
conniey Nov 7, 2025
8386e31
Rename to release.
conniey Nov 7, 2025
03aca18
Optionally copy release and use appsettings.
conniey Nov 7, 2025
598b57e
Move Otel and rest of McpServerConfiguration into one method.
conniey Nov 9, 2025
accc290
Add resource detector to support DI.
conniey Nov 9, 2025
9e35aaf
Configure OpenTelemetry exporter for logging, tracing, and metrics.
conniey Nov 9, 2025
c609acb
Remove unneeded ConfigureOpenTelemetryLogger()
conniey Nov 9, 2025
4eb836f
Fix build breaks.
conniey Nov 9, 2025
b9177cd
Apply suggestions from code review
conniey Nov 10, 2025
402b8b6
Use APplkicationInsights string
conniey Nov 12, 2025
f7abb2b
Add suppressions for IL2026 IL3050
conniey Nov 13, 2025
5c33b01
Fix build breaks from rebase.
conniey Nov 13, 2025
5c4518b
Undo fix
conniey Nov 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -21,11 +22,21 @@ namespace Azure.Mcp.Core.Areas.Server.Commands.Discovery;
/// <param name="commandFactory">The command factory used to access available command groups.</param>
/// <param name="options">Options for configuring the service behavior.</param>
/// <param name="logger">Logger instance for this discovery strategy.</param>
public sealed class ConsolidatedToolDiscoveryStrategy(CommandFactory commandFactory, IServiceProvider serviceProvider, IOptions<ServiceStartOptions> options, ILogger<ConsolidatedToolDiscoveryStrategy> logger) : BaseDiscoveryStrategy(logger)
public sealed class ConsolidatedToolDiscoveryStrategy(
CommandFactory commandFactory,
IServiceProvider serviceProvider,
ITelemetryService telemetryService,
IOptions<ServiceStartOptions> options,
IOptions<AzureMcpServerConfiguration> serverConfigurationOptions,
ILogger<CommandFactory> commandFactoryLogger,
ILogger<ConsolidatedToolDiscoveryStrategy> logger) : BaseDiscoveryStrategy(logger)
{
private readonly CommandFactory _commandFactory = commandFactory;
private readonly IServiceProvider _serviceProvider = serviceProvider;
private readonly ITelemetryService _telemetryService = telemetryService;
private readonly IOptions<ServiceStartOptions> _options = options;
private readonly IOptions<AzureMcpServerConfiguration> _serverConfigurationOptions = serverConfigurationOptions;
private readonly ILogger<CommandFactory> _commandFactoryLogger = commandFactoryLogger;
private CommandFactory? _consolidatedCommandFactory;

/// <summary>
Expand Down Expand Up @@ -146,14 +157,12 @@ public CommandFactory CreateConsolidatedCommandFactory()
#endif

// Create a new CommandFactory with all consolidated areas
var telemetryService = _serviceProvider.GetRequiredService<ITelemetryService>();
var factoryLogger = _serviceProvider.GetRequiredService<ILogger<CommandFactory>>();

_consolidatedCommandFactory = new CommandFactory(
_serviceProvider,
consolidatedAreas,
telemetryService,
factoryLogger
_telemetryService,
_serverConfigurationOptions,
_commandFactoryLogger
);

return _consolidatedCommandFactory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,8 +27,6 @@ namespace Azure.Mcp.Core.Areas.Server.Commands;
/// </summary>
public static class AzureMcpServiceCollectionExtensions
{
private const string DefaultServerName = "Azure MCP Server";

/// <summary>
/// Adds the Azure MCP server services to the specified <see cref="IServiceCollection"/>.
/// </summary>
Expand All @@ -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<IMcpRuntime, McpRuntime>();

if (serviceStartOptions.Mode == ModeTypes.NamespaceProxy)
ConfigureToolLoadersAndDiscoveryStrategies(services, serviceStartOptions);

var mcpServerOptions = services.AddOptions<McpServerOptions>()
.Configure<IMcpRuntime, IOptions<AzureMcpServerConfiguration>>(
(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<CommandFactoryToolLoader>();
services.AddSingleton<RegistryToolLoader>();
Expand All @@ -77,8 +102,25 @@ public static IServiceCollection AddAzureMcpServer(this IServiceCollection servi
services.AddSingleton<RegistryDiscoveryStrategy>();
services.AddSingleton<ConsolidatedToolDiscoveryStrategy>();

// Register MCP runtimes
services.AddSingleton<IMcpRuntime, McpRuntime>();
// 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)
Expand Down Expand Up @@ -207,45 +249,74 @@ public static IServiceCollection AddAzureMcpServer(this IServiceCollection servi
return new CompositeToolLoader(toolLoaders, loggerFactory.CreateLogger<CompositeToolLoader>());
});
}
}

var mcpServerOptions = services
.AddOptions<McpServerOptions>()
.Configure<IMcpRuntime>((mcpServerOptions, mcpRuntime) =>
/// <summary>
/// Using <see cref="IConfiguration"/> configures <see cref="AzureMcpServerConfiguration"/>.
/// </summary>
/// <param name="services">Service Collection to add configuration logic to.</param>
public static void ConfigureMcpServerOptions(this IServiceCollection services)
{
services.AddOptions<AzureMcpServerConfiguration>()
.BindConfiguration(string.Empty)
.Configure<IConfiguration, IOptions<ServiceStartOptions>>((options, rootConfiguration, serviceStartOptions) =>
{
var mcpServerOptionsBuilder = services.AddOptions<McpServerOptions>();
var entryAssembly = Assembly.GetEntryAssembly();
var assemblyName = entryAssembly?.GetName();
var serverName = entryAssembly?.GetCustomAttribute<AssemblyTitleAttribute>()?.Title ?? DefaultServerName;
var collectTelemetry = rootConfiguration.GetValue<bool?>("AZURE_MCP_COLLECT_TELEMETRY");
var isOtelExporterEnabled = rootConfiguration.GetValue<bool?>("AZURE_MCP_ENABLE_OTLP_EXPORTER");
var applicationInsightsString = rootConfiguration.GetValue<string?>("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<IValidateOptions<AzureMcpServerConfiguration>, AzureMcpServerConfigurationValidator>();
}

if (serviceStartOptions.Transport == TransportTypes.Http)
/// <summary>
/// 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"
/// </summary>
/// <param name="entryAssembly">The caller assembly to extract name and version information from.</param>
/// <returns>A version string.</returns>
internal static string GetServerVersion(Assembly entryAssembly)
{
AssemblyInformationalVersionAttribute? versionAttribute = entryAssembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
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;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -55,7 +57,7 @@ public sealed class ServiceStartCommand : BaseCommand<ServiceStartOptions>
/// </summary>
public override ToolMetadata Metadata => new() { Destructive = false, ReadOnly = true };

public static Action<IServiceCollection> ConfigureServices { get; set; } = _ => { };
public static Action<IHostApplicationBuilder> ConfigureServices { get; set; } = (_) => { };

public static Func<IServiceProvider, Task> InitializeServicesAsync { get; set; } = _ => Task.CompletedTask;

Expand Down Expand Up @@ -332,41 +334,39 @@ private IHost CreateHost(ServiceStartOptions serverOptions)
/// <returns>An IHost instance configured for STDIO transport.</returns>
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();
}

/// <summary>
Expand All @@ -380,7 +380,6 @@ private IHost CreateHttpHost(ServiceStartOptions serverOptions)

// Configure logging
builder.Logging.ClearProviders();
builder.Logging.ConfigureOpenTelemetryLogger();
builder.Logging.AddEventSourceLogger();
builder.Logging.AddConsole();

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -555,7 +554,6 @@ private IHost CreateIncomingAuthDisabledHttpHost(ServiceStartOptions serverOptio

// Configure logging
builder.Logging.ClearProviders();
builder.Logging.ConfigureOpenTelemetryLogger();
builder.Logging.AddEventSourceLogger();
builder.Logging.AddConsole();

Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions core/Azure.Mcp.Core/src/Azure.Mcp.Core.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsAotCompatible>true</IsAotCompatible>
<!-- Enable strongly typed binding for IConfiguration to support AOT and trimming. -->
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

Expand Down
Loading
Loading