Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 24 additions & 3 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ indent_size = 4

# C# files
[*.cs]
file_header_template = Copyright (c) Microsoft Corporation.\nLicensed under the MIT License.
# New line preferences
csharp_new_line_before_open_brace = all # vs-default: any
csharp_new_line_before_else = true # vs-default: true
Expand All @@ -33,7 +34,7 @@ csharp_indent_switch_labels = true # vs-default: true
csharp_indent_labels = one_less_than_current # vs-default: one_less_than_current

# Modifier preferences
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:error

# avoid this. unless absolutely necessary
dotnet_style_qualification_for_field = false:suggestion # vs-default: false:none
Expand Down Expand Up @@ -83,18 +84,21 @@ dotnet_naming_style.camel_case_underscore_style.required_prefix = _
dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case

# Code style defaults
csharp_using_directive_placement = outside_namespace:suggestion
csharp_using_directive_placement = outside_namespace:error
dotnet_sort_system_directives_first = true # vs-default: true
csharp_prefer_braces = true:refactoring
csharp_preserve_single_line_blocks = true # vs-default: true
csharp_preserve_single_line_statements = false # vs-default: true
csharp_prefer_static_local_function = true:suggestion
csharp_prefer_simple_using_statement = false:none
csharp_style_prefer_switch_expression = true:suggestion
csharp_style_namespace_declarations = file_scoped:error
csharp_style_namespace_match_folder = true:error

# Code quality
dotnet_style_readonly_field = true:suggestion
dotnet_code_quality_unused_parameters = non_public:suggestion
# Safe to warn on all unused parameters as the project is shipped as an application and not a library.
dotnet_code_quality_unused_parameters = all:error

# Expression-level preferences
dotnet_style_object_initializer = true:suggestion # vs-default: true:suggestion
Expand Down Expand Up @@ -167,6 +171,23 @@ dotnet_code_quality.ca1802.api_surface = private, internal
# CA2016: Forward the CancellationToken parameter to methods that take one
dotnet_diagnostic.CA2016.severity = error

# Diagnostics without corresponding editorconfig options
# Unused usings
dotnet_diagnostic.IDE0005.severity = error
# Invalid string formatting
dotnet_diagnostic.IDE0043.severity = error
# Unused/unread private members
dotnet_diagnostic.IDE0051.severity = error
dotnet_diagnostic.IDE0052.severity = error
# Require file header
dotnet_diagnostic.IDE0073.severity = error
# Make methods synchronous (remove async modifier from methods without any await statements)
dotnet_diagnostic.IDE0390.severity = error
dotnet_diagnostic.IDE0391.severity = error

#
dotnet_diagnostic.CS1591.severity = none

# Xml project files
[*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]
indent_size = 2
Expand Down
2 changes: 2 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
See: https://learn.microsoft.com/nuget/reference/errors-and-warnings/nu1901-nu1904
-->
<WarningsNotAsErrors>$(WarningsNotAsErrors);NU1901;NU1902</WarningsNotAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

