diff --git a/core/Microsoft.Mcp.Core/src/Commands/BaseCommand`2.cs b/core/Microsoft.Mcp.Core/src/Commands/BaseCommand`2.cs
index cafdbecdaa..d4fc94b9a2 100644
--- a/core/Microsoft.Mcp.Core/src/Commands/BaseCommand`2.cs
+++ b/core/Microsoft.Mcp.Core/src/Commands/BaseCommand`2.cs
@@ -121,15 +121,8 @@ protected virtual void HandleException(CommandContext context, Exception ex)
if (ex is CommandValidationException cve)
{
response.Status = cve.StatusCode;
- // If specific missing options are provided, format a consistent message
- if (cve.MissingOptions is { Count: > 0 })
- {
- response.Message = $"{MissingRequiredOptionsPrefix}{string.Join(", ", cve.MissingOptions)}";
- }
- else
- {
- response.Message = cve.Message;
- }
+ response.Message = cve.Message;
+
// Include the command validation exception message as it should be safe. Requires custom validators to
// exclude any sensitive information from their error messages.
context.Activity?.SetTag(TagName.ExceptionMessage, response.Message);
diff --git a/core/Microsoft.Mcp.Core/src/Options/OptionAttribute.cs b/core/Microsoft.Mcp.Core/src/Options/OptionAttribute.cs
index 5d96f8d662..7b96ac86f4 100644
--- a/core/Microsoft.Mcp.Core/src/Options/OptionAttribute.cs
+++ b/core/Microsoft.Mcp.Core/src/Options/OptionAttribute.cs
@@ -18,22 +18,6 @@ namespace Microsoft.Mcp.Core.Options;
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
public sealed class OptionAttribute : Attribute
{
- ///
- /// Initializes a new instance with default values.
- /// Use named properties to override: [Option(Name = "x", Description = "y", Hidden = true)].
- ///
- public OptionAttribute() { }
-
- ///
- /// Initializes a new instance with a description.
- /// Shorthand for [Option(Description = "...")].
- ///
- /// A description of what the option controls.
- public OptionAttribute(string description)
- {
- Description = description;
- }
-
///
/// Override the CLI option name (without the "--" prefix).
/// When null, the name is derived from the property name in kebab-case.
@@ -48,7 +32,7 @@ public OptionAttribute(string description)
///
/// A description of what the option controls. Used in help text and by AI agents.
///
- public string? Description { get; init; }
+ public required string Description { get; init; }
///
/// A default value for the option when a value is not provided. Must match the property type being attributed.
@@ -56,7 +40,12 @@ public OptionAttribute(string description)
public object? DefaultValue { get; init; }
///
- /// Whether the option is hidden from help output.
+ /// Whether the option is hidden from help output. Default is false.
+ ///
+ public bool Hidden { get; init; } = false;
+
+ ///
+ /// Whether the option allows an empty or whitespace-only string as a valid value. Default handling is to reject such values.
///
- public bool Hidden { get; init; }
+ public bool AllowEmptyOrWhiteSpaceString { get; init; } = false;
}
diff --git a/core/Microsoft.Mcp.Core/src/Options/OptionBinder.cs b/core/Microsoft.Mcp.Core/src/Options/OptionBinder.cs
index 6591abeb60..74a557bed4 100644
--- a/core/Microsoft.Mcp.Core/src/Options/OptionBinder.cs
+++ b/core/Microsoft.Mcp.Core/src/Options/OptionBinder.cs
@@ -51,7 +51,7 @@ public static class OptionBinder
{
var instance = (TOptions)CreateInstance(typeof(TOptions));
List missingOptions = [];
- List errors = [];
+ List errors = [.. parseResult.Errors.Select(e => e.Message)];
Dictionary? parentInstances = null;
var handlers = s_optionTypeHandlers.GetOrAdd(typeof(TOptions), _ => GetOptionTypeHandlers());
diff --git a/core/Microsoft.Mcp.Core/src/Options/OptionContainerAttribute.cs b/core/Microsoft.Mcp.Core/src/Options/OptionContainerAttribute.cs
new file mode 100644
index 0000000000..95714c02be
--- /dev/null
+++ b/core/Microsoft.Mcp.Core/src/Options/OptionContainerAttribute.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace Microsoft.Mcp.Core.Options;
+
+///
+/// Attribute for complex option containers (aka, classes that are used in option definitions that contain options themselves).
+///
+/// Properties with this attribute must be complex types that contain other options.
+///
+///
+[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
+public sealed class OptionContainerAttribute : Attribute
+{
+ ///
+ /// The prefix to use for the options in the container.
+ /// If null, the property name (in kebab-case) is used as the prefix.
+ ///
+ /// For example, if the property is named "Vault" and the prefix is "v", the options in the container will be prefixed with "--v-".
+ /// If the property is named "Vault" and the prefix is null, the options in the container will be prefixed with "--vault-".
+ ///
+ ///
+ public string? Prefix { get; init; }
+}
diff --git a/core/Microsoft.Mcp.Core/src/Options/OptionDescriptor.cs b/core/Microsoft.Mcp.Core/src/Options/OptionDescriptor.cs
index d63ba6b3b6..e3be42b52d 100644
--- a/core/Microsoft.Mcp.Core/src/Options/OptionDescriptor.cs
+++ b/core/Microsoft.Mcp.Core/src/Options/OptionDescriptor.cs
@@ -11,10 +11,11 @@ public class OptionDescriptor
{
public required string Name { get; init; }
public required string Description { get; init; }
- public string[] Aliases { get; init; } = [];
+ public required string[] Aliases { get; init; }
public bool Required { get; init; }
public bool Hidden { get; init; }
public object? DefaultValue { get; init; }
+ public bool AllowEmptyOrWhiteSpaceString { get; init; }
public required PropertyInfo TargetProperty { get; init; }
public required Type Type { get; init; }
public PropertyInfo? ParentProperty { get; init; }
@@ -22,14 +23,19 @@ public class OptionDescriptor
public static OptionDescriptor[] FromType<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>() where T : class
{
List optionDescriptors = [];
- NullabilityInfoContext nullabilityContext = new();
- CollectDescriptors(typeof(T), prefix: null, optionDescriptors, nullabilityContext);
+ CollectDescriptors(typeof(T), null, optionDescriptors, new(), true, null);
return [.. optionDescriptors];
}
[UnconditionalSuppressMessage("Trimming", "IL2070:UnrecognizedReflectionPattern",
Justification = "Nested option types are rooted by the application.")]
- private static void CollectDescriptors(Type type, string? prefix, List descriptors, NullabilityInfoContext nullabilityContext, bool parentRequired = true, PropertyInfo? parentProperty = null)
+ private static void CollectDescriptors(
+ Type type,
+ string? prefix,
+ List descriptors,
+ NullabilityInfoContext nullabilityContext,
+ bool parentRequired,
+ PropertyInfo? parentProperty)
{
PropertyInfo[] allProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
@@ -53,28 +59,27 @@ private static void CollectDescriptors(Type type, string? prefix, List();
- // Only include properties with [Option]
- if (optionAttribute == null)
+ var optionAttribute = property.GetCustomAttribute();
+ var optionContainerAttribute = property.GetCustomAttribute();
+ // Only include properties with [Option] or [OptionContainer]
+ if (optionAttribute == null && optionContainerAttribute == null)
{
continue;
}
- string name = optionAttribute?.Name ?? OptionNameConvention.ToKebabCase(property.Name);
-
- if (!string.IsNullOrEmpty(prefix))
+ if (optionAttribute != null && optionContainerAttribute != null)
{
- name = $"{prefix}-{name}";
+ throw new InvalidOperationException("Properties can only be attributed with [Option] or [OptionContainer], not both.");
}
var required = Attribute.IsDefined(property, typeof(RequiredMemberAttribute));
- if (IsComplexType(property.PropertyType))
- {
- // Flatten nested complex types with a prefix.
- CollectDescriptors(property.PropertyType, name, descriptors, nullabilityContext, parentRequired: required, parentProperty: property);
- }
- else
+ var complex = IsComplexType(property.PropertyType);
+ if (optionAttribute != null)
{
+ if (complex)
+ {
+ throw new InvalidOperationException("Complex properties cannot use [Option] attribute. Use [OptionContainer] instead.");
+ }
if (!parentRequired && required)
{
throw new InvalidOperationException(
@@ -86,24 +91,45 @@ private static void CollectDescriptors(Type type, string? prefix, List
- Type underlying = Nullable.GetUnderlyingType(type) ?? type;
+ var underlying = Nullable.GetUnderlyingType(type) ?? type;
if (IsScalarType(underlying))
{
@@ -114,7 +140,7 @@ private static bool IsComplexType(Type type)
// String is an IEnumerable, but we treat it as a scalar type, so it will be handled above.
if (underlying.IsArray || underlying.IsAssignableTo(typeof(System.Collections.IEnumerable)))
{
- Type? elementType = GetCollectionElementType(underlying);
+ var elementType = GetCollectionElementType(underlying);
if (elementType is not null && !IsScalarType(elementType))
{
throw new InvalidOperationException(
@@ -130,7 +156,7 @@ private static bool IsComplexType(Type type)
private static bool IsScalarType(Type type)
{
- Type underlying = Nullable.GetUnderlyingType(type) ?? type;
+ var underlying = Nullable.GetUnderlyingType(type) ?? type;
return underlying.IsPrimitive ||
underlying.IsEnum ||
underlying == typeof(string) ||
@@ -156,17 +182,4 @@ private static bool IsScalarType(Type type)
.Select(i => i.GetGenericArguments()[0])
.FirstOrDefault();
}
-
- private static bool IsNullable(PropertyInfo property, NullabilityInfoContext nullabilityContext)
- {
- // Nullable value types (e.g., TimeSpan?)
- if (Nullable.GetUnderlyingType(property.PropertyType) is not null)
- {
- return true;
- }
-
- // Nullable reference types (e.g., string?)
- NullabilityInfo nullabilityInfo = nullabilityContext.Create(property);
- return nullabilityInfo.WriteState == NullabilityState.Nullable;
- }
}
diff --git a/core/Microsoft.Mcp.Core/src/Options/OptionTypeHandler.cs b/core/Microsoft.Mcp.Core/src/Options/OptionTypeHandler.cs
index b9f91b79a4..469d015109 100644
--- a/core/Microsoft.Mcp.Core/src/Options/OptionTypeHandler.cs
+++ b/core/Microsoft.Mcp.Core/src/Options/OptionTypeHandler.cs
@@ -125,13 +125,14 @@ private static (Option, Func)? CreateOptionAndBinder(
// Enums are handled as a string with additional binding.
if (typeof(string) == type || type.IsEnum)
{
+ Action? validateEmptyOrWhiteSpace = descriptor.AllowEmptyOrWhiteSpaceString ? null : ValidateEmptyOrWhiteSpace;
if (isNullable && isMulti)
- return CreateOptionAndBinderHelper(descriptor);
+ return CreateOptionAndBinderHelper(descriptor, validateEmptyOrWhiteSpace);
if (isMulti)
- return CreateOptionAndBinderHelper(descriptor);
+ return CreateOptionAndBinderHelper(descriptor, validateEmptyOrWhiteSpace);
if (isNullable)
- return CreateOptionAndBinderHelper(descriptor);
- return CreateOptionAndBinderHelper(descriptor);
+ return CreateOptionAndBinderHelper(descriptor, validateEmptyOrWhiteSpace);
+ return CreateOptionAndBinderHelper(descriptor, validateEmptyOrWhiteSpace);
}
if (typeof(bool) == type)
{
@@ -306,13 +307,19 @@ private static (Option, Func)? CreateOptionAndBinder(
return null;
}
- private static (Option, Func) CreateOptionAndBinderHelper(OptionDescriptor descriptor)
+ private static (Option, Func) CreateOptionAndBinderHelper(
+ OptionDescriptor descriptor,
+ Action? emptyOrWhiteSpaceValidator = null)
{
var option = new Option($"--{descriptor.Name}", [.. descriptor.Aliases.Select(a => $"--{a}")]);
if (descriptor.DefaultValue != null)
{
option.DefaultValueFactory = _ => (T)descriptor.DefaultValue;
}
+ if (emptyOrWhiteSpaceValidator != null)
+ {
+ option.Validators.Add(emptyOrWhiteSpaceValidator);
+ }
return (option, parseResult => parseResult.GetValueOrDefaultWithoutName(option));
}
@@ -326,4 +333,20 @@ private static ArgumentArity GetArgumentArity(Type type, bool isNullable, bool i
return ArgumentArity.ZeroOrOne;
return typeof(bool) == type ? ArgumentArity.ZeroOrOne : ArgumentArity.ExactlyOne;
}
+
+ private static void ValidateEmptyOrWhiteSpace(OptionResult result)
+ {
+ // Calling on an Option that isn't required, has a default, or doesn't allow values to be passed skips checking.
+ // This matches previous behavior.
+ var option = result.Option;
+ if (!option.Required || option.HasDefaultValue || option.Arity.MaximumNumberOfValues == 0)
+ {
+ return;
+ }
+
+ if (result.Tokens is not { Count: > 0 } || result.Tokens.Any(t => string.IsNullOrWhiteSpace(t.Value)))
+ {
+ result.AddError("Option was configured to require non-empty, non-whitespace values but one or more empty or whitespace values were provided.");
+ }
+ }
}
diff --git a/core/Microsoft.Mcp.Core/tests/Microsoft.Mcp.Core.Tests/Options/OptionBinderTests.cs b/core/Microsoft.Mcp.Core/tests/Microsoft.Mcp.Core.Tests/Options/OptionBinderTests.cs
index efe6d7aed4..313df33066 100644
--- a/core/Microsoft.Mcp.Core/tests/Microsoft.Mcp.Core.Tests/Options/OptionBinderTests.cs
+++ b/core/Microsoft.Mcp.Core/tests/Microsoft.Mcp.Core.Tests/Options/OptionBinderTests.cs
@@ -16,37 +16,37 @@ public sealed class OptionBinderTests
private sealed class StringOptions
{
- [Option]
+ [Option(Description = "The name of the item.")]
public string? Name { get; set; }
- [Option]
+ [Option(Description = "The description of the item.")]
public string? Description { get; set; }
}
private sealed class IntOptions
{
- [Option]
+ [Option(Description = "The number of items.")]
public int Count { get; set; }
- [Option]
+ [Option(Description = "The maximum number of items.")]
public int? Limit { get; set; }
}
private sealed class BoolOptions
{
- [Option]
+ [Option(Description = "Whether to display verbose output.")]
public bool Verbose { get; set; }
- [Option]
+ [Option(Description = "Whether to enable debug mode.")]
public bool? Debug { get; set; }
}
private sealed class ArrayOptions
{
- [Option]
+ [Option(Description = "The tags associated with the item.")]
public required string[] Tags { get; set; }
- [Option]
+ [Option(Description = "The ports associated with the item.")]
public required int[] Ports { get; set; }
- [Option]
+ [Option(Description = "The tags associated with the item.")]
public string[]? NullableTags { get; set; }
- [Option]
+ [Option(Description = "The ports associated with the item.")]
public int[]? NullablePorts { get; set; }
}
@@ -59,100 +59,131 @@ private enum Color
private sealed class EnumOptions
{
- [Option]
+ [Option(Description = "The color of the item.")]
public Color Color { get; set; }
- [Option]
+ [Option(Description = "The background color of the item.")]
public Color? Background { get; set; }
}
private sealed class ArrayEnumOptions
{
- [Option]
+ [Option(Description = "The colors of the item.")]
public required Color[] Colors { get; set; }
- [Option]
+ [Option(Description = "The background colors of the item.")]
public Color[]? Backgrounds { get; set; }
}
private sealed class GuidOptions
{
- [Option]
+ [Option(Description = "The ID of the item.")]
public Guid Id { get; set; }
- [Option]
+ [Option(Description = "The correlation ID of the item.")]
public Guid? CorrelationId { get; set; }
}
private sealed class DateTimeOptions
{
- [Option]
+ [Option(Description = "The start date of the item.")]
public DateTime StartDate { get; set; }
- [Option]
+ [Option(Description = "The timestamp of the item.")]
public DateTimeOffset? Timestamp { get; set; }
}
private sealed class DecimalOptions
{
- [Option]
+ [Option(Description = "The price of the item.")]
public decimal Price { get; set; }
- [Option]
+ [Option(Description = "The rate of the item.")]
public double? Rate { get; set; }
}
private sealed class UnsupportedTypeOptions
{
- [Option]
+ [Option(Description = "The value of the item.")]
public object[]? Value { get; set; }
}
private sealed class NetworkSettings
{
- [Option]
+ [Option(Description = "The host of the item.")]
public string? Host { get; set; }
- [Option]
+ [Option(Description = "The port of the item.")]
public int? Port { get; set; }
}
private sealed class RequiredNetworkSettings
{
- [Option]
+ [Option(Description = "The name of the item.")]
public required string Name { get; set; }
- [Option]
+ [Option(Description = "The port of the item.")]
public int? Port { get; set; }
}
private sealed class NestedOptional
{
- [Option]
+ [Option(Description = "The name of the item.")]
public string? Name { get; set; }
- [Option]
+ [OptionContainer]
public NetworkSettings? Optional { get; set; }
- [Option]
+ [OptionContainer]
public required NetworkSettings Required { get; set; }
}
private sealed class NestedRequired
{
- [Option]
+ [Option(Description = "The name of the item.")]
public string? Name { get; set; }
- [Option]
+ [OptionContainer]
public required RequiredNetworkSettings Required { get; set; }
}
private sealed class AliasedOptions
{
- [Option(Name = "name", Aliases = ["n", "nm"])]
+ [Option(Description = "The name of the item.", Name = "name", Aliases = ["n", "nm"])]
public required string Name { get; set; }
- [Option(Name = "address", Aliases = ["a", "addr"])]
+ [Option(Description = "The address of the item.", Name = "address", Aliases = ["a", "addr"])]
public string? Address { get; set; }
}
private sealed class DefaultedOptions
{
- [Option(DefaultValue = "default")]
+ [Option(Description = "The default name.", DefaultValue = "default")]
public string? Name { get; set; }
- [Option(DefaultValue = 42)]
+ [Option(Description = "The default count.", DefaultValue = 42)]
public int? Count { get; set; }
}
+ private sealed class EmptyOrWhiteSpaceOptions
+ {
+ [Option(Description = "The name of the item.")]
+ public required string Name { get; set; }
+ [Option(Description = "The description of the item.", AllowEmptyOrWhiteSpaceString = true)]
+ public required string Description { get; set; }
+ [Option(Description = "The tags of the item.")]
+ public required string[] Tags { get; set; }
+ [Option(Description = "The notes of the item.", AllowEmptyOrWhiteSpaceString = true)]
+ public required string[] Notes { get; set; }
+ }
+
+ private sealed class InvalidAttributesCombinationOptions
+ {
+ [Option(Description = "Invalid attribute combination.")]
+ [OptionContainer]
+ public NetworkSettings? Invalid { get; set; }
+ }
+
+ private sealed class InvalidOptionAttributeOptions
+ {
+ [Option(Description = "Invalid option attribute.")]
+ public NetworkSettings? Invalid { get; set; }
+ }
+
+ private sealed class InvalidOptionContainerAttributeOptions
+ {
+ [OptionContainer]
+ public string? Invalid { get; set; }
+ }
+
#endregion
#region RegisterOptions Tests
@@ -236,6 +267,39 @@ public void RegisterOptions_UnsupportedType_Throws()
Assert.Contains("non-scalar element type", ex.Message);
}
+ [Fact]
+ public void RegisterOptions_InvalidAttributes_Throws()
+ {
+ var command = new Command("test");
+
+ var ex = Assert.Throws(
+ () => OptionBinder.RegisterOptions(command));
+
+ Assert.Contains("Properties can only be attributed with [Option] or [OptionContainer], not both.", ex.Message);
+ }
+
+ [Fact]
+ public void RegisterOptions_InvalidOptionAttribute_Throws()
+ {
+ var command = new Command("test");
+
+ var ex = Assert.Throws(
+ () => OptionBinder.RegisterOptions(command));
+
+ Assert.Contains("Complex properties cannot use [Option] attribute. Use [OptionContainer] instead.", ex.Message);
+ }
+
+ [Fact]
+ public void RegisterOptions_InvalidOptionContainerAttribute_Throws()
+ {
+ var command = new Command("test");
+
+ var ex = Assert.Throws(
+ () => OptionBinder.RegisterOptions(command));
+
+ Assert.Contains("Non-complex properties cannot use [OptionContainer] attribute. Use [Option] instead.", ex.Message);
+ }
+
#endregion
#region BindOptions Tests
@@ -394,6 +458,18 @@ public void BindOptions_NullableArrayEnum_NullWhenNotProvided()
Assert.Null(options.Backgrounds);
}
+ [Fact]
+ public void BindOptions_InvalidEnumValue_Throws()
+ {
+ var command = new Command("test");
+ OptionBinder.RegisterOptions(command);
+
+ var parseResult = command.Parse("--color Invalid");
+ var ex = Assert.Throws(() => OptionBinder.BindOptions(parseResult));
+
+ Assert.Contains("Argument 'Invalid' not recognized. Must be one of:", ex.Message);
+ }
+
[Fact]
public void BindOptions_Guid_BindsValue()
{
@@ -610,6 +686,34 @@ public void BindOptions_NestedRequired_BindsDeepValues()
Assert.Equal(5432, options.Required.Port);
}
+ [Fact]
+ public void BindOptions_EmptyOrWhiteSpace_Allowed()
+ {
+ var command = new Command("test");
+ OptionBinder.RegisterOptions(command);
+
+ var parseResult = command.Parse("--name John --description \" \" --tags updated --notes \"\"");
+ var options = OptionBinder.BindOptions(parseResult);
+
+ Assert.Equal(" ", options.Description);
+ Assert.Single(options.Notes);
+ Assert.Equal("", options.Notes[0]);
+ }
+
+ [Theory]
+ [InlineData("--name \" \" --description description --tags updated --notes notes")]
+ [InlineData("--name John --description description --tags \"\" --notes notes")]
+ public void BindOptions_EmptyOrWhiteSpace_Rejected(string args)
+ {
+ var command = new Command("test");
+ OptionBinder.RegisterOptions(command);
+
+ var parseResult = command.Parse(args);
+ var exception = Assert.Throws(() => OptionBinder.BindOptions(parseResult));
+
+ Assert.Contains("require non-empty, non-whitespace values", exception.Message);
+ }
+
#endregion
#region Trimming Annotation Tests
diff --git a/core/Microsoft.Mcp.Core/tests/Microsoft.Mcp.Tests/Client/CommandTestsBase.cs b/core/Microsoft.Mcp.Core/tests/Microsoft.Mcp.Tests/Client/CommandTestsBase.cs
index c81d049f02..9ad8c49e0e 100644
--- a/core/Microsoft.Mcp.Core/tests/Microsoft.Mcp.Tests/Client/CommandTestsBase.cs
+++ b/core/Microsoft.Mcp.Core/tests/Microsoft.Mcp.Tests/Client/CommandTestsBase.cs
@@ -40,6 +40,11 @@ public void SetArguments(params string[] arguments)
public virtual async ValueTask InitializeAsync()
{
+ // load settings first to determine test mode
+ await LoadSettingsAsync();
+
+ CheckLiveTestOnly(TestMethodResolver.TryResolveCurrentMethodInfo());
+
await InitializeAsyncInternal(null);
}
@@ -121,11 +126,6 @@ protected virtual async ValueTask LoadSettingsAsync()
protected virtual async ValueTask InitializeAsyncInternal(TestProxyFixture? proxy = null)
{
- await LoadSettingsAsync();
-
- // Check that the test is allowed to run in the current mode
- CheckLiveOnly();
-
// Use custom arguments if provided, otherwise use standard mode (debug can be enabled via environment variable)
var debugEnvVar = Environment.GetEnvironmentVariable("AZURE_MCP_TEST_DEBUG");
var enableDebug = string.Equals(debugEnvVar, "true", StringComparison.OrdinalIgnoreCase) || Settings.DebugOutput;
@@ -285,4 +285,13 @@ protected virtual void Dispose(bool disposing)
// subclasses should override this method to dispose async resources
// overrides should still call base.DisposeAsyncCore()
protected virtual ValueTask DisposeAsyncCore() => ValueTask.CompletedTask;
+
+ protected void CheckLiveTestOnly(MethodInfo? methodInfo)
+ {
+ // skip tests marked [LiveTestOnly] when not in Live mode
+ if (TestMode != TestMode.Live && methodInfo?.GetCustomAttribute() != null)
+ {
+ Assert.Skip("Test is marked [LiveTestOnly] and cannot run in Playback or Record mode.");
+ }
+ }
}
diff --git a/core/Microsoft.Mcp.Core/tests/Microsoft.Mcp.Tests/Client/RecordedCommandTestsBase.cs b/core/Microsoft.Mcp.Core/tests/Microsoft.Mcp.Tests/Client/RecordedCommandTestsBase.cs
index 76526978a1..910a2c7be4 100644
--- a/core/Microsoft.Mcp.Core/tests/Microsoft.Mcp.Tests/Client/RecordedCommandTestsBase.cs
+++ b/core/Microsoft.Mcp.Core/tests/Microsoft.Mcp.Tests/Client/RecordedCommandTestsBase.cs
@@ -172,8 +172,11 @@ public override async ValueTask InitializeAsync()
// load settings first to determine test mode
await LoadSettingsAsync();
- // check if the test is allowed to run in the current mode
- CheckLiveOnly();
+ // resolve the current test method once for all attribute checks
+ var methodInfo = TestMethodResolver.TryResolveCurrentMethodInfo();
+
+ // skip tests marked [LiveTestOnly] when not in Live mode
+ CheckLiveTestOnly(methodInfo);
if (fixture.Proxy == null)
{
diff --git a/docs/option-conversion.md b/docs/option-conversion.md
index 640460147a..cdd6bb3423 100644
--- a/docs/option-conversion.md
+++ b/docs/option-conversion.md
@@ -32,25 +32,25 @@ The new pattern uses `[Option]` attributes on a flat options POCO. `OptionBinder
// NEW: options are just a POCO with attributes
public class BlobUploadOptions : ISubscriptionOption
{
- [Option("The name of the Azure Storage account.")]
+ [Option(Description = "The name of the Azure Storage account.")]
public required string Account { get; set; }
- [Option("The name of the container within the storage account.")]
+ [Option(Description = "The name of the container within the storage account.")]
public required string Container { get; set; }
- [Option("The blob name/path within the container.")]
+ [Option(Description = "The blob name/path within the container.")]
public required string Blob { get; set; }
- [Option("The local file path to read content from.")]
+ [Option(Description = "The local file path to read content from.")]
public required string LocalFilePath { get; set; }
- [Option(OptionDescriptions.Subscription)]
+ [Option(Description = OptionDescriptions.Subscription)]
public string? Subscription { get; set; }
- [Option(OptionDescriptions.Tenant)]
+ [Option(Description = OptionDescriptions.Tenant)]
public string? Tenant { get; set; }
- [Option(Name = "retry")]
+ [OptionContainer(Prefix = "retry")]
public RetryPolicyOptions? RetryPolicy { get; set; }
}
```
@@ -109,10 +109,10 @@ Replace the options class hierarchy with a single flat POCO that implements `ISu
**Conventions:**
- **Name**: Derived automatically from the property name in kebab-case (e.g., `LocalFilePath` → `--local-file-path`). Only use `[Option(Name = "...")]` when the convention doesn't produce the desired name (e.g., `RetryPolicy` → `--retry` instead of `--retry-policy`). **Do not** specify `Name =` when it matches the default.
-- **Required**: Determined by nullability. Use `required` keyword or non-nullable types for required options. Use `?` for optional.
-- **Description**: Pass as the constructor argument `[Option("description")]` or via `[Option(Description = "...")]`.
+- **Required**: Driven by the `required` keyword (`RequiredMemberAttribute`). Use `required` on required options; use nullable types (`?`) for optional options.
+- **Description**: Always required, passed using attribute properties: `[Option(Description = "description")]`.
- **Shared descriptions**: Use constants from `OptionDescriptions` (e.g., `OptionDescriptions.Subscription`, `OptionDescriptions.Tenant`).
-- **Nested objects**: Use `[Option(Name = "prefix")]` on a property of a complex type. Its child properties become `--prefix-child-name`. Example: `RetryPolicyOptions` with `[Option(Name = "retry")]` produces `--retry-delay`, `--retry-max-retries`, etc.
+- **Nested objects**: Use `[OptionContainer(Prefix = "prefix")]` on a property of a complex type. Its child properties become `--prefix-child-name`. Example: `RetryPolicyOptions` with `[OptionContainer(Prefix = "retry")]` produces `--retry-delay`, `--retry-max-retries`, etc.
- **Property ordering**: List command-specific options first, then sink common/infrastructure options to the bottom in this order: `ResourceGroup`, `Subscription`, `Tenant`, `AuthMethod`, `RetryPolicy`. This keeps the most relevant options visible at a glance.
**Before (hierarchy of 6 classes):**
@@ -129,25 +129,25 @@ GlobalOptions → Tenant, AuthMethod, RetryPolicy
```csharp
public class BlobUploadOptions : ISubscriptionOption
{
- [Option("The name of the Azure Storage account.")]
+ [Option(Description = "The name of the Azure Storage account.")]
public required string Account { get; set; }
- [Option("The name of the container within the storage account.")]
+ [Option(Description = "The name of the container within the storage account.")]
public required string Container { get; set; }
- [Option("The blob name/path within the container.")]
+ [Option(Description = "The blob name/path within the container.")]
public required string Blob { get; set; }
- [Option("The local file path to read content from.")]
+ [Option(Description = "The local file path to read content from.")]
public required string LocalFilePath { get; set; }
- [Option(OptionDescriptions.Subscription)]
+ [Option(Description = OptionDescriptions.Subscription)]
public string? Subscription { get; set; }
- [Option(OptionDescriptions.Tenant)]
+ [Option(Description = OptionDescriptions.Tenant)]
public string? Tenant { get; set; }
- [Option(Name = "retry")]
+ [OptionContainer(Prefix = "retry")]
public RetryPolicyOptions? RetryPolicy { get; set; }
}
```
@@ -279,25 +279,25 @@ The options class stays flat — no inheritance. It just implements the interfac
```csharp
public class BlobUploadOptions : ISubscriptionOption, IBlobOption
{
- [Option("The name of the Azure Storage account.")]
+ [Option(Description = "The name of the Azure Storage account.")]
public required string Account { get; set; }
- [Option("The name of the container within the storage account.")]
+ [Option(Description = "The name of the container within the storage account.")]
public required string Container { get; set; }
- [Option("The blob name/path within the container.")]
+ [Option(Description = "The blob name/path within the container.")]
public required string Blob { get; set; }
- [Option("The local file path to read content from.")]
+ [Option(Description = "The local file path to read content from.")]
public required string LocalFilePath { get; set; }
- [Option(OptionDescriptions.Subscription)]
+ [Option(Description = OptionDescriptions.Subscription)]
public string? Subscription { get; set; }
- [Option(OptionDescriptions.Tenant)]
+ [Option(Description = OptionDescriptions.Tenant)]
public string? Tenant { get; set; }
- [Option(Name = "retry")]
+ [OptionContainer(Prefix = "retry")]
public RetryPolicyOptions? RetryPolicy { get; set; }
}
```
diff --git a/servers/Azure.Mcp.Server/changelog-entries/1782228036890.yaml b/servers/Azure.Mcp.Server/changelog-entries/1782228036890.yaml
new file mode 100644
index 0000000000..99c78bb9fb
--- /dev/null
+++ b/servers/Azure.Mcp.Server/changelog-entries/1782228036890.yaml
@@ -0,0 +1,4 @@
+pr: 2922
+changes:
+ - section: "Breaking Changes"
+ description: "Removed unused auth-method parameter from Kusto and Storage tools."
diff --git a/servers/Azure.Mcp.Server/docs/new-command.md b/servers/Azure.Mcp.Server/docs/new-command.md
index caec920ef5..0c3eaab4f3 100644
--- a/servers/Azure.Mcp.Server/docs/new-command.md
+++ b/servers/Azure.Mcp.Server/docs/new-command.md
@@ -487,19 +487,19 @@ Options classes are flat POCOs with `[Option]` attributes. Registration and bind
```csharp
public class {Resource}{Operation}Options : ISubscriptionOption
{
- [Option("Description of the required option.")]
+ [Option(Description = "Description of the required option.")]
public required string RequiredOption { get; set; }
- [Option("Description of the optional option.")]
+ [Option(Description = "Description of the optional option.")]
public string? OptionalOption { get; set; }
- [Option(OptionDescriptions.Subscription)]
+ [Option(Description = OptionDescriptions.Subscription)]
public string? Subscription { get; set; }
- [Option(OptionDescriptions.Tenant)]
+ [Option(Description = OptionDescriptions.Tenant)]
public string? Tenant { get; set; }
- [Option(Name = "retry")]
+ [OptionContainer(Prefix = "retry")]
public RetryPolicyOptions? RetryPolicy { get; set; }
}
```
@@ -535,10 +535,10 @@ The `[Option]` attribute drives automatic option registration and binding via `O
**Conventions:**
- **Name**: Derived automatically from the property name in kebab-case (e.g., `LocalFilePath` → `--local-file-path`). Only use `[Option(Name = "...")]` when the convention doesn't produce the desired name (e.g., `RetryPolicy` → `--retry` instead of `--retry-policy`). **Do not** specify `Name =` when it matches the default.
-- **Required**: Determined by nullability. Use non-nullable types for required options. Use `?` for optional. Use the `required` keyword to suppress compiler warnings about uninitialized non-nullable reference properties.
-- **Description**: Pass as the constructor argument `[Option("description")]` or via `[Option(Description = "...")]`.
+- **Required**: Driven by the `required` keyword (`RequiredMemberAttribute`). Use `required` on required options; use nullable types (`?`) for optional options.
+- **Description**: Always required, passed using attribute properties: `[Option(Description = "description")]`.
- **Shared descriptions**: Use constants from `OptionDescriptions` (e.g., `OptionDescriptions.Subscription`, `OptionDescriptions.Tenant`).
-- **Nested objects**: Use `[Option(Name = "prefix")]` on a property of a complex type. Its child properties become `--prefix-child-name`. Example: `RetryPolicyOptions` with `[Option(Name = "retry")]` produces `--retry-delay`, `--retry-max-retries`, etc.
+- **Nested objects**: Use `[OptionContainer(Prefix = "prefix")]` on a property of a complex type. Its child properties become `--prefix-child-name`. Example: `RetryPolicyOptions` with `[OptionContainer(Prefix = "retry")]` produces `--retry-delay`, `--retry-max-retries`, etc.
- **Property ordering**: List command-specific options first, then sink common/infrastructure options to the bottom in this order: `ResourceGroup`, `Subscription`, `Tenant`, `AuthMethod`, `RetryPolicy`. This keeps the most relevant options visible at a glance.
### Usage Patterns
@@ -550,25 +550,25 @@ The most common pattern — a command that needs some required parameters and so
```csharp
public class {Resource}{Operation}Options : ISubscriptionOption
{
- [Option("The name of the Azure Storage account.")]
+ [Option(Description = "The name of the Azure Storage account.")]
public required string Account { get; set; }
- [Option("The name of the container within the storage account.")]
+ [Option(Description = "The name of the container within the storage account.")]
public required string Container { get; set; }
- [Option("Optional filter expression.")]
+ [Option(Description = "Optional filter expression.")]
public string? Filter { get; set; }
- [Option(OptionDescriptions.ResourceGroup)]
+ [Option(Description = OptionDescriptions.ResourceGroup)]
public string? ResourceGroup { get; set; }
- [Option(OptionDescriptions.Subscription)]
+ [Option(Description = OptionDescriptions.Subscription)]
public string? Subscription { get; set; }
- [Option(OptionDescriptions.Tenant)]
+ [Option(Description = OptionDescriptions.Tenant)]
public string? Tenant { get; set; }
- [Option(Name = "retry")]
+ [OptionContainer(Prefix = "retry")]
public RetryPolicyOptions? RetryPolicy { get; set; }
}
```
@@ -580,19 +580,19 @@ When options are mutually exclusive, make them both optional in the POCO and val
```csharp
public class MyCommandOptions : ISubscriptionOption
{
- [Option("First exclusive option.")]
+ [Option(Description = "First exclusive option.")]
public string? EitherThis { get; set; }
- [Option("Second exclusive option.")]
+ [Option(Description = "Second exclusive option.")]
public string? OrThat { get; set; }
- [Option(OptionDescriptions.Subscription)]
+ [Option(Description = OptionDescriptions.Subscription)]
public string? Subscription { get; set; }
- [Option(OptionDescriptions.Tenant)]
+ [Option(Description = OptionDescriptions.Tenant)]
public string? Tenant { get; set; }
- [Option(Name = "retry")]
+ [OptionContainer(Prefix = "retry")]
public RetryPolicyOptions? RetryPolicy { get; set; }
}
@@ -623,16 +623,16 @@ For options with a fixed set of valid values:
```csharp
public class MyCommandOptions : ISubscriptionOption
{
- [Option("The output format.")]
+ [Option(Description = "The output format.")]
public required string Format { get; set; } // Validated in ValidateOptions
- [Option(OptionDescriptions.Subscription)]
+ [Option(Description = OptionDescriptions.Subscription)]
public string? Subscription { get; set; }
- [Option(OptionDescriptions.Tenant)]
+ [Option(Description = OptionDescriptions.Tenant)]
public string? Tenant { get; set; }
- [Option(Name = "retry")]
+ [OptionContainer(Prefix = "retry")]
public RetryPolicyOptions? RetryPolicy { get; set; }
}
@@ -669,25 +669,25 @@ The concrete options class implements these interfaces while remaining flat:
```csharp
public class BlobUploadOptions : ISubscriptionOption, IContainerOption
{
- [Option("The name of the Azure Storage account.")]
+ [Option(Description = "The name of the Azure Storage account.")]
public required string Account { get; set; }
- [Option("The name of the container within the storage account.")]
+ [Option(Description = "The name of the container within the storage account.")]
public required string Container { get; set; }
- [Option("The blob name/path within the container.")]
+ [Option(Description = "The blob name/path within the container.")]
public required string Blob { get; set; }
- [Option("The local file path to read content from.")]
+ [Option(Description = "The local file path to read content from.")]
public required string LocalFilePath { get; set; }
- [Option(OptionDescriptions.Subscription)]
+ [Option(Description = OptionDescriptions.Subscription)]
public string? Subscription { get; set; }
- [Option(OptionDescriptions.Tenant)]
+ [Option(Description = OptionDescriptions.Tenant)]
public string? Tenant { get; set; }
- [Option(Name = "retry")]
+ [OptionContainer(Prefix = "retry")]
public RetryPolicyOptions? RetryPolicy { get; set; }
}
```
@@ -699,16 +699,16 @@ Commands that list resources with optional narrowing:
```csharp
public class StorageAccountListOptions : ISubscriptionOption
{
- [Option(OptionDescriptions.ResourceGroup)]
+ [Option(Description = OptionDescriptions.ResourceGroup)]
public string? ResourceGroup { get; set; }
- [Option(OptionDescriptions.Subscription)]
+ [Option(Description = OptionDescriptions.Subscription)]
public string? Subscription { get; set; }
- [Option(OptionDescriptions.Tenant)]
+ [Option(Description = OptionDescriptions.Tenant)]
public string? Tenant { get; set; }
- [Option(Name = "retry")]
+ [OptionContainer(Prefix = "retry")]
public RetryPolicyOptions? RetryPolicy { get; set; }
}
```
@@ -2005,19 +2005,19 @@ var subscriptionResource = await _subscriptionService.GetSubscription(subscripti
// Options are a flat POCO with [Option] attributes
public class MyOptions : ISubscriptionOption
{
- [Option("The resource group name.")]
+ [Option(Description = "The resource group name.")]
public required string ResourceGroup { get; set; }
- [Option("The service-specific option.")]
+ [Option(Description = "The service-specific option.")]
public string? ServiceOption { get; set; }
- [Option(OptionDescriptions.Subscription)]
+ [Option(Description = OptionDescriptions.Subscription)]
public string? Subscription { get; set; }
- [Option(OptionDescriptions.Tenant)]
+ [Option(Description = OptionDescriptions.Tenant)]
public string? Tenant { get; set; }
- [Option(Name = "retry")]
+ [OptionContainer(Prefix = "retry")]
public RetryPolicyOptions? RetryPolicy { get; set; }
}
diff --git a/tools/Azure.Mcp.Tools.Acr/src/Options/Registry/RegistryListOptions.cs b/tools/Azure.Mcp.Tools.Acr/src/Options/Registry/RegistryListOptions.cs
index 54de050844..fef4322344 100644
--- a/tools/Azure.Mcp.Tools.Acr/src/Options/Registry/RegistryListOptions.cs
+++ b/tools/Azure.Mcp.Tools.Acr/src/Options/Registry/RegistryListOptions.cs
@@ -8,18 +8,18 @@ namespace Azure.Mcp.Tools.Acr.Options.Registry;
public class RegistryListOptions : ISubscriptionOption
{
- [Option(OptionDescriptions.Tenant)]
+ [Option(Description = OptionDescriptions.Tenant)]
public string? Tenant { get; set; }
- [Option(OptionDescriptions.Subscription)]
+ [Option(Description = OptionDescriptions.Subscription)]
public string? Subscription { get; set; }
- [Option(OptionDescriptions.ResourceGroup)]
+ [Option(Description = OptionDescriptions.ResourceGroup)]
public string? ResourceGroup { get; set; }
- [Option(Name = "retry")]
+ [OptionContainer(Prefix = "retry")]
public RetryPolicyOptions? RetryPolicy { get; set; }
- [Option("The name of the Azure Container Registry.")]
+ [Option(Description = "The name of the Azure Container Registry.")]
public string? Registry { get; set; }
}
diff --git a/tools/Azure.Mcp.Tools.Acr/src/Options/Registry/RegistryRepositoryListOptions.cs b/tools/Azure.Mcp.Tools.Acr/src/Options/Registry/RegistryRepositoryListOptions.cs
index f01b327334..134f113b30 100644
--- a/tools/Azure.Mcp.Tools.Acr/src/Options/Registry/RegistryRepositoryListOptions.cs
+++ b/tools/Azure.Mcp.Tools.Acr/src/Options/Registry/RegistryRepositoryListOptions.cs
@@ -8,18 +8,18 @@ namespace Azure.Mcp.Tools.Acr.Options.Registry;
public class RegistryRepositoryListOptions : ISubscriptionOption
{
- [Option(OptionDescriptions.Tenant)]
+ [Option(Description = OptionDescriptions.Tenant)]
public string? Tenant { get; set; }
- [Option(OptionDescriptions.Subscription)]
+ [Option(Description = OptionDescriptions.Subscription)]
public string? Subscription { get; set; }
- [Option(OptionDescriptions.ResourceGroup)]
+ [Option(Description = OptionDescriptions.ResourceGroup)]
public string? ResourceGroup { get; set; }
- [Option(Name = "retry")]
+ [OptionContainer(Prefix = "retry")]
public RetryPolicyOptions? RetryPolicy { get; set; }
- [Option("The name of the Azure Container Registry.")]
+ [Option(Description = "The name of the Azure Container Registry.")]
public string? Registry { get; set; }
}
diff --git a/tools/Azure.Mcp.Tools.Aks/src/Options/Cluster/ClusterGetOptions.cs b/tools/Azure.Mcp.Tools.Aks/src/Options/Cluster/ClusterGetOptions.cs
index 1765ee3b21..052a360c66 100644
--- a/tools/Azure.Mcp.Tools.Aks/src/Options/Cluster/ClusterGetOptions.cs
+++ b/tools/Azure.Mcp.Tools.Aks/src/Options/Cluster/ClusterGetOptions.cs
@@ -8,18 +8,18 @@ namespace Azure.Mcp.Tools.Aks.Options.Cluster;
public class ClusterGetOptions : ISubscriptionOption
{
- [Option(OptionDescriptions.Tenant)]
+ [Option(Description = OptionDescriptions.Tenant)]
public string? Tenant { get; set; }
- [Option(OptionDescriptions.Subscription)]
+ [Option(Description = OptionDescriptions.Subscription)]
public string? Subscription { get; set; }
- [Option(OptionDescriptions.ResourceGroup)]
+ [Option(Description = OptionDescriptions.ResourceGroup)]
public string? ResourceGroup { get; set; }
- [Option(Name = "retry")]
+ [OptionContainer(Prefix = "retry")]
public RetryPolicyOptions? RetryPolicy { get; set; }
- [Option("AKS Cluster name.")]
+ [Option(Description = "AKS Cluster name.")]
public string? Cluster { get; set; }
}
diff --git a/tools/Azure.Mcp.Tools.Aks/src/Options/Nodepool/NodepoolGetOptions.cs b/tools/Azure.Mcp.Tools.Aks/src/Options/Nodepool/NodepoolGetOptions.cs
index 9ccfd7df5c..b6c3a3bb22 100644
--- a/tools/Azure.Mcp.Tools.Aks/src/Options/Nodepool/NodepoolGetOptions.cs
+++ b/tools/Azure.Mcp.Tools.Aks/src/Options/Nodepool/NodepoolGetOptions.cs
@@ -8,22 +8,22 @@ namespace Azure.Mcp.Tools.Aks.Options.Nodepool;
public class NodepoolGetOptions : ISubscriptionOption
{
- [Option(OptionDescriptions.Tenant)]
+ [Option(Description = OptionDescriptions.Tenant)]
public string? Tenant { get; set; }
- [Option(OptionDescriptions.Subscription)]
+ [Option(Description = OptionDescriptions.Subscription)]
public string? Subscription { get; set; }
- [Option(OptionDescriptions.ResourceGroup)]
+ [Option(Description = OptionDescriptions.ResourceGroup)]
public required string ResourceGroup { get; set; }
- [Option(Name = "retry")]
+ [OptionContainer(Prefix = "retry")]
public RetryPolicyOptions? RetryPolicy { get; set; }
- [Option("AKS Cluster name.")]
+ [Option(Description = "AKS Cluster name.")]
public required string Cluster { get; set; }
- [Option("AKS node pool (agent pool) name.")]
+ [Option(Description = "AKS node pool (agent pool) name.")]
public string? Nodepool { get; set; }
}
diff --git a/tools/Azure.Mcp.Tools.AppConfig/src/Options/KeyValue/Lock/KeyValueLockSetOptions.cs b/tools/Azure.Mcp.Tools.AppConfig/src/Options/KeyValue/Lock/KeyValueLockSetOptions.cs
index 2d31551398..fc5cc30621 100644
--- a/tools/Azure.Mcp.Tools.AppConfig/src/Options/KeyValue/Lock/KeyValueLockSetOptions.cs
+++ b/tools/Azure.Mcp.Tools.AppConfig/src/Options/KeyValue/Lock/KeyValueLockSetOptions.cs
@@ -8,24 +8,24 @@ namespace Azure.Mcp.Tools.AppConfig.Options.KeyValue.Lock;
public class KeyValueLockSetOptions : ISubscriptionOption
{
- [Option(OptionDescriptions.Subscription)]
+ [Option(Description = OptionDescriptions.Subscription)]
public string? Subscription { get; set; }
- [Option("Whether a key-value will be locked (set to read-only) or unlocked (read-only removed).")]
+ [Option(Description = "Whether a key-value will be locked (set to read-only) or unlocked (read-only removed).")]
public bool? Lock { get; set; }
- [Option("The name of the App Configuration store (e.g., my-appconfig).")]
+ [Option(Description = "The name of the App Configuration store (e.g., my-appconfig).")]
public required string Account { get; set; }
- [Option("The name of the key to access within the App Configuration store.")]
+ [Option(Description = "The name of the key to access within the App Configuration store.")]
public required string Key { get; set; }
- [Option("The label to apply to the configuration key. Labels are used to group and organize settings.")]
+ [Option(Description = "The label to apply to the configuration key. Labels are used to group and organize settings.")]
public string? Label { get; set; }
- [Option(OptionDescriptions.Tenant)]
+ [Option(Description = OptionDescriptions.Tenant)]
public string? Tenant { get; set; }
- [Option(Name = "retry")]
+ [OptionContainer(Prefix = "retry")]
public RetryPolicyOptions? RetryPolicy { get; set; }
}
diff --git a/tools/Azure.Mcp.Tools.AppLens/src/Options/Resource/ResourceDiagnoseOptions.cs b/tools/Azure.Mcp.Tools.AppLens/src/Options/Resource/ResourceDiagnoseOptions.cs
index 4d127e2671..90fae73d2e 100644
--- a/tools/Azure.Mcp.Tools.AppLens/src/Options/Resource/ResourceDiagnoseOptions.cs
+++ b/tools/Azure.Mcp.Tools.AppLens/src/Options/Resource/ResourceDiagnoseOptions.cs
@@ -11,33 +11,33 @@ namespace Azure.Mcp.Tools.AppLens.Options.Resource;
///
public sealed class ResourceDiagnoseOptions
{
- [Option(OptionDescriptions.Tenant)]
+ [Option(Description = OptionDescriptions.Tenant)]
public string? Tenant { get; set; }
- [Option("Azure resource group name. Provide this when disambiguating between multiple resources of the same name.")]
+ [Option(Description = "Azure resource group name. Provide this when disambiguating between multiple resources of the same name.")]
public string? ResourceGroup { get; set; }
///
/// The user's question for diagnosis.
///
- [Option("User question")]
+ [Option(Description = "User question")]
public required string Question { get; set; }
///
/// The name of the resource to diagnose.
///
- [Option("The name of the resource to investigate or diagnose")]
+ [Option(Description = "The name of the resource to investigate or diagnose")]
public required string Resource { get; set; }
///
/// The Resource Type of the resource to diagnose. This is optional and used to disambiguate between multiple resources with the same name.
///
- [Option("Resource type. Provide this when disambiguating between multiple resources of the same name.")]
+ [Option(Description = "Resource type. Provide this when disambiguating between multiple resources of the same name.")]
public string? ResourceType { get; set; }
///
/// The subscription of the resource to diagnose. This is optional and used to disambiguate between multiple resources with the same name.
///
- [Option("Azure subscription ID or name. Provide this when disambiguating between multiple resources of the same name.")]
+ [Option(Description = "Azure subscription ID or name. Provide this when disambiguating between multiple resources of the same name.")]
public string? Subscription { get; set; }
}
diff --git a/tools/Azure.Mcp.Tools.ApplicationInsights/src/Options/RecommendationListOptions.cs b/tools/Azure.Mcp.Tools.ApplicationInsights/src/Options/RecommendationListOptions.cs
index 0c4de4c239..0f18266918 100644
--- a/tools/Azure.Mcp.Tools.ApplicationInsights/src/Options/RecommendationListOptions.cs
+++ b/tools/Azure.Mcp.Tools.ApplicationInsights/src/Options/RecommendationListOptions.cs
@@ -8,15 +8,15 @@ namespace Azure.Mcp.Tools.ApplicationInsights.Options;
public class RecommendationListOptions : ISubscriptionOption
{
- [Option(OptionDescriptions.Tenant)]
+ [Option(Description = OptionDescriptions.Tenant)]
public string? Tenant { get; set; }
- [Option(OptionDescriptions.Subscription)]
+ [Option(Description = OptionDescriptions.Subscription)]
public string? Subscription { get; set; }
- [Option(OptionDescriptions.ResourceGroup)]
+ [Option(Description = OptionDescriptions.ResourceGroup)]
public string? ResourceGroup { get; set; }
- [Option(Name = "retry")]
+ [OptionContainer(Prefix = "retry")]
public RetryPolicyOptions? RetryPolicy { get; set; }
}
diff --git a/tools/Azure.Mcp.Tools.Kusto/src/Commands/BaseClusterCommand.cs b/tools/Azure.Mcp.Tools.Kusto/src/Commands/BaseClusterCommand.cs
index 53e35aac8d..cbf7df1d21 100644
--- a/tools/Azure.Mcp.Tools.Kusto/src/Commands/BaseClusterCommand.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/src/Commands/BaseClusterCommand.cs
@@ -33,7 +33,7 @@ public override void ValidateOptions(TOptions options, ValidationResult validati
}
// clusterUri not provided, require both subscription and clusterName
- if (string.IsNullOrEmpty(options.ClusterName) || string.IsNullOrEmpty(options.Subscription))
+ if (string.IsNullOrEmpty(options.Cluster) || string.IsNullOrEmpty(options.Subscription))
{
validationResult.Errors.Add("Either --cluster-uri must be provided, or both --subscription and --cluster must be provided.");
}
diff --git a/tools/Azure.Mcp.Tools.Kusto/src/Commands/BaseDatabaseCommand.cs b/tools/Azure.Mcp.Tools.Kusto/src/Commands/BaseDatabaseCommand.cs
deleted file mode 100644
index f6095eb279..0000000000
--- a/tools/Azure.Mcp.Tools.Kusto/src/Commands/BaseDatabaseCommand.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System.Diagnostics.CodeAnalysis;
-using Azure.Mcp.Core.Options;
-using Azure.Mcp.Core.Services.Azure.Subscription;
-using Azure.Mcp.Tools.Kusto.Options;
-using Microsoft.Mcp.Core.Commands;
-
-namespace Azure.Mcp.Tools.Kusto.Commands;
-
-public abstract class BaseDatabaseCommand<
- [DynamicallyAccessedMembers(TrimAnnotations.CommandAnnotations)] TOptions, TResult>(
- ISubscriptionResolver subscriptionResolver)
- : BaseClusterCommand(subscriptionResolver)
- where TOptions : class, ISubscriptionOption, IDatabaseOption
-{
-}
diff --git a/tools/Azure.Mcp.Tools.Kusto/src/Commands/BaseTableCommand.cs b/tools/Azure.Mcp.Tools.Kusto/src/Commands/BaseTableCommand.cs
deleted file mode 100644
index 48aef03de4..0000000000
--- a/tools/Azure.Mcp.Tools.Kusto/src/Commands/BaseTableCommand.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System.Diagnostics.CodeAnalysis;
-using Azure.Mcp.Core.Options;
-using Azure.Mcp.Core.Services.Azure.Subscription;
-using Azure.Mcp.Tools.Kusto.Options;
-using Microsoft.Mcp.Core.Commands;
-
-namespace Azure.Mcp.Tools.Kusto.Commands;
-
-public abstract class BaseTableCommand<
- [DynamicallyAccessedMembers(TrimAnnotations.CommandAnnotations)] TOptions, TResult>(
- ISubscriptionResolver subscriptionResolver)
- : BaseDatabaseCommand(subscriptionResolver)
- where TOptions : class, ISubscriptionOption, ITableOption
-{
-}
diff --git a/tools/Azure.Mcp.Tools.Kusto/src/Commands/ClusterGetCommand.cs b/tools/Azure.Mcp.Tools.Kusto/src/Commands/ClusterGetCommand.cs
index 43a8d10eef..43d6e68292 100644
--- a/tools/Azure.Mcp.Tools.Kusto/src/Commands/ClusterGetCommand.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/src/Commands/ClusterGetCommand.cs
@@ -36,7 +36,7 @@ public override async Task ExecuteAsync(CommandContext context,
{
var cluster = await kustoService.GetClusterAsync(
options.Subscription!,
- options.ClusterName,
+ options.Cluster,
options.Tenant,
options.RetryPolicy,
cancellationToken);
@@ -46,7 +46,7 @@ public override async Task ExecuteAsync(CommandContext context,
}
catch (Exception ex)
{
- logger.LogError(ex, "An exception occurred getting Kusto cluster details. Cluster: {Cluster}.", options.ClusterName);
+ logger.LogError(ex, "An exception occurred getting Kusto cluster details. Cluster: {Cluster}.", options.Cluster);
HandleException(context, ex);
}
diff --git a/tools/Azure.Mcp.Tools.Kusto/src/Commands/DatabaseListCommand.cs b/tools/Azure.Mcp.Tools.Kusto/src/Commands/DatabaseListCommand.cs
index 97a97e0dde..828af9287f 100644
--- a/tools/Azure.Mcp.Tools.Kusto/src/Commands/DatabaseListCommand.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/src/Commands/DatabaseListCommand.cs
@@ -38,7 +38,6 @@ public override async Task ExecuteAsync(CommandContext context,
databasesNames = await kustoService.ListDatabasesAsync(
options.ClusterUri!,
options.Tenant,
- options.AuthMethod,
options.RetryPolicy,
cancellationToken);
}
@@ -46,9 +45,8 @@ public override async Task ExecuteAsync(CommandContext context,
{
databasesNames = await kustoService.ListDatabasesAsync(
options.Subscription!,
- options.ClusterName!,
+ options.Cluster!,
options.Tenant,
- options.AuthMethod,
options.RetryPolicy,
cancellationToken);
}
@@ -57,7 +55,7 @@ public override async Task ExecuteAsync(CommandContext context,
}
catch (Exception ex)
{
- logger.LogError(ex, "An exception occurred listing databases. Cluster: {Cluster}.", options.ClusterUri ?? options.ClusterName);
+ logger.LogError(ex, "An exception occurred listing databases. Cluster: {Cluster}.", options.ClusterUri ?? options.Cluster);
HandleException(context, ex);
}
return context.Response;
diff --git a/tools/Azure.Mcp.Tools.Kusto/src/Commands/KustoJsonContext.cs b/tools/Azure.Mcp.Tools.Kusto/src/Commands/KustoJsonContext.cs
index 7a1c90edec..d64000eaba 100644
--- a/tools/Azure.Mcp.Tools.Kusto/src/Commands/KustoJsonContext.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/src/Commands/KustoJsonContext.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using Azure.Mcp.Tools.Kusto.Services.Models;
diff --git a/tools/Azure.Mcp.Tools.Kusto/src/Commands/QueryCommand.cs b/tools/Azure.Mcp.Tools.Kusto/src/Commands/QueryCommand.cs
index 129efe0622..8c69151e05 100644
--- a/tools/Azure.Mcp.Tools.Kusto/src/Commands/QueryCommand.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/src/Commands/QueryCommand.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using System.Text.Json;
using Azure.Mcp.Core.Services.Azure.Subscription;
using Azure.Mcp.Tools.Kusto.Options;
using Azure.Mcp.Tools.Kusto.Services;
@@ -25,7 +26,7 @@ public sealed class QueryCommand(
ILogger logger,
IKustoService kustoService,
ISubscriptionResolver subscriptionResolver)
- : BaseDatabaseCommand(subscriptionResolver)
+ : BaseClusterCommand(subscriptionResolver)
{
public override async Task ExecuteAsync(CommandContext context, QueryOptions options, CancellationToken cancellationToken)
{
@@ -40,7 +41,6 @@ public override async Task ExecuteAsync(CommandContext context,
options.Database,
options.Query,
options.Tenant,
- options.AuthMethod,
options.RetryPolicy,
cancellationToken);
}
@@ -48,11 +48,10 @@ public override async Task ExecuteAsync(CommandContext context,
{
results = await kustoService.QueryItemsAsync(
options.Subscription!,
- options.ClusterName!,
+ options.Cluster!,
options.Database,
options.Query,
options.Tenant,
- options.AuthMethod,
options.RetryPolicy,
cancellationToken);
}
@@ -62,7 +61,7 @@ public override async Task ExecuteAsync(CommandContext context,
catch (Exception ex)
{
logger.LogError(ex, "An exception occurred querying Kusto. Cluster: {Cluster}, Database: {Database},"
- + " Query: {Query}", options.ClusterUri ?? options.ClusterName, options.Database, options.Query);
+ + " Query: {Query}", options.ClusterUri ?? options.Cluster, options.Database, options.Query);
HandleException(context, ex);
}
return context.Response;
diff --git a/tools/Azure.Mcp.Tools.Kusto/src/Commands/SampleCommand.cs b/tools/Azure.Mcp.Tools.Kusto/src/Commands/SampleCommand.cs
index 2b4a271369..19fe151de7 100644
--- a/tools/Azure.Mcp.Tools.Kusto/src/Commands/SampleCommand.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/src/Commands/SampleCommand.cs
@@ -26,7 +26,7 @@ public sealed class SampleCommand(
ILogger logger,
IKustoService kustoService,
ISubscriptionResolver subscriptionResolver)
- : BaseTableCommand(subscriptionResolver)
+ : BaseClusterCommand(subscriptionResolver)
{
public override async Task ExecuteAsync(CommandContext context, SampleOptions options, CancellationToken cancellationToken)
{
@@ -46,7 +46,6 @@ public override async Task ExecuteAsync(CommandContext context,
options.Database,
query,
options.Tenant,
- options.AuthMethod,
options.RetryPolicy,
cancellationToken);
}
@@ -54,11 +53,10 @@ public override async Task ExecuteAsync(CommandContext context,
{
results = await kustoService.QueryItemsAsync(
options.Subscription!,
- options.ClusterName!,
+ options.Cluster!,
options.Database,
query,
options.Tenant,
- options.AuthMethod,
options.RetryPolicy,
cancellationToken);
}
@@ -67,7 +65,7 @@ public override async Task ExecuteAsync(CommandContext context,
}
catch (Exception ex)
{
- logger.LogError(ex, "An exception occurred sampling table. Cluster: {Cluster}, Database: {Database}, Table: {Table}.", options.ClusterUri ?? options.ClusterName, options.Database, options.Table);
+ logger.LogError(ex, "An exception occurred sampling table. Cluster: {Cluster}, Database: {Database}, Table: {Table}.", options.ClusterUri ?? options.Cluster, options.Database, options.Table);
HandleException(context, ex);
}
return context.Response;
diff --git a/tools/Azure.Mcp.Tools.Kusto/src/Commands/TableListCommand.cs b/tools/Azure.Mcp.Tools.Kusto/src/Commands/TableListCommand.cs
index 44599f3b57..aa9e6b7c05 100644
--- a/tools/Azure.Mcp.Tools.Kusto/src/Commands/TableListCommand.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/src/Commands/TableListCommand.cs
@@ -25,7 +25,7 @@ public sealed class TableListCommand(
ILogger logger,
IKustoService kustoService,
ISubscriptionResolver subscriptionResolver)
- : BaseDatabaseCommand(subscriptionResolver)
+ : BaseClusterCommand(subscriptionResolver)
{
public override async Task ExecuteAsync(CommandContext context, TableListOptions options, CancellationToken cancellationToken)
{
@@ -39,7 +39,6 @@ public override async Task ExecuteAsync(CommandContext context,
options.ClusterUri!,
options.Database,
options.Tenant,
- options.AuthMethod,
options.RetryPolicy,
cancellationToken);
}
@@ -47,10 +46,9 @@ public override async Task ExecuteAsync(CommandContext context,
{
tableNames = await kustoService.ListTablesAsync(
options.Subscription!,
- options.ClusterName!,
+ options.Cluster!,
options.Database,
options.Tenant,
- options.AuthMethod,
options.RetryPolicy,
cancellationToken);
}
@@ -59,7 +57,7 @@ public override async Task ExecuteAsync(CommandContext context,
}
catch (Exception ex)
{
- logger.LogError(ex, "An exception occurred listing tables. Cluster: {Cluster}, Database: {Database}.", options.ClusterUri ?? options.ClusterName, options.Database);
+ logger.LogError(ex, "An exception occurred listing tables. Cluster: {Cluster}, Database: {Database}.", options.ClusterUri ?? options.Cluster, options.Database);
HandleException(context, ex);
}
return context.Response;
diff --git a/tools/Azure.Mcp.Tools.Kusto/src/Commands/TableSchemaCommand.cs b/tools/Azure.Mcp.Tools.Kusto/src/Commands/TableSchemaCommand.cs
index 5bbaa056cf..f9b163dbe0 100644
--- a/tools/Azure.Mcp.Tools.Kusto/src/Commands/TableSchemaCommand.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/src/Commands/TableSchemaCommand.cs
@@ -25,7 +25,7 @@ public sealed class TableSchemaCommand(
ILogger logger,
IKustoService kustoService,
ISubscriptionResolver subscriptionResolver)
- : BaseTableCommand(subscriptionResolver)
+ : BaseClusterCommand(subscriptionResolver)
{
public override async Task ExecuteAsync(CommandContext context, TableSchemaOptions options, CancellationToken cancellationToken)
{
@@ -40,7 +40,6 @@ public override async Task ExecuteAsync(CommandContext context,
options.Database,
options.Table,
options.Tenant,
- options.AuthMethod,
options.RetryPolicy,
cancellationToken);
}
@@ -48,11 +47,10 @@ public override async Task ExecuteAsync(CommandContext context,
{
tableSchema = await kustoService.GetTableSchemaAsync(
options.Subscription!,
- options.ClusterName!,
+ options.Cluster!,
options.Database,
options.Table,
options.Tenant,
- options.AuthMethod,
options.RetryPolicy,
cancellationToken);
}
@@ -61,7 +59,7 @@ public override async Task ExecuteAsync(CommandContext context,
}
catch (Exception ex)
{
- logger.LogError(ex, "An exception occurred getting table schema. Cluster: {Cluster}, Table: {Table}.", options.ClusterUri ?? options.ClusterName, options.Table);
+ logger.LogError(ex, "An exception occurred getting table schema. Cluster: {Cluster}, Table: {Table}.", options.ClusterUri ?? options.Cluster, options.Table);
HandleException(context, ex);
}
return context.Response;
diff --git a/tools/Azure.Mcp.Tools.Kusto/src/GlobalUsings.cs b/tools/Azure.Mcp.Tools.Kusto/src/GlobalUsings.cs
deleted file mode 100644
index 14a017b104..0000000000
--- a/tools/Azure.Mcp.Tools.Kusto/src/GlobalUsings.cs
+++ /dev/null
@@ -1,5 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-global using System.CommandLine;
-global using System.Text.Json;
diff --git a/tools/Azure.Mcp.Tools.Kusto/src/Options/ClusterGetOptions.cs b/tools/Azure.Mcp.Tools.Kusto/src/Options/ClusterGetOptions.cs
index 9ef38f7c8a..19f2f3fefc 100644
--- a/tools/Azure.Mcp.Tools.Kusto/src/Options/ClusterGetOptions.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/src/Options/ClusterGetOptions.cs
@@ -2,25 +2,21 @@
// Licensed under the MIT License.
using Azure.Mcp.Core.Options;
-using Microsoft.Mcp.Core.Models;
using Microsoft.Mcp.Core.Options;
namespace Azure.Mcp.Tools.Kusto.Options;
-public class ClusterGetOptions : ISubscriptionOption
+public sealed class ClusterGetOptions : ISubscriptionOption
{
- [Option("Kusto Cluster name.", Name = "cluster")]
- public required string ClusterName { get; set; }
+ [Option(Description = KustOptionDescriptions.Cluster)]
+ public required string Cluster { get; set; }
- [Option(OptionDescriptions.Subscription)]
+ [Option(Description = OptionDescriptions.Subscription)]
public string? Subscription { get; set; }
- [Option(OptionDescriptions.Tenant)]
+ [Option(Description = OptionDescriptions.Tenant)]
public string? Tenant { get; set; }
- [Option(OptionDescriptions.AuthMethod, Name = "auth-method")]
- public AuthMethod? AuthMethod { get; set; }
-
- [Option(Name = "retry")]
+ [OptionContainer(Prefix = "retry")]
public RetryPolicyOptions? RetryPolicy { get; set; }
}
diff --git a/tools/Azure.Mcp.Tools.Kusto/src/Options/ClusterListOptions.cs b/tools/Azure.Mcp.Tools.Kusto/src/Options/ClusterListOptions.cs
index 316fe511af..dd01010d49 100644
--- a/tools/Azure.Mcp.Tools.Kusto/src/Options/ClusterListOptions.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/src/Options/ClusterListOptions.cs
@@ -2,25 +2,21 @@
// Licensed under the MIT License.
using Azure.Mcp.Core.Options;
-using Microsoft.Mcp.Core.Models;
using Microsoft.Mcp.Core.Options;
namespace Azure.Mcp.Tools.Kusto.Options;
-public class ClusterListOptions : ISubscriptionOption
+public sealed class ClusterListOptions : ISubscriptionOption
{
- [Option(OptionDescriptions.ResourceGroup, Name = "resource-group")]
+ [Option(Description = OptionDescriptions.ResourceGroup)]
public string? ResourceGroup { get; set; }
- [Option(OptionDescriptions.Subscription)]
+ [Option(Description = OptionDescriptions.Subscription)]
public string? Subscription { get; set; }
- [Option(OptionDescriptions.Tenant)]
+ [Option(Description = OptionDescriptions.Tenant)]
public string? Tenant { get; set; }
- [Option(OptionDescriptions.AuthMethod, Name = "auth-method")]
- public AuthMethod? AuthMethod { get; set; }
-
- [Option(Name = "retry")]
+ [OptionContainer(Prefix = "retry")]
public RetryPolicyOptions? RetryPolicy { get; set; }
}
diff --git a/tools/Azure.Mcp.Tools.Kusto/src/Options/DatabaseListOptions.cs b/tools/Azure.Mcp.Tools.Kusto/src/Options/DatabaseListOptions.cs
index d9afcd3533..74da8ee1d2 100644
--- a/tools/Azure.Mcp.Tools.Kusto/src/Options/DatabaseListOptions.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/src/Options/DatabaseListOptions.cs
@@ -2,28 +2,24 @@
// Licensed under the MIT License.
using Azure.Mcp.Core.Options;
-using Microsoft.Mcp.Core.Models;
using Microsoft.Mcp.Core.Options;
namespace Azure.Mcp.Tools.Kusto.Options;
-public class DatabaseListOptions : ISubscriptionOption, IClusterOption
+public sealed class DatabaseListOptions : ISubscriptionOption, IClusterOption
{
- [Option("Kusto Cluster URI.", Name = "cluster-uri")]
+ [Option(Description = KustOptionDescriptions.ClusterUri)]
public string? ClusterUri { get; set; }
- [Option("Kusto Cluster name.", Name = "cluster")]
- public string? ClusterName { get; set; }
+ [Option(Description = KustOptionDescriptions.Cluster)]
+ public string? Cluster { get; set; }
- [Option(OptionDescriptions.Subscription)]
+ [Option(Description = OptionDescriptions.Subscription)]
public string? Subscription { get; set; }
- [Option(OptionDescriptions.Tenant)]
+ [Option(Description = OptionDescriptions.Tenant)]
public string? Tenant { get; set; }
- [Option(OptionDescriptions.AuthMethod, Name = "auth-method")]
- public AuthMethod? AuthMethod { get; set; }
-
- [Option(Name = "retry")]
+ [OptionContainer(Prefix = "retry")]
public RetryPolicyOptions? RetryPolicy { get; set; }
}
diff --git a/tools/Azure.Mcp.Tools.Kusto/src/Options/IClusterOption.cs b/tools/Azure.Mcp.Tools.Kusto/src/Options/IClusterOption.cs
index 9eb5963741..ac36de4643 100644
--- a/tools/Azure.Mcp.Tools.Kusto/src/Options/IClusterOption.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/src/Options/IClusterOption.cs
@@ -5,6 +5,6 @@ namespace Azure.Mcp.Tools.Kusto.Options;
public interface IClusterOption
{
- string? ClusterName { get; }
+ string? Cluster { get; }
string? ClusterUri { get; }
}
diff --git a/tools/Azure.Mcp.Tools.Kusto/src/Options/KustoOptionDescriptions.cs b/tools/Azure.Mcp.Tools.Kusto/src/Options/KustoOptionDescriptions.cs
new file mode 100644
index 0000000000..f6a20f606e
--- /dev/null
+++ b/tools/Azure.Mcp.Tools.Kusto/src/Options/KustoOptionDescriptions.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace Azure.Mcp.Tools.Kusto.Options;
+
+internal static class KustOptionDescriptions
+{
+ internal const string ClusterUri = "Kusto Cluster URI.";
+ internal const string Cluster = "Kusto Cluster name.";
+ internal const string Database = "Kusto Database name.";
+ internal const string Table = "Kusto Table name.";
+}
diff --git a/tools/Azure.Mcp.Tools.Kusto/src/Options/QueryOptions.cs b/tools/Azure.Mcp.Tools.Kusto/src/Options/QueryOptions.cs
index 9830842c56..1b00c31f4b 100644
--- a/tools/Azure.Mcp.Tools.Kusto/src/Options/QueryOptions.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/src/Options/QueryOptions.cs
@@ -2,34 +2,30 @@
// Licensed under the MIT License.
using Azure.Mcp.Core.Options;
-using Microsoft.Mcp.Core.Models;
using Microsoft.Mcp.Core.Options;
namespace Azure.Mcp.Tools.Kusto.Options;
-public class QueryOptions : ISubscriptionOption, IDatabaseOption
+public sealed class QueryOptions : ISubscriptionOption, IDatabaseOption
{
- [Option("Kusto query to execute. Uses KQL syntax.")]
+ [Option(Description = "Kusto query to execute. Uses KQL syntax.")]
public required string Query { get; set; }
- [Option("Kusto Database name.")]
+ [Option(Description = KustOptionDescriptions.Database)]
public required string Database { get; set; }
- [Option("Kusto Cluster URI.", Name = "cluster-uri")]
+ [Option(Description = KustOptionDescriptions.ClusterUri)]
public string? ClusterUri { get; set; }
- [Option("Kusto Cluster name.", Name = "cluster")]
- public string? ClusterName { get; set; }
+ [Option(Description = KustOptionDescriptions.Cluster)]
+ public string? Cluster { get; set; }
- [Option(OptionDescriptions.Subscription)]
+ [Option(Description = OptionDescriptions.Subscription)]
public string? Subscription { get; set; }
- [Option(OptionDescriptions.Tenant)]
+ [Option(Description = OptionDescriptions.Tenant)]
public string? Tenant { get; set; }
- [Option(OptionDescriptions.AuthMethod, Name = "auth-method")]
- public AuthMethod? AuthMethod { get; set; }
-
- [Option(Name = "retry")]
+ [OptionContainer(Prefix = "retry")]
public RetryPolicyOptions? RetryPolicy { get; set; }
}
diff --git a/tools/Azure.Mcp.Tools.Kusto/src/Options/SampleOptions.cs b/tools/Azure.Mcp.Tools.Kusto/src/Options/SampleOptions.cs
index ae13075c0c..987aecb8c9 100644
--- a/tools/Azure.Mcp.Tools.Kusto/src/Options/SampleOptions.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/src/Options/SampleOptions.cs
@@ -2,37 +2,33 @@
// Licensed under the MIT License.
using Azure.Mcp.Core.Options;
-using Microsoft.Mcp.Core.Models;
using Microsoft.Mcp.Core.Options;
namespace Azure.Mcp.Tools.Kusto.Options;
-public class SampleOptions : ISubscriptionOption, ITableOption
+public sealed class SampleOptions : ISubscriptionOption, ITableOption
{
- [Option("The maximum number of results to return. Must be a positive integer between 1 and 10000. Default is 10.")]
+ [Option(Description = "The maximum number of results to return. Must be a positive integer between 1 and 10000. Default is 10.")]
public int? Limit { get; set; }
- [Option("Kusto Table name.")]
+ [Option(Description = KustOptionDescriptions.Table)]
public required string Table { get; set; }
- [Option("Kusto Database name.")]
+ [Option(Description = KustOptionDescriptions.Database)]
public required string Database { get; set; }
- [Option("Kusto Cluster URI.", Name = "cluster-uri")]
+ [Option(Description = KustOptionDescriptions.ClusterUri)]
public string? ClusterUri { get; set; }
- [Option("Kusto Cluster name.", Name = "cluster")]
- public string? ClusterName { get; set; }
+ [Option(Description = KustOptionDescriptions.Cluster)]
+ public string? Cluster { get; set; }
- [Option(OptionDescriptions.Subscription)]
+ [Option(Description = OptionDescriptions.Subscription)]
public string? Subscription { get; set; }
- [Option(OptionDescriptions.Tenant)]
+ [Option(Description = OptionDescriptions.Tenant)]
public string? Tenant { get; set; }
- [Option(OptionDescriptions.AuthMethod, Name = "auth-method")]
- public AuthMethod? AuthMethod { get; set; }
-
- [Option(Name = "retry")]
+ [OptionContainer(Prefix = "retry")]
public RetryPolicyOptions? RetryPolicy { get; set; }
}
diff --git a/tools/Azure.Mcp.Tools.Kusto/src/Options/TableListOptions.cs b/tools/Azure.Mcp.Tools.Kusto/src/Options/TableListOptions.cs
index fe8ddb949c..830aa77b29 100644
--- a/tools/Azure.Mcp.Tools.Kusto/src/Options/TableListOptions.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/src/Options/TableListOptions.cs
@@ -2,31 +2,27 @@
// Licensed under the MIT License.
using Azure.Mcp.Core.Options;
-using Microsoft.Mcp.Core.Models;
using Microsoft.Mcp.Core.Options;
namespace Azure.Mcp.Tools.Kusto.Options;
-public class TableListOptions : ISubscriptionOption, IDatabaseOption
+public sealed class TableListOptions : ISubscriptionOption, IDatabaseOption
{
- [Option("Kusto Database name.")]
+ [Option(Description = KustOptionDescriptions.Database)]
public required string Database { get; set; }
- [Option("Kusto Cluster URI.", Name = "cluster-uri")]
+ [Option(Description = KustOptionDescriptions.ClusterUri)]
public string? ClusterUri { get; set; }
- [Option("Kusto Cluster name.", Name = "cluster")]
- public string? ClusterName { get; set; }
+ [Option(Description = KustOptionDescriptions.Cluster)]
+ public string? Cluster { get; set; }
- [Option(OptionDescriptions.Subscription)]
+ [Option(Description = OptionDescriptions.Subscription)]
public string? Subscription { get; set; }
- [Option(OptionDescriptions.Tenant)]
+ [Option(Description = OptionDescriptions.Tenant)]
public string? Tenant { get; set; }
- [Option(OptionDescriptions.AuthMethod, Name = "auth-method")]
- public AuthMethod? AuthMethod { get; set; }
-
- [Option(Name = "retry")]
+ [OptionContainer(Prefix = "retry")]
public RetryPolicyOptions? RetryPolicy { get; set; }
}
diff --git a/tools/Azure.Mcp.Tools.Kusto/src/Options/TableSchemaOptions.cs b/tools/Azure.Mcp.Tools.Kusto/src/Options/TableSchemaOptions.cs
index 80c6290724..fd26f4935c 100644
--- a/tools/Azure.Mcp.Tools.Kusto/src/Options/TableSchemaOptions.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/src/Options/TableSchemaOptions.cs
@@ -2,34 +2,30 @@
// Licensed under the MIT License.
using Azure.Mcp.Core.Options;
-using Microsoft.Mcp.Core.Models;
using Microsoft.Mcp.Core.Options;
namespace Azure.Mcp.Tools.Kusto.Options;
-public class TableSchemaOptions : ISubscriptionOption, ITableOption
+public sealed class TableSchemaOptions : ISubscriptionOption, ITableOption
{
- [Option("Kusto Table name.")]
+ [Option(Description = KustOptionDescriptions.Table)]
public required string Table { get; set; }
- [Option("Kusto Database name.")]
+ [Option(Description = KustOptionDescriptions.Database)]
public required string Database { get; set; }
- [Option("Kusto Cluster URI.", Name = "cluster-uri")]
+ [Option(Description = KustOptionDescriptions.ClusterUri)]
public string? ClusterUri { get; set; }
- [Option("Kusto Cluster name.", Name = "cluster")]
- public string? ClusterName { get; set; }
+ [Option(Description = KustOptionDescriptions.Cluster)]
+ public string? Cluster { get; set; }
- [Option(OptionDescriptions.Subscription)]
+ [Option(Description = OptionDescriptions.Subscription)]
public string? Subscription { get; set; }
- [Option(OptionDescriptions.Tenant)]
+ [Option(Description = OptionDescriptions.Tenant)]
public string? Tenant { get; set; }
- [Option(OptionDescriptions.AuthMethod, Name = "auth-method")]
- public AuthMethod? AuthMethod { get; set; }
-
- [Option(Name = "retry")]
+ [OptionContainer(Prefix = "retry")]
public RetryPolicyOptions? RetryPolicy { get; set; }
}
diff --git a/tools/Azure.Mcp.Tools.Kusto/src/Services/IKustoService.cs b/tools/Azure.Mcp.Tools.Kusto/src/Services/IKustoService.cs
index b9e6166978..bdda69c34a 100644
--- a/tools/Azure.Mcp.Tools.Kusto/src/Services/IKustoService.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/src/Services/IKustoService.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using System.Text.Json;
using Azure.Mcp.Core.Services.Azure;
using Azure.Mcp.Tools.Kusto.Models;
using Microsoft.Mcp.Core.Models;
@@ -27,7 +28,6 @@ Task GetClusterAsync(
Task> ListDatabasesAsync(
string clusterUri,
string? tenant = null,
- AuthMethod? authMethod = AuthMethod.Credential,
RetryPolicyOptions? retryPolicy = null,
CancellationToken cancellationToken = default);
@@ -35,7 +35,6 @@ Task> ListDatabasesAsync(
string subscription,
string clusterName,
string? tenant = null,
- AuthMethod? authMethod = AuthMethod.Credential,
RetryPolicyOptions? retryPolicy = null,
CancellationToken cancellationToken = default);
@@ -44,7 +43,6 @@ Task> QueryItemsAsync(
string databaseName,
string query,
string? tenant = null,
- AuthMethod? authMethod = AuthMethod.Credential,
RetryPolicyOptions? retryPolicy = null,
CancellationToken cancellationToken = default);
@@ -54,7 +52,6 @@ Task> QueryItemsAsync(
string databaseName,
string query,
string? tenant = null,
- AuthMethod? authMethod = AuthMethod.Credential,
RetryPolicyOptions? retryPolicy = null,
CancellationToken cancellationToken = default);
@@ -62,7 +59,6 @@ Task> ListTablesAsync(
string clusterUri,
string databaseName,
string? tenant = null,
- AuthMethod? authMethod = AuthMethod.Credential,
RetryPolicyOptions? retryPolicy = null,
CancellationToken cancellationToken = default);
@@ -71,7 +67,6 @@ Task> ListTablesAsync(
string clusterName,
string databaseName,
string? tenant = null,
- AuthMethod? authMethod = AuthMethod.Credential,
RetryPolicyOptions? retryPolicy = null,
CancellationToken cancellationToken = default);
@@ -80,7 +75,6 @@ Task GetTableSchemaAsync(
string databaseName,
string tableName,
string? tenant = null,
- AuthMethod? authMethod = AuthMethod.Credential,
RetryPolicyOptions? retryPolicy = null,
CancellationToken cancellationToken = default);
@@ -90,7 +84,6 @@ Task GetTableSchemaAsync(
string databaseName,
string tableName,
string? tenant = null,
- AuthMethod? authMethod = AuthMethod.Credential,
RetryPolicyOptions? retryPolicy = null,
CancellationToken cancellationToken = default);
}
diff --git a/tools/Azure.Mcp.Tools.Kusto/src/Services/KustoClient.cs b/tools/Azure.Mcp.Tools.Kusto/src/Services/KustoClient.cs
index 2571ce606f..6b8fc48a2a 100644
--- a/tools/Azure.Mcp.Tools.Kusto/src/Services/KustoClient.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/src/Services/KustoClient.cs
@@ -263,7 +263,7 @@ private async Task GenerateRequestAsync(string uri, string d
return httpRequest;
}
- private async Task SendRequestAsync(HttpClient httpClient, HttpRequestMessage httpRequest, CancellationToken cancellationToken)
+ private static async Task SendRequestAsync(HttpClient httpClient, HttpRequestMessage httpRequest, CancellationToken cancellationToken)
{
var httpResponse = await httpClient.SendAsync(httpRequest, HttpCompletionOption.ResponseContentRead, cancellationToken);
if (!httpResponse.IsSuccessStatusCode)
diff --git a/tools/Azure.Mcp.Tools.Kusto/src/Services/KustoResult.cs b/tools/Azure.Mcp.Tools.Kusto/src/Services/KustoResult.cs
index e50cd8f029..a3cd9259dc 100644
--- a/tools/Azure.Mcp.Tools.Kusto/src/Services/KustoResult.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/src/Services/KustoResult.cs
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using System.Text.Json;
+
namespace Azure.Mcp.Tools.Kusto.Services;
public sealed class KustoResult
diff --git a/tools/Azure.Mcp.Tools.Kusto/src/Services/KustoService.cs b/tools/Azure.Mcp.Tools.Kusto/src/Services/KustoService.cs
index a5767e8b1d..9b82690647 100644
--- a/tools/Azure.Mcp.Tools.Kusto/src/Services/KustoService.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/src/Services/KustoService.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using System.Text.Json;
using Azure.Core;
using Azure.Mcp.Core.Services.Azure;
using Azure.Mcp.Core.Services.Azure.Subscription;
@@ -9,7 +10,6 @@
using Azure.Mcp.Tools.Kusto.Validation;
using Microsoft.Extensions.Logging;
using Microsoft.Mcp.Core.Helpers;
-using Microsoft.Mcp.Core.Models;
using Microsoft.Mcp.Core.Options;
using Microsoft.Mcp.Core.Services.Caching;
using Microsoft.Mcp.Core.Validation;
@@ -116,8 +116,6 @@ public async Task> ListDatabasesAsync(
string subscriptionId,
string clusterName,
string? tenant = null,
- AuthMethod? authMethod =
- AuthMethod.Credential,
RetryPolicyOptions? retryPolicy = null,
CancellationToken cancellationToken = default)
{
@@ -126,13 +124,12 @@ public async Task> ListDatabasesAsync(
(nameof(clusterName), clusterName));
string clusterUri = await GetClusterUriAsync(subscriptionId, clusterName, tenant, retryPolicy);
- return await ListDatabasesAsync(clusterUri, tenant, authMethod, retryPolicy, cancellationToken);
+ return await ListDatabasesAsync(clusterUri, tenant, retryPolicy, cancellationToken);
}
public async Task> ListDatabasesAsync(
string clusterUri,
string? tenant = null,
- AuthMethod? authMethod = AuthMethod.Credential,
RetryPolicyOptions? retryPolicy = null,
CancellationToken cancellationToken = default)
{
@@ -151,7 +148,6 @@ public async Task> ListTablesAsync(
string clusterName,
string databaseName,
string? tenant = null,
- AuthMethod? authMethod = AuthMethod.Credential,
RetryPolicyOptions? retryPolicy = null,
CancellationToken cancellationToken = default)
{
@@ -161,14 +157,13 @@ public async Task> ListTablesAsync(
(nameof(databaseName), databaseName));
string clusterUri = await GetClusterUriAsync(subscriptionId, clusterName, tenant, retryPolicy);
- return await ListTablesAsync(clusterUri, databaseName, tenant, authMethod, retryPolicy, cancellationToken);
+ return await ListTablesAsync(clusterUri, databaseName, tenant, retryPolicy, cancellationToken);
}
public async Task> ListTablesAsync(
string clusterUri,
string databaseName,
string? tenant = null,
- AuthMethod? authMethod = AuthMethod.Credential,
RetryPolicyOptions? retryPolicy = null,
CancellationToken cancellationToken = default)
{
@@ -188,12 +183,11 @@ public async Task GetTableSchemaAsync(
string databaseName,
string tableName,
string? tenant = null,
- AuthMethod? authMethod = AuthMethod.Credential,
RetryPolicyOptions? retryPolicy = null,
CancellationToken cancellationToken = default)
{
string clusterUri = await GetClusterUriAsync(subscriptionId, clusterName, tenant, retryPolicy);
- return await GetTableSchemaAsync(clusterUri, databaseName, tableName, tenant, authMethod, retryPolicy, cancellationToken);
+ return await GetTableSchemaAsync(clusterUri, databaseName, tableName, tenant, retryPolicy, cancellationToken);
}
public async Task GetTableSchemaAsync(
@@ -201,7 +195,6 @@ public async Task GetTableSchemaAsync(
string databaseName,
string tableName,
string? tenant = null,
- AuthMethod? authMethod = AuthMethod.Credential,
RetryPolicyOptions? retryPolicy = null,
CancellationToken cancellationToken = default)
{
@@ -232,7 +225,6 @@ public async Task> QueryItemsAsync(
string databaseName,
string query,
string? tenant = null,
- AuthMethod? authMethod = AuthMethod.Credential,
RetryPolicyOptions? retryPolicy = null,
CancellationToken cancellationToken = default)
{
@@ -243,7 +235,7 @@ public async Task> QueryItemsAsync(
(nameof(query), query));
string clusterUri = await GetClusterUriAsync(subscriptionId, clusterName, tenant, retryPolicy);
- return await QueryItemsAsync(clusterUri, databaseName, query, tenant, authMethod, retryPolicy, cancellationToken);
+ return await QueryItemsAsync(clusterUri, databaseName, query, tenant, retryPolicy, cancellationToken);
}
public async Task> QueryItemsAsync(
@@ -251,7 +243,6 @@ public async Task> QueryItemsAsync(
string databaseName,
string query,
string? tenant = null,
- AuthMethod? authMethod = AuthMethod.Credential,
RetryPolicyOptions? retryPolicy = null,
CancellationToken cancellationToken = default)
{
diff --git a/tools/Azure.Mcp.Tools.Kusto/src/Services/Models/KustoClusterData.cs b/tools/Azure.Mcp.Tools.Kusto/src/Services/Models/KustoClusterData.cs
index 64cc81e9a9..4e635b1f1b 100644
--- a/tools/Azure.Mcp.Tools.Kusto/src/Services/Models/KustoClusterData.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/src/Services/Models/KustoClusterData.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using System.Text.Json;
using System.Text.Json.Serialization;
using Azure.Mcp.Tools.Kusto.Commands;
using Microsoft.Mcp.Core.Services.Azure.Models;
diff --git a/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/ClusterGetCommandTests.cs b/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/ClusterGetCommandTests.cs
index 31a36d95ac..4cc7fd3052 100644
--- a/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/ClusterGetCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/ClusterGetCommandTests.cs
@@ -7,7 +7,6 @@
using Azure.Mcp.Tools.Kusto.Models;
using Azure.Mcp.Tools.Kusto.Services;
using Microsoft.Mcp.Core.Options;
-using Microsoft.Mcp.Tests.Client;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using Xunit;
diff --git a/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/ClusterListCommandTests.cs b/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/ClusterListCommandTests.cs
index 2b7049d396..84fc159f35 100644
--- a/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/ClusterListCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/ClusterListCommandTests.cs
@@ -7,7 +7,6 @@
using Azure.Mcp.Tools.Kusto.Commands;
using Azure.Mcp.Tools.Kusto.Services;
using Microsoft.Mcp.Core.Options;
-using Microsoft.Mcp.Tests.Client;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using Xunit;
diff --git a/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/DatabaseListCommandTests.cs b/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/DatabaseListCommandTests.cs
index 0e72b25796..cef70c52a5 100644
--- a/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/DatabaseListCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/DatabaseListCommandTests.cs
@@ -5,9 +5,7 @@
using Azure.Mcp.Tests.Commands;
using Azure.Mcp.Tools.Kusto.Commands;
using Azure.Mcp.Tools.Kusto.Services;
-using Microsoft.Mcp.Core.Models;
using Microsoft.Mcp.Core.Options;
-using Microsoft.Mcp.Tests.Client;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using Xunit;
@@ -32,13 +30,13 @@ public async Task ExecuteAsync_ReturnsDatabases(string cliArgs, bool useClusterU
{
Service.ListDatabasesAsync(
"https://mycluster.kusto.windows.net",
- Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ Arg.Any(), Arg.Any(), Arg.Any())
.Returns(expectedDatabases);
}
else
{
Service.ListDatabasesAsync(
- "sub1", "mycluster", Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ "sub1", "mycluster", Arg.Any(), Arg.Any(), Arg.Any())
.Returns(expectedDatabases);
}
@@ -59,13 +57,13 @@ public async Task ExecuteAsync_ReturnsEmpty_WhenNoDatabasesExist(string cliArgs,
{
Service.ListDatabasesAsync(
"https://mycluster.kusto.windows.net",
- Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ Arg.Any(), Arg.Any(), Arg.Any())
.Returns([]);
}
else
{
Service.ListDatabasesAsync(
- "sub1", "mycluster", Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ "sub1", "mycluster", Arg.Any(), Arg.Any(), Arg.Any())
.Returns([]);
}
@@ -87,13 +85,13 @@ public async Task ExecuteAsync_HandlesException_AndSetsException(string cliArgs,
{
Service.ListDatabasesAsync(
"https://mycluster.kusto.windows.net",
- Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ Arg.Any(), Arg.Any(), Arg.Any())
.ThrowsAsync(new Exception("Test error"));
}
else
{
Service.ListDatabasesAsync(
- "sub1", "mycluster", Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ "sub1", "mycluster", Arg.Any(), Arg.Any(), Arg.Any())
.ThrowsAsync(new Exception("Test error"));
}
diff --git a/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/QueryCommandTests.cs b/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/QueryCommandTests.cs
index 2d4fcb25db..cbe2950135 100644
--- a/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/QueryCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/QueryCommandTests.cs
@@ -6,9 +6,7 @@
using Azure.Mcp.Tests.Commands;
using Azure.Mcp.Tools.Kusto.Commands;
using Azure.Mcp.Tools.Kusto.Services;
-using Microsoft.Mcp.Core.Models;
using Microsoft.Mcp.Core.Options;
-using Microsoft.Mcp.Tests.Client;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using Xunit;
@@ -35,14 +33,14 @@ public async Task ExecuteAsync_ReturnsQueryResults(string cliArgs, bool useClust
"https://mycluster.kusto.windows.net",
"db1",
"StormEvents | take 1",
- Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ Arg.Any(), Arg.Any(), Arg.Any())
.Returns(expectedJson);
}
else
{
Service.QueryItemsAsync(
"sub1", "mycluster", "db1", "StormEvents | take 1",
- Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ Arg.Any(), Arg.Any(), Arg.Any())
.Returns(expectedJson);
}
@@ -69,14 +67,14 @@ public async Task ExecuteAsync_ReturnsEmpty_WhenNoResults(string cliArgs, bool u
"https://mycluster.kusto.windows.net",
"db1",
"StormEvents | take 1",
- Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ Arg.Any(), Arg.Any(), Arg.Any())
.Returns([]);
}
else
{
Service.QueryItemsAsync(
"sub1", "mycluster", "db1", "StormEvents | take 1",
- Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ Arg.Any(), Arg.Any(), Arg.Any())
.Returns([]);
}
@@ -97,14 +95,14 @@ public async Task ExecuteAsync_HandlesException_AndSetsException(string cliArgs,
"https://mycluster.kusto.windows.net",
"db1",
"StormEvents | take 1",
- Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ Arg.Any(), Arg.Any(), Arg.Any())
.ThrowsAsync(new Exception("Test error"));
}
else
{
Service.QueryItemsAsync(
"sub1", "mycluster", "db1", "StormEvents | take 1",
- Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ Arg.Any(), Arg.Any(), Arg.Any())
.ThrowsAsync(new Exception("Test error"));
}
diff --git a/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/SampleCommandTests.cs b/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/SampleCommandTests.cs
index 6721f55e66..83c9ec1915 100644
--- a/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/SampleCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/SampleCommandTests.cs
@@ -6,9 +6,7 @@
using Azure.Mcp.Tests.Commands;
using Azure.Mcp.Tools.Kusto.Commands;
using Azure.Mcp.Tools.Kusto.Services;
-using Microsoft.Mcp.Core.Models;
using Microsoft.Mcp.Core.Options;
-using Microsoft.Mcp.Tests.Client;
using NSubstitute;
using Xunit;
@@ -34,14 +32,14 @@ public async Task ExecuteAsync_ReturnsSampleResults(string cliArgs, bool useClus
"https://mycluster.kusto.windows.net",
"db1",
"['table1'] | sample 10",
- Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ Arg.Any(), Arg.Any(), Arg.Any())
.Returns(expectedJson);
}
else
{
Service.QueryItemsAsync(
"sub1", "mycluster", "db1", "['table1'] | sample 10",
- Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ Arg.Any(), Arg.Any(), Arg.Any())
.Returns(expectedJson);
}
@@ -68,14 +66,14 @@ public async Task ExecuteAsync_ReturnsEmpty_WhenNoResults(string cliArgs, bool u
"https://mycluster.kusto.windows.net",
"db1",
"['table1'] | sample 10",
- Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ Arg.Any(), Arg.Any(), Arg.Any())
.Returns([]);
}
else
{
Service.QueryItemsAsync(
"sub1", "mycluster", "db1", "['table1'] | sample 10",
- Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ Arg.Any(), Arg.Any(), Arg.Any())
.Returns([]);
}
@@ -97,14 +95,14 @@ public async Task ExecuteAsync_ReturnsEmpty_WhenNoResults(string cliArgs, bool u
// "https://mycluster.kusto.windows.net",
// "db1",
// "table1 | sample 10",
- // Arg.Any(), Arg.Any(), Arg.Any())
+ // Arg.Any(), Arg.Any())
// .Returns(Task.FromException>(new Exception("Test error")));
// }
// else
// {
// _kusto.QueryItems(
// "sub1", "mycluster", "db1", "table1 | sample 10",
- // Arg.Any(), Arg.Any(), Arg.Any())
+ // Arg.Any(), Arg.Any())
// .Returns(Task.FromException>(new Exception("Test error")));
// }
// var command = new SampleCommand(_logger, _kusto);
diff --git a/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/TableListCommandTests.cs b/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/TableListCommandTests.cs
index 6317dd3847..b03ecf4f68 100644
--- a/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/TableListCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/TableListCommandTests.cs
@@ -5,9 +5,7 @@
using Azure.Mcp.Tests.Commands;
using Azure.Mcp.Tools.Kusto.Commands;
using Azure.Mcp.Tools.Kusto.Services;
-using Microsoft.Mcp.Core.Models;
using Microsoft.Mcp.Core.Options;
-using Microsoft.Mcp.Tests.Client;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using Xunit;
@@ -32,14 +30,14 @@ public async Task ExecuteAsync_ReturnsTables(string cliArgs, bool useClusterUri)
Service.ListTablesAsync(
"https://mycluster.kusto.windows.net",
"db1",
- Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ Arg.Any(), Arg.Any(), Arg.Any())
.Returns(expectedTables);
}
else
{
Service.ListTablesAsync(
"sub1", "mycluster", "db1",
- Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ Arg.Any(), Arg.Any(), Arg.Any())
.Returns(expectedTables);
}
@@ -59,14 +57,14 @@ public async Task ExecuteAsync_ReturnsEmpty_WhenNoTables(string cliArgs, bool us
Service.ListTablesAsync(
"https://mycluster.kusto.windows.net",
"db1",
- Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ Arg.Any(), Arg.Any(), Arg.Any())
.Returns([]);
}
else
{
Service.ListTablesAsync(
"sub1", "mycluster", "db1",
- Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ Arg.Any(), Arg.Any(), Arg.Any())
.Returns([]);
}
@@ -86,14 +84,14 @@ public async Task ExecuteAsync_HandlesException_AndSetsException(string cliArgs,
Service.ListTablesAsync(
"https://mycluster.kusto.windows.net",
"db1",
- Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ Arg.Any(), Arg.Any(), Arg.Any())
.ThrowsAsync(new Exception("Test error"));
}
else
{
Service.ListTablesAsync(
"sub1", "mycluster", "db1",
- Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ Arg.Any(), Arg.Any(), Arg.Any())
.ThrowsAsync(new Exception("Test error"));
}
diff --git a/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/TableSchemaCommandTests.cs b/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/TableSchemaCommandTests.cs
index 9c4e575d16..1f8a65139f 100644
--- a/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/TableSchemaCommandTests.cs
+++ b/tools/Azure.Mcp.Tools.Kusto/tests/Azure.Mcp.Tools.Kusto.Tests/TableSchemaCommandTests.cs
@@ -5,9 +5,7 @@
using Azure.Mcp.Tests.Commands;
using Azure.Mcp.Tools.Kusto.Commands;
using Azure.Mcp.Tools.Kusto.Services;
-using Microsoft.Mcp.Core.Models;
using Microsoft.Mcp.Core.Options;
-using Microsoft.Mcp.Tests.Client;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using Xunit;
@@ -34,14 +32,14 @@ public async Task ExecuteAsync_ReturnsSchema(string cliArgs, bool useClusterUri)
"https://mycluster.kusto.windows.net",
"db1",
"table1",
- Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ Arg.Any(), Arg.Any(), Arg.Any())
.Returns(expectedSchema);
}
else
{
Service.GetTableSchemaAsync(
"sub1", "mycluster", "db1", "table1",
- Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ Arg.Any(), Arg.Any(), Arg.Any())
.Returns(expectedSchema);
}
@@ -66,14 +64,14 @@ public async Task ExecuteAsync_ReturnsNull_WhenNoSchema(string cliArgs, bool use
"https://mycluster.kusto.windows.net",
"db1",
"table1",
- Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ Arg.Any(), Arg.Any(), Arg.Any())
.ThrowsAsync(new Exception("Test error"));
}
else
{
Service.GetTableSchemaAsync(
"sub1", "mycluster", "db1", "table1",
- Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
+ Arg.Any(), Arg.Any(), Arg.Any