<PropertyGroup Condition="'$(BuildNative)' == 'true'">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public abstract class BaseAzureResourceService(
/// Gets the tenant resource for the specified subscription.
/// </summary>
/// <param name="tenantId">The tenant ID from the subscription</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None"/>.</param>
/// <returns>The tenant resource associated with the subscription</returns>
private async Task<TenantResource> GetTenantResourceAsync(Guid? tenantId, CancellationToken cancellationToken = default)
{
Expand Down Expand Up @@ -77,7 +77,7 @@ private async Task<bool> ValidateResourceGroupExistsAsync(SubscriptionResource s
/// <param name="tableName">Optional table name to query (default: "resources")</param>
/// <param name="additionalFilter">Optional additional KQL filter condition</param>
/// <param name="limit">Maximum number of results to return (default: 50)</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None"/>.</param>
/// <param name="tenant">Optional tenant to use for the query</param>
/// <returns>List of resources converted to the specified type</returns>
protected async Task<ResourceQueryResults<T>> ExecuteResourceQueryAsync<T>(
Expand Down Expand Up @@ -157,8 +157,10 @@ protected async Task<ResourceQueryResults<T>> ExecuteResourceQueryAsync<T>(
/// <param name="subscription">The subscription ID or name</param>
/// <param name="retryPolicy">Optional retry policy configuration</param>
/// <param name="converter">Function to convert JsonElement to the target type</param>
/// <param name="tableName">Optional table name to query (default: "resources")</param>
Comment thread
alzimmermsft marked this conversation as resolved.
/// <param name="additionalFilter">Optional additional KQL filter condition</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <param name="tenant">Optional tenant to use for the query</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None"/>.</param>
/// <returns>Single resource converted to the specified type, or null if not found</returns>
protected async Task<T?> ExecuteSingleResourceQueryAsync<T>(
string resourceType,
Expand All @@ -184,6 +186,7 @@ protected async Task<ResourceQueryResults<T>> ExecuteResourceQueryAsync<T>(
/// <param name="apiVersion">The API version to set for the specified resource type.</param>
/// <param name="tenant">Optional tenant to use when creating the client.</param>
/// <param name="retryPolicy">Optional retry policy used by token acquisition.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None"/>.</param>
/// <returns>An initialized <see cref="ArmClient"/> configured with the requested API version.</returns>
protected async Task<ArmClient> CreateArmClientWithApiVersionAsync(string resourceTypeForApiVersion, string apiVersion, string? tenant = null, RetryPolicyOptions? retryPolicy = null, CancellationToken cancellationToken = default)
{
Expand All @@ -198,7 +201,7 @@ protected async Task<ArmClient> CreateArmClientWithApiVersionAsync(string resour
/// </summary>
/// <param name="armClient">The ArmClient to use for the call.</param>
/// <param name="resourceIdentifier">The resource identifier of the resource to retrieve.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None"/>.</param>
/// <returns>The <see cref="GenericResource"/> instance for the requested resource.</returns>
/// <exception cref="ArgumentNullException">Thrown when a required parameter is null.</exception>
protected static async Task<GenericResource> GetGenericResourceAsync(ArmClient armClient, ResourceIdentifier resourceIdentifier, CancellationToken cancellationToken = default)
Expand All @@ -225,6 +228,7 @@ protected static async Task<GenericResource> GetGenericResourceAsync(ArmClient a
/// <param name="azureLocation">The Azure location for the resource.</param>
/// <param name="content">The content to create or update the resource with.</param>
/// <param name="jsonTypeInfo">The JSON type information for serialization.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None"/>.</param>
/// <returns>The <see cref="GenericResource"/> instance for the requested resource.</returns>
/// <exception cref="ArgumentNullException">Thrown when a required parameter is null.</exception>
/// <exception cref="InvalidOperationException">Thrown when the content is invalid.</exception>
Expand Down
15 changes: 9 additions & 6 deletions core/Azure.Mcp.Core/src/Services/Azure/BaseAzureService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
using Azure.Core.Pipeline;
using Azure.Mcp.Core.Services.Azure.Tenant;
using Azure.ResourceManager;
#pragma warning disable IDE0005 // using isn't used in release builds.
using Microsoft.Mcp.Core.Helpers;
#pragma warning restore IDE0005 // using isn't used in release builds.
using Microsoft.Mcp.Core.Options;
using Microsoft.Mcp.Core.Services.Azure;

Expand All @@ -31,7 +33,7 @@ public abstract class BaseAzureService
private static readonly string s_framework;
private static readonly string s_platform;
private static readonly string s_defaultUserAgent;
private static readonly TimeSpan? s_defaultPollInterval = null;
private static TimeSpan? s_defaultPollInterval = null;

static BaseAzureService()
{
Expand Down Expand Up @@ -165,13 +167,11 @@ protected static string EscapeKqlString(string value)

protected async Task<TokenCredential> GetCredential(CancellationToken cancellationToken)
{
// TODO @vukelich: separate PR for cancellationToken to be required, not optional default
return await GetCredential(null, cancellationToken);
}

protected async Task<TokenCredential> GetCredential(string? tenant, CancellationToken cancellationToken)
{
// TODO @vukelich: separate PR for cancellationToken to be required, not optional default
var tenantId = string.IsNullOrEmpty(tenant) ? null : await ResolveTenantIdAsync(tenant, cancellationToken);

try
Expand All @@ -188,7 +188,8 @@ protected async Task<TokenCredential> GetCredential(string? tenant, Cancellation
/// Gets an ARM access token for the given tenant using the ARM default scope.
/// </summary>
/// <param name="tenant">Optional tenant ID or name to authenticate against.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None"/>.</param>
/// <returns>An <see cref="AccessToken"/> for the ARM default scope.</returns>
protected async Task<AccessToken> GetArmAccessTokenAsync(string? tenant, CancellationToken cancellationToken)
{
var credential = await GetCredential(tenant, cancellationToken);
Expand Down Expand Up @@ -253,6 +254,8 @@ protected static T ConfigureRetryPolicy<T>(T clientOptions, RetryPolicyOptions?
/// <param name="tenantIdOrName">Optional Azure tenant ID or name.</param>
/// <param name="retryPolicy">Optional retry policy configuration.</param>
/// <param name="armClientOptions">Optional ARM client options.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None"/>.</param>
/// <returns>An initialized <see cref="ArmClient"/> instance.</returns>
protected async Task<ArmClient> CreateArmClientAsync(
string? tenantIdOrName = null,
RetryPolicyOptions? retryPolicy = null,
Expand Down Expand Up @@ -302,7 +305,7 @@ protected static void ValidateRequiredParameters(params (string name, string? va
/// </summary>
/// <typeparam name="T">The return type.</typeparam>
/// <param name="operation">The long-running operation.</param>
/// <param name="cancellationToken">The cancellation token that can cancel the request.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None"/>.</param>
/// <returns>The response once the long-running operation completes.</returns>
protected static async Task WaitForLroCompletionAsync<T>(Operation<T> operation, CancellationToken cancellationToken = default) where T : notnull
{
Expand All @@ -322,7 +325,7 @@ protected static async Task WaitForLroCompletionAsync<T>(Operation<T> operation,
/// Waits for the completion of a long-running operation, periodically polling the operation status until it completes.
/// </summary>
/// <param name="operation">The long-running operation.</param>
/// <param name="cancellationToken">The cancellation token that can cancel the request.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None"/>.</param>
/// <returns>The response once the long-running operation completes.</returns>
protected static async Task WaitForLroCompletionAsync(Operation operation, CancellationToken cancellationToken = default)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public interface ITenantService
/// <summary>
/// Gets the list of all available Azure tenants.
/// </summary>
/// <param name="cancellationToken">A token to cancel the operation.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>
/// A task representing the asynchronous operation, with a list of <see cref="TenantResource"/>
/// instances.
Expand All @@ -31,7 +31,7 @@ public interface ITenantService
/// Gets the tenant ID from either a tenant ID or tenant name.
/// </summary>
/// <param name="tenantIdOrName">The tenant ID or tenant name.</param>
/// <param name="cancellation">A cancellation token.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>
/// A task representing the asynchronous operation, with the tenant ID or <see langword="null"/>
/// if not found.
Expand All @@ -40,15 +40,15 @@ public interface ITenantService
/// Thrown when a tenant with the specified name is not found.
/// </exception>
/// <exception cref="InvalidOperationException">
/// Thrown when the tenant has a <see langword="null"> TenantId.
/// Thrown when the tenant has a <see langword="null"/> TenantId.
/// </exception>
Task<string> GetTenantId(string tenantIdOrName, CancellationToken cancellationToken);

/// <summary>
/// Gets the tenant ID by tenant name.
/// </summary>
/// <param name="tenantName">The tenant name.</param>
/// <param name="cancellation">A cancellation token.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>
/// A task representing the asynchronous operation, with the tenant ID or <see langword="null"/>
/// if not found.
Expand All @@ -57,23 +57,23 @@ public interface ITenantService
/// Thrown when a tenant with the specified name is not found.
/// </exception>
/// <exception cref="InvalidOperationException">
/// Thrown when the tenant has a <see langword="null"> TenantId.
/// Thrown when the tenant has a <see langword="null"/> TenantId.
/// </exception>
Task<string> GetTenantIdByName(string tenantName, CancellationToken cancellationToken);

/// <summary>
/// Gets the tenant name by tenant ID.
/// </summary>
/// <param name="tenantId">The tenant ID.</param>
/// <param name="cancellation">A cancellation token.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>
/// A task representing the asynchronous operation, with the tenant name or <see langword="null"/> if not found.
/// </returns>
/// <exception cref="Exception">
/// Thrown when a tenant with the specified ID is not found.
/// </exception>
/// <exception cref="InvalidOperationException">
/// Thrown when the tenant has a <see langword="null"> DisplayName.
/// Thrown when the tenant has a <see langword="null"/> DisplayName.
/// </exception>
Task<string> GetTenantNameById(string tenantId, CancellationToken cancellationToken);

Expand All @@ -90,7 +90,7 @@ public interface ITenantService
/// Gets an instance of <see cref="TokenCredential"/>.
/// </summary>
/// <param name="tenantId">Optional tenant ID. Use <see langword="null"/> in most cases.</param>
/// <param name="cancellation">A cancellation token.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>
/// A task representing the asynchronous operation, with a value of <see cref="TokenCredential"/>.
/// </returns>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public async Task<List<TenantResource>> GetTenants(CancellationToken cancellatio
var options = AddDefaultPolicies(new ArmClientOptions());
options.Transport = new HttpClientTransport(GetClient());
options.Environment = CloudConfiguration.ArmEnvironment;
var client = new ArmClient(await GetCredential(cancellationToken), default, options);
var client = new ArmClient(await GetTokenCredentialAsync(null, cancellationToken), default, options);

await foreach (var tenant in client.GetTenants().WithCancellation(cancellationToken))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ public static class TenantServiceCollectionExtensions
/// <list type="bullet">
/// <item>
/// <description>
/// <see cref="IAzureTokenCredentialProvider"/> via <see cref="Authentication.AuthenticationServiceCollectionExtensions.AddSingleIdentityTokenCredentialProvider"/>.
/// This can be overridden using <see cref="Authentication.AuthenticationServiceCollectionExtensions.AddAzureTokenCredentialProvider"/>
Comment thread
alzimmermsft marked this conversation as resolved.
/// based on parsed command line arguments and environment variables.
/// <see cref="IAzureTokenCredentialProvider"/> via <see cref="Microsoft.Mcp.Core.Services.Azure.Authentication.AuthenticationServiceCollectionExtensions.AddSingleIdentityTokenCredentialProvider(IServiceCollection)"/>.
/// </description>
/// </item>
/// <item>
/// <see cref="ITenantService"/> via <see cref="TenantService"/>.
/// </item>
/// </list>
/// </remarks>
public static IServiceCollection AddAzureTenantService(this IServiceCollection services)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Net;
using System.Net.Http.Headers;
using Azure.Core;
using Microsoft.Mcp.Core;
Comment thread
alzimmermsft marked this conversation as resolved.
using Microsoft.Mcp.Core.Services.Azure.Authentication;
using NSubstitute;
using Xunit;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
using Microsoft.Mcp.Core.Services.Telemetry;
using Microsoft.Mcp.Core.Services.Time;
using NSubstitute;
using ExtensionsOptions = Microsoft.Extensions.Options;

namespace Azure.Mcp.Core.Tests.Areas.Server;

Expand Down Expand Up @@ -106,7 +107,7 @@ public static ICommandFactory CreateCommandFactory(IServiceProvider? serviceProv

var services = serviceProvider ?? CreateDefaultServiceProvider();
var logger = services.GetRequiredService<ILogger<CommandFactory>>();
var configurationOptions = Microsoft.Extensions.Options.Options.Create(new McpServerConfiguration
var configurationOptions = ExtensionsOptions.Options.Create(new McpServerConfiguration
{
Name = "Test Server",
ShortName = "test",
Expand Down
Loading
Loading