diff --git a/servers/Azure.Mcp.Server/changelog-entries/1782416793886.yaml b/servers/Azure.Mcp.Server/changelog-entries/1782416793886.yaml new file mode 100644 index 0000000000..9e40fa4b10 --- /dev/null +++ b/servers/Azure.Mcp.Server/changelog-entries/1782416793886.yaml @@ -0,0 +1,3 @@ +changes: + - section: "Breaking Changes" + description: "Removed unused parameters from Search tools." diff --git a/tools/Azure.Mcp.Tools.Search/src/Azure.Mcp.Tools.Search.csproj b/tools/Azure.Mcp.Tools.Search/src/Azure.Mcp.Tools.Search.csproj index febeb8c15f..2f89e7f9a0 100644 --- a/tools/Azure.Mcp.Tools.Search/src/Azure.Mcp.Tools.Search.csproj +++ b/tools/Azure.Mcp.Tools.Search/src/Azure.Mcp.Tools.Search.csproj @@ -7,7 +7,7 @@ - + @@ -16,6 +16,5 @@ - \ No newline at end of file diff --git a/tools/Azure.Mcp.Tools.Search/src/Commands/Index/IndexGetCommand.cs b/tools/Azure.Mcp.Tools.Search/src/Commands/Index/IndexGetCommand.cs index e62668d596..8ce56215c2 100644 --- a/tools/Azure.Mcp.Tools.Search/src/Commands/Index/IndexGetCommand.cs +++ b/tools/Azure.Mcp.Tools.Search/src/Commands/Index/IndexGetCommand.cs @@ -2,14 +2,11 @@ // Licensed under the MIT License. using Azure.Mcp.Tools.Search.Models; -using Azure.Mcp.Tools.Search.Options; using Azure.Mcp.Tools.Search.Options.Index; using Azure.Mcp.Tools.Search.Services; using Microsoft.Extensions.Logging; using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Extensions; using Microsoft.Mcp.Core.Models.Command; -using Microsoft.Mcp.Core.Models.Option; namespace Azure.Mcp.Tools.Search.Commands.Index; @@ -28,39 +25,18 @@ namespace Azure.Mcp.Tools.Search.Commands.Index; ReadOnly = true, Secret = false, LocalRequired = false)] -public sealed class IndexGetCommand(ILogger logger, ISearchService searchService) : GlobalCommand() +public sealed class IndexGetCommand(ILogger logger, ISearchService searchService) + : AuthenticatedCommand { private readonly ILogger _logger = logger; private readonly ISearchService _searchService = searchService; - protected override void RegisterOptions(Command command) + public override async Task ExecuteAsync(CommandContext context, IndexGetOptions options, CancellationToken cancellationToken) { - base.RegisterOptions(command); - command.Options.Add(SearchOptionDefinitions.Service); - command.Options.Add(SearchOptionDefinitions.Index.AsOptional()); - } - - protected override IndexGetOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.Service = parseResult.GetValueOrDefault(SearchOptionDefinitions.Service.Name); - options.Index = parseResult.GetValueOrDefault(SearchOptionDefinitions.Index.Name); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - try { var indexes = await _searchService.GetIndexDetails( - options.Service!, + options.Service, options.Index, options.RetryPolicy, cancellationToken); @@ -76,5 +52,5 @@ public override async Task ExecuteAsync(CommandContext context, return context.Response; } - internal sealed record IndexGetCommandResult(List Indexes); + public sealed record IndexGetCommandResult(List Indexes); } diff --git a/tools/Azure.Mcp.Tools.Search/src/Commands/Index/IndexQueryCommand.cs b/tools/Azure.Mcp.Tools.Search/src/Commands/Index/IndexQueryCommand.cs index dc66c60314..4458b4895d 100644 --- a/tools/Azure.Mcp.Tools.Search/src/Commands/Index/IndexQueryCommand.cs +++ b/tools/Azure.Mcp.Tools.Search/src/Commands/Index/IndexQueryCommand.cs @@ -1,12 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Azure.Mcp.Tools.Search.Options; +using System.Text.Json; using Azure.Mcp.Tools.Search.Options.Index; using Azure.Mcp.Tools.Search.Services; using Microsoft.Extensions.Logging; using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Extensions; using Microsoft.Mcp.Core.Models.Command; namespace Azure.Mcp.Tools.Search.Commands.Index; @@ -25,43 +24,20 @@ namespace Azure.Mcp.Tools.Search.Commands.Index; ReadOnly = true, Secret = false, LocalRequired = false)] -public sealed class IndexQueryCommand(ILogger logger, ISearchService searchService) : GlobalCommand() +public sealed class IndexQueryCommand(ILogger logger, ISearchService searchService) + : AuthenticatedCommand> { private readonly ILogger _logger = logger; private readonly ISearchService _searchService = searchService; - protected override void RegisterOptions(Command command) + public override async Task ExecuteAsync(CommandContext context, IndexQueryOptions options, CancellationToken cancellationToken) { - base.RegisterOptions(command); - command.Options.Add(SearchOptionDefinitions.Service); - command.Options.Add(SearchOptionDefinitions.Index); - command.Options.Add(SearchOptionDefinitions.Query); - } - - protected override IndexQueryOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.Service = parseResult.GetValueOrDefault(SearchOptionDefinitions.Service.Name); - options.Index = parseResult.GetValueOrDefault(SearchOptionDefinitions.Index.Name); - options.Query = parseResult.GetValueOrDefault(SearchOptionDefinitions.Query.Name); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - try { var results = await _searchService.QueryIndex( - options.Service!, - options.Index!, - options.Query!, + options.Service, + options.Index, + options.Query, options.RetryPolicy, cancellationToken); diff --git a/tools/Azure.Mcp.Tools.Search/src/Commands/Knowledge/KnowledgeBaseGetCommand.cs b/tools/Azure.Mcp.Tools.Search/src/Commands/Knowledge/KnowledgeBaseGetCommand.cs index 5b854dfea9..8899a7cf0b 100644 --- a/tools/Azure.Mcp.Tools.Search/src/Commands/Knowledge/KnowledgeBaseGetCommand.cs +++ b/tools/Azure.Mcp.Tools.Search/src/Commands/Knowledge/KnowledgeBaseGetCommand.cs @@ -1,14 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Azure.Mcp.Tools.Search.Options; using Azure.Mcp.Tools.Search.Options.Knowledge; using Azure.Mcp.Tools.Search.Services; using Microsoft.Extensions.Logging; using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Extensions; using Microsoft.Mcp.Core.Models.Command; -using Microsoft.Mcp.Core.Models.Option; namespace Azure.Mcp.Tools.Search.Commands.Knowledge; @@ -30,38 +27,17 @@ Gets the details of Azure AI Search knowledge bases. Knowledge bases encapsulate ReadOnly = true, Secret = false, LocalRequired = false)] -public sealed class KnowledgeBaseGetCommand(ILogger logger, ISearchService searchService) : GlobalCommand() +public sealed class KnowledgeBaseGetCommand(ILogger logger, ISearchService searchService) + : AuthenticatedCommand { private readonly ILogger _logger = logger; private readonly ISearchService _searchService = searchService; - protected override void RegisterOptions(Command command) + public override async Task ExecuteAsync(CommandContext context, KnowledgeBaseGetOptions options, CancellationToken cancellationToken) { - base.RegisterOptions(command); - command.Options.Add(SearchOptionDefinitions.Service); - command.Options.Add(SearchOptionDefinitions.KnowledgeBase.AsOptional()); - } - - protected override KnowledgeBaseGetOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.Service = parseResult.GetValueOrDefault(SearchOptionDefinitions.Service.Name); - options.KnowledgeBase = parseResult.GetValueOrDefault(SearchOptionDefinitions.KnowledgeBase.Name); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - try { - var bases = await _searchService.ListKnowledgeBases(options.Service!, options.KnowledgeBase, options.RetryPolicy, cancellationToken); + var bases = await _searchService.ListKnowledgeBases(options.Service, options.KnowledgeBase, options.RetryPolicy, cancellationToken); context.Response.Results = ResponseResult.Create(new(bases ?? []), SearchJsonContext.Default.KnowledgeBaseGetCommandResult); } catch (Exception ex) @@ -73,5 +49,5 @@ public override async Task ExecuteAsync(CommandContext context, return context.Response; } - internal sealed record KnowledgeBaseGetCommandResult(List KnowledgeBases); + public sealed record KnowledgeBaseGetCommandResult(List KnowledgeBases); } diff --git a/tools/Azure.Mcp.Tools.Search/src/Commands/Knowledge/KnowledgeBaseRetrieveCommand.cs b/tools/Azure.Mcp.Tools.Search/src/Commands/Knowledge/KnowledgeBaseRetrieveCommand.cs index 4df07b3431..5aedd6267f 100644 --- a/tools/Azure.Mcp.Tools.Search/src/Commands/Knowledge/KnowledgeBaseRetrieveCommand.cs +++ b/tools/Azure.Mcp.Tools.Search/src/Commands/Knowledge/KnowledgeBaseRetrieveCommand.cs @@ -2,15 +2,11 @@ // Licensed under the MIT License. using System.Net; -using Azure.Mcp.Core.Commands; -using Azure.Mcp.Tools.Search.Options; using Azure.Mcp.Tools.Search.Options.Knowledge; using Azure.Mcp.Tools.Search.Services; using Microsoft.Extensions.Logging; using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Extensions; using Microsoft.Mcp.Core.Models.Command; -using Microsoft.Mcp.Core.Models.Option; namespace Azure.Mcp.Tools.Search.Commands.Knowledge; @@ -34,70 +30,46 @@ namespace Azure.Mcp.Tools.Search.Commands.Knowledge; ReadOnly = true, Secret = false, LocalRequired = false)] -public sealed class KnowledgeBaseRetrieveCommand(ILogger logger, ISearchService searchService) : GlobalCommand() +public sealed class KnowledgeBaseRetrieveCommand(ILogger logger, ISearchService searchService) + : AuthenticatedCommand { private readonly ILogger _logger = logger; private readonly ISearchService _searchService = searchService; - protected override void RegisterOptions(Command command) + public override void ValidateOptions(KnowledgeBaseRetrieveOptions options, ValidationResult validationResult) { - base.RegisterOptions(command); - command.Options.Add(SearchOptionDefinitions.Service); - command.Options.Add(SearchOptionDefinitions.KnowledgeBase); - command.Options.Add(SearchOptionDefinitions.KnowledgeQuery.AsOptional()); - command.Options.Add(SearchOptionDefinitions.Messages.AsOptional()); - command.Validators.Add(commandResult => + base.ValidateOptions(options, validationResult); + + if (string.IsNullOrEmpty(options.Query) && (options.Messages == null || options.Messages.Length == 0)) { - var query = commandResult.GetValueOrDefault(SearchOptionDefinitions.KnowledgeQuery.Name); - var messages = commandResult.GetValueOrDefault(SearchOptionDefinitions.Messages.Name) ?? []; - if (string.IsNullOrEmpty(query) && messages.Length == 0) - { - commandResult.AddError("Either --query or at least one --messages entry must be provided."); - } - else if (!string.IsNullOrEmpty(query) && messages.Length > 0) - { - commandResult.AddError("Specifying both --query and --messages is not allowed."); - } + validationResult.Errors.Add("Either --query or at least one --messages entry must be provided."); + } + else if (!string.IsNullOrEmpty(options.Query) && options.Messages is { Length: > 0 }) + { + validationResult.Errors.Add("Specifying both --query and --messages is not allowed."); + } - if (messages.Length > 0) + if (options.Messages is { Length: > 0 }) + { + foreach ((var index, var message) in options.Messages.Index()) { - foreach ((var index, var message) in messages.Index()) + try { - try - { - ParseMessage(message); - } - catch (ArgumentException ex) - { - commandResult.AddError($"Message {index}: {ex.Message}"); - continue; - } + ParseMessage(message); + } + catch (ArgumentException ex) + { + validationResult.Errors.Add($"Message {index}: {ex.Message}"); + continue; } } - }); - } - - protected override KnowledgeBaseRetrieveOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.Service = parseResult.GetValueOrDefault(SearchOptionDefinitions.Service.Name); - options.KnowledgeBase = parseResult.GetValueOrDefault(SearchOptionDefinitions.KnowledgeBase.Name); - options.Query = parseResult.GetValueOrDefault(SearchOptionDefinitions.KnowledgeQuery.Name); - options.Messages = parseResult.GetValueOrDefault(SearchOptionDefinitions.Messages.Name) ?? []; - return options; + } } - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) + public override async Task ExecuteAsync(CommandContext context, KnowledgeBaseRetrieveOptions options, CancellationToken cancellationToken) { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - List<(string role, string message)>? parsedMessages = null; - if (options.Messages.Length > 0) + if (options.Messages is { Length: > 0 }) { try { @@ -113,7 +85,7 @@ public override async Task ExecuteAsync(CommandContext context, try { - var result = await _searchService.RetrieveFromKnowledgeBase(options.Service!, options.KnowledgeBase!, options.Query, parsedMessages, options.RetryPolicy, cancellationToken); + var result = await _searchService.RetrieveFromKnowledgeBase(options.Service, options.KnowledgeBase, options.Query, parsedMessages, options.RetryPolicy, cancellationToken); context.Response.Results = ResponseResult.Create(new(result), SearchJsonContext.Default.KnowledgeBaseRetrieveCommandResult); } catch (Exception ex) @@ -145,5 +117,5 @@ internal static (string role, string content) ParseMessage(string message) return (role, content); } - internal sealed record KnowledgeBaseRetrieveCommandResult(string RetrievalResult); + public sealed record KnowledgeBaseRetrieveCommandResult(string RetrievalResult); } diff --git a/tools/Azure.Mcp.Tools.Search/src/Commands/Knowledge/KnowledgeSourceGetCommand.cs b/tools/Azure.Mcp.Tools.Search/src/Commands/Knowledge/KnowledgeSourceGetCommand.cs index a899ed731d..5e6d06ca83 100644 --- a/tools/Azure.Mcp.Tools.Search/src/Commands/Knowledge/KnowledgeSourceGetCommand.cs +++ b/tools/Azure.Mcp.Tools.Search/src/Commands/Knowledge/KnowledgeSourceGetCommand.cs @@ -1,14 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Azure.Mcp.Tools.Search.Options; using Azure.Mcp.Tools.Search.Options.Knowledge; using Azure.Mcp.Tools.Search.Services; using Microsoft.Extensions.Logging; using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Extensions; using Microsoft.Mcp.Core.Models.Command; -using Microsoft.Mcp.Core.Models.Option; namespace Azure.Mcp.Tools.Search.Commands.Knowledge; @@ -32,38 +29,17 @@ within the specified service. ReadOnly = true, Secret = false, LocalRequired = false)] -public sealed class KnowledgeSourceGetCommand(ILogger logger, ISearchService searchService) : GlobalCommand() +public sealed class KnowledgeSourceGetCommand(ILogger logger, ISearchService searchService) + : AuthenticatedCommand { private readonly ILogger _logger = logger; private readonly ISearchService _searchService = searchService; - protected override void RegisterOptions(Command command) + public override async Task ExecuteAsync(CommandContext context, KnowledgeSourceGetOptions options, CancellationToken cancellationToken) { - base.RegisterOptions(command); - command.Options.Add(SearchOptionDefinitions.Service); - command.Options.Add(SearchOptionDefinitions.KnowledgeSource.AsOptional()); - } - - protected override KnowledgeSourceGetOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.Service = parseResult.GetValueOrDefault(SearchOptionDefinitions.Service.Name); - options.KnowledgeSource = parseResult.GetValueOrDefault(SearchOptionDefinitions.KnowledgeSource.Name); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - try { - var sources = await _searchService.ListKnowledgeSources(options.Service!, options.KnowledgeSource, options.RetryPolicy, cancellationToken); + var sources = await _searchService.ListKnowledgeSources(options.Service, options.KnowledgeSource, options.RetryPolicy, cancellationToken); context.Response.Results = ResponseResult.Create(new(sources ?? []), SearchJsonContext.Default.KnowledgeSourceGetCommandResult); } catch (Exception ex) @@ -75,5 +51,5 @@ public override async Task ExecuteAsync(CommandContext context, return context.Response; } - internal sealed record KnowledgeSourceGetCommandResult(List KnowledgeSources); + public sealed record KnowledgeSourceGetCommandResult(List KnowledgeSources); } diff --git a/tools/Azure.Mcp.Tools.Search/src/Commands/SearchJsonContext.cs b/tools/Azure.Mcp.Tools.Search/src/Commands/SearchJsonContext.cs index f034e5ff73..ef863c05a9 100644 --- a/tools/Azure.Mcp.Tools.Search/src/Commands/SearchJsonContext.cs +++ b/tools/Azure.Mcp.Tools.Search/src/Commands/SearchJsonContext.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.Search.Commands.Index; using Azure.Mcp.Tools.Search.Commands.Knowledge; @@ -18,7 +19,4 @@ namespace Azure.Mcp.Tools.Search.Commands; [JsonSerializable(typeof(KnowledgeBaseGetCommand.KnowledgeBaseGetCommandResult))] [JsonSerializable(typeof(KnowledgeBaseRetrieveCommand.KnowledgeBaseRetrieveCommandResult))] [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] -internal sealed partial class SearchJsonContext : JsonSerializerContext -{ - // This class is generated at runtime by the source generator. -} +internal sealed partial class SearchJsonContext : JsonSerializerContext; diff --git a/tools/Azure.Mcp.Tools.Search/src/Commands/Service/ServiceListCommand.cs b/tools/Azure.Mcp.Tools.Search/src/Commands/Service/ServiceListCommand.cs index dde3f0637e..503320a8c7 100644 --- a/tools/Azure.Mcp.Tools.Search/src/Commands/Service/ServiceListCommand.cs +++ b/tools/Azure.Mcp.Tools.Search/src/Commands/Service/ServiceListCommand.cs @@ -2,13 +2,12 @@ // Licensed under the MIT License. using Azure.Mcp.Core.Commands.Subscription; +using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Tools.Search.Options.Service; using Azure.Mcp.Tools.Search.Services; using Microsoft.Extensions.Logging; using Microsoft.Mcp.Core.Commands; -using Microsoft.Mcp.Core.Extensions; using Microsoft.Mcp.Core.Models.Command; -using Microsoft.Mcp.Core.Models.Option; namespace Azure.Mcp.Tools.Search.Commands.Service; @@ -23,33 +22,14 @@ namespace Azure.Mcp.Tools.Search.Commands.Service; ReadOnly = true, Secret = false, LocalRequired = false)] -public sealed class ServiceListCommand(ILogger logger, ISearchService searchService) : SubscriptionCommand() +public sealed class ServiceListCommand(ILogger logger, ISearchService searchService, ISubscriptionResolver subscriptionResolver) + : SubscriptionCommand(subscriptionResolver) { private readonly ILogger _logger = logger; private readonly ISearchService _searchService = searchService; - protected override void RegisterOptions(Command command) + public override async Task ExecuteAsync(CommandContext context, ServiceListOptions options, CancellationToken cancellationToken) { - base.RegisterOptions(command); - command.Options.Add(OptionDefinitions.Common.ResourceGroup.AsOptional()); - } - - protected override ServiceListOptions BindOptions(ParseResult parseResult) - { - var options = base.BindOptions(parseResult); - options.ResourceGroup = parseResult.GetValueOrDefault(OptionDefinitions.Common.ResourceGroup.Name); - return options; - } - - public override async Task ExecuteAsync(CommandContext context, ParseResult parseResult, CancellationToken cancellationToken) - { - if (!Validate(parseResult.CommandResult, context.Response).IsValid) - { - return context.Response; - } - - var options = BindOptions(parseResult); - try { var services = await _searchService.ListServices( @@ -70,5 +50,5 @@ public override async Task ExecuteAsync(CommandContext context, return context.Response; } - internal sealed record ServiceListCommandResult(List Services); + public sealed record ServiceListCommandResult(List Services); } diff --git a/tools/Azure.Mcp.Tools.Search/src/GlobalUsings.cs b/tools/Azure.Mcp.Tools.Search/src/GlobalUsings.cs deleted file mode 100644 index 14a017b104..0000000000 --- a/tools/Azure.Mcp.Tools.Search/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.Search/src/Models/FieldInfo.cs b/tools/Azure.Mcp.Tools.Search/src/Models/FieldInfo.cs index 894b1d1e46..43e78991be 100644 --- a/tools/Azure.Mcp.Tools.Search/src/Models/FieldInfo.cs +++ b/tools/Azure.Mcp.Tools.Search/src/Models/FieldInfo.cs @@ -1,16 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; - namespace Azure.Mcp.Tools.Search.Models; public sealed record FieldInfo( - [property: JsonPropertyName("name")] string Name, - [property: JsonPropertyName("type")] string Type, - [property: JsonPropertyName("key")] bool? Key, - [property: JsonPropertyName("searchable")] bool? Searchable, - [property: JsonPropertyName("filterable")] bool? Filterable, - [property: JsonPropertyName("sortable")] bool? Sortable, - [property: JsonPropertyName("facetable")] bool? Facetable, - [property: JsonPropertyName("retrievable")] bool? Retrievable); + string Name, + string Type, + bool? Key, + bool? Searchable, + bool? Filterable, + bool? Sortable, + bool? Facetable, + bool? Retrievable); diff --git a/tools/Azure.Mcp.Tools.Search/src/Models/IndexInfo.cs b/tools/Azure.Mcp.Tools.Search/src/Models/IndexInfo.cs index f8438ff076..e2f7400e6c 100644 --- a/tools/Azure.Mcp.Tools.Search/src/Models/IndexInfo.cs +++ b/tools/Azure.Mcp.Tools.Search/src/Models/IndexInfo.cs @@ -1,11 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; - namespace Azure.Mcp.Tools.Search.Models; -public sealed record IndexInfo( - [property: JsonPropertyName("name")] string Name, - [property: JsonPropertyName("description")] string? Description, - [property: JsonPropertyName("fields")] List? Fields); +public sealed record IndexInfo(string Name, string? Description, List? Fields); diff --git a/tools/Azure.Mcp.Tools.Search/src/Options/BaseSearchOptions.cs b/tools/Azure.Mcp.Tools.Search/src/Options/BaseSearchOptions.cs deleted file mode 100644 index 8279afd440..0000000000 --- a/tools/Azure.Mcp.Tools.Search/src/Options/BaseSearchOptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; -using Microsoft.Mcp.Core.Options; - -namespace Azure.Mcp.Tools.Search.Options; - -public class BaseSearchOptions : GlobalOptions -{ - [JsonPropertyName(SearchOptionDefinitions.ServiceName)] - public string? Service { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Search/src/Options/Index/BaseIndexOptions.cs b/tools/Azure.Mcp.Tools.Search/src/Options/Index/BaseIndexOptions.cs deleted file mode 100644 index 48e484ef15..0000000000 --- a/tools/Azure.Mcp.Tools.Search/src/Options/Index/BaseIndexOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Text.Json.Serialization; - -namespace Azure.Mcp.Tools.Search.Options.Index; - -public class BaseIndexOptions : BaseSearchOptions -{ - [JsonPropertyName(SearchOptionDefinitions.IndexName)] - public string? Index { get; set; } -} diff --git a/tools/Azure.Mcp.Tools.Search/src/Options/Index/IndexGetOptions.cs b/tools/Azure.Mcp.Tools.Search/src/Options/Index/IndexGetOptions.cs index 2f1198d05b..4cd747c704 100644 --- a/tools/Azure.Mcp.Tools.Search/src/Options/Index/IndexGetOptions.cs +++ b/tools/Azure.Mcp.Tools.Search/src/Options/Index/IndexGetOptions.cs @@ -1,6 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using Microsoft.Mcp.Core.Options; + namespace Azure.Mcp.Tools.Search.Options.Index; -public class IndexGetOptions : BaseIndexOptions; +public sealed class IndexGetOptions +{ + [Option(Description = SearchOptionDescriptions.Service)] + public required string Service { get; set; } + + [Option(Description = SearchOptionDescriptions.Index)] + public string? Index { get; set; } + + [OptionContainer(Prefix = "retry")] + public RetryPolicyOptions? RetryPolicy { get; set; } +} diff --git a/tools/Azure.Mcp.Tools.Search/src/Options/Index/IndexQueryOptions.cs b/tools/Azure.Mcp.Tools.Search/src/Options/Index/IndexQueryOptions.cs index 78bddac29b..b930122402 100644 --- a/tools/Azure.Mcp.Tools.Search/src/Options/Index/IndexQueryOptions.cs +++ b/tools/Azure.Mcp.Tools.Search/src/Options/Index/IndexQueryOptions.cs @@ -1,12 +1,21 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.Search.Options.Index; -public class IndexQueryOptions : BaseIndexOptions +public class IndexQueryOptions { - [JsonPropertyName(SearchOptionDefinitions.QueryName)] - public string? Query { get; set; } + [Option(Description = "The search query to execute against the Azure AI Search index.")] + public required string Query { get; set; } + + [Option(Description = SearchOptionDescriptions.Service)] + public required string Service { get; set; } + + [Option(Description = SearchOptionDescriptions.Index)] + public required string Index { get; set; } + + [OptionContainer(Prefix = "retry")] + public RetryPolicyOptions? RetryPolicy { get; set; } } diff --git a/tools/Azure.Mcp.Tools.Search/src/Options/Knowledge/KnowledgeBaseGetOptions.cs b/tools/Azure.Mcp.Tools.Search/src/Options/Knowledge/KnowledgeBaseGetOptions.cs index 1dc2adab47..ae02f95015 100644 --- a/tools/Azure.Mcp.Tools.Search/src/Options/Knowledge/KnowledgeBaseGetOptions.cs +++ b/tools/Azure.Mcp.Tools.Search/src/Options/Knowledge/KnowledgeBaseGetOptions.cs @@ -1,12 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.Search.Options.Knowledge; -public class KnowledgeBaseGetOptions : BaseSearchOptions +public sealed class KnowledgeBaseGetOptions { - [JsonPropertyName(SearchOptionDefinitions.KnowledgeBaseName)] + [Option(Description = SearchOptionDescriptions.KnowledgeBase)] public string? KnowledgeBase { get; set; } + + [Option(Description = SearchOptionDescriptions.Service)] + public required string Service { get; set; } + + [OptionContainer(Prefix = "retry")] + public RetryPolicyOptions? RetryPolicy { get; set; } } diff --git a/tools/Azure.Mcp.Tools.Search/src/Options/Knowledge/KnowledgeBaseRetrieveOptions.cs b/tools/Azure.Mcp.Tools.Search/src/Options/Knowledge/KnowledgeBaseRetrieveOptions.cs index b88653e110..3f1ef8c5c9 100644 --- a/tools/Azure.Mcp.Tools.Search/src/Options/Knowledge/KnowledgeBaseRetrieveOptions.cs +++ b/tools/Azure.Mcp.Tools.Search/src/Options/Knowledge/KnowledgeBaseRetrieveOptions.cs @@ -1,18 +1,24 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.Search.Options.Knowledge; -public class KnowledgeBaseRetrieveOptions : BaseSearchOptions +public sealed class KnowledgeBaseRetrieveOptions { - [JsonPropertyName(SearchOptionDefinitions.KnowledgeBaseName)] - public string? KnowledgeBase { get; set; } + [Option(Description = SearchOptionDescriptions.KnowledgeBase)] + public required string KnowledgeBase { get; set; } - [JsonPropertyName(SearchOptionDefinitions.QueryName)] + [Option(Description = "Natural language query for retrieval when a conversational message history isn't provided.")] public string? Query { get; set; } - [JsonPropertyName(SearchOptionDefinitions.MessagesName)] - public string[] Messages { get; set; } = []; + [Option(Description = "Conversation history messages passed to the knowledge base. Able to specify multiple --messages entries. Each entry formatted as role:content, where role is `user` or `assistant` (e.g., user:How many docs?).")] + public string[]? Messages { get; set; } + + [Option(Description = SearchOptionDescriptions.Service)] + public required string Service { get; set; } + + [OptionContainer(Prefix = "retry")] + public RetryPolicyOptions? RetryPolicy { get; set; } } diff --git a/tools/Azure.Mcp.Tools.Search/src/Options/Knowledge/KnowledgeSourceGetOptions.cs b/tools/Azure.Mcp.Tools.Search/src/Options/Knowledge/KnowledgeSourceGetOptions.cs index 1ae21925da..5628897cd9 100644 --- a/tools/Azure.Mcp.Tools.Search/src/Options/Knowledge/KnowledgeSourceGetOptions.cs +++ b/tools/Azure.Mcp.Tools.Search/src/Options/Knowledge/KnowledgeSourceGetOptions.cs @@ -1,12 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Text.Json.Serialization; +using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.Search.Options.Knowledge; -public class KnowledgeSourceGetOptions : BaseSearchOptions +public sealed class KnowledgeSourceGetOptions { - [JsonPropertyName(SearchOptionDefinitions.KnowledgeSourceName)] + [Option(Description = "The name of the knowledge source within the Azure AI Search service.")] public string? KnowledgeSource { get; set; } + + [Option(Description = SearchOptionDescriptions.Service)] + public required string Service { get; set; } + + [OptionContainer(Prefix = "retry")] + public RetryPolicyOptions? RetryPolicy { get; set; } } diff --git a/tools/Azure.Mcp.Tools.Search/src/Options/SearchOptionDefinitions.cs b/tools/Azure.Mcp.Tools.Search/src/Options/SearchOptionDefinitions.cs deleted file mode 100644 index f6b0152a91..0000000000 --- a/tools/Azure.Mcp.Tools.Search/src/Options/SearchOptionDefinitions.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Azure.Mcp.Tools.Search.Options; - -public static class SearchOptionDefinitions -{ - public const string ServiceName = "service"; - public const string IndexName = "index"; - public const string QueryName = "query"; - public const string KnowledgeBaseName = "knowledge-base"; - public const string KnowledgeSourceName = "knowledge-source"; - public const string MessagesName = "messages"; - - public static readonly Option Service = new( - $"--{ServiceName}" - ) - { - Description = "The name of the Azure AI Search service (e.g., my-search-service).", - Required = true - }; - - public static readonly Option Index = new( - $"--{IndexName}" - ) - { - Description = "The name of the search index within the Azure AI Search service.", - Required = true - }; - - public static readonly Option Query = new( - $"--{QueryName}" - ) - { - Description = "The search query to execute against the Azure AI Search index.", - Required = true - }; - - public static readonly Option KnowledgeBase = new( - $"--{KnowledgeBaseName}" - ) - { - Description = "The name of the knowledge base within the Azure AI Search service.", - Required = true - }; - - public static readonly Option KnowledgeSource = new( - $"--{KnowledgeSourceName}" - ) - { - Description = "The name of the knowledge source within the Azure AI Search service.", - Required = true - }; - - public static readonly Option KnowledgeQuery = new( - $"--{QueryName}" - ) - { - Description = "Natural language query for retrieval when a conversational message history isn't provided.", - Required = false - }; - - public static readonly Option Messages = new( - $"--{MessagesName}") - { - Description = "Conversation history messages passed to the knowledge base. Able to specify multiple --messages entries. Each entry formatted as role:content, where role is `user` or `assistant` (e.g., user:How many docs?).", - Arity = ArgumentArity.ZeroOrMore, - AllowMultipleArgumentsPerToken = true - }; -} diff --git a/tools/Azure.Mcp.Tools.Search/src/Options/SearchOptionDescriptions.cs b/tools/Azure.Mcp.Tools.Search/src/Options/SearchOptionDescriptions.cs new file mode 100644 index 0000000000..df02540ce5 --- /dev/null +++ b/tools/Azure.Mcp.Tools.Search/src/Options/SearchOptionDescriptions.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Azure.Mcp.Tools.Search.Options; + +internal static class SearchOptionDescriptions +{ + internal const string Service = "The name of the Azure AI Search service (e.g., my-search-service)."; + internal const string Index = "The name of the search index within the Azure AI Search service."; + internal const string KnowledgeBase = "The name of the knowledge base within the Azure AI Search service."; +} diff --git a/tools/Azure.Mcp.Tools.Search/src/Options/Service/ServiceListOptions.cs b/tools/Azure.Mcp.Tools.Search/src/Options/Service/ServiceListOptions.cs index 0d0345857a..67411411b2 100644 --- a/tools/Azure.Mcp.Tools.Search/src/Options/Service/ServiceListOptions.cs +++ b/tools/Azure.Mcp.Tools.Search/src/Options/Service/ServiceListOptions.cs @@ -1,8 +1,22 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using Azure.Mcp.Core.Options; using Microsoft.Mcp.Core.Options; namespace Azure.Mcp.Tools.Search.Options.Service; -public class ServiceListOptions : SubscriptionOptions; +public sealed class ServiceListOptions : ISubscriptionOption +{ + [Option(Description = OptionDescriptions.ResourceGroup)] + public string? ResourceGroup { get; set; } + + [Option(Description = OptionDescriptions.Subscription)] + public string? Subscription { get; set; } + + [Option(Description = OptionDescriptions.Tenant)] + public string? Tenant { get; set; } + + [OptionContainer(Prefix = "retry")] + public RetryPolicyOptions? RetryPolicy { get; set; } +} diff --git a/tools/Azure.Mcp.Tools.Search/src/Services/ISearchService.cs b/tools/Azure.Mcp.Tools.Search/src/Services/ISearchService.cs index e43397d9ba..c26ad84b31 100644 --- a/tools/Azure.Mcp.Tools.Search/src/Services/ISearchService.cs +++ b/tools/Azure.Mcp.Tools.Search/src/Services/ISearchService.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Text.Json; using Azure.Mcp.Tools.Search.Models; using Microsoft.Mcp.Core.Options; diff --git a/tools/Azure.Mcp.Tools.Search/src/Services/SearchService.cs b/tools/Azure.Mcp.Tools.Search/src/Services/SearchService.cs index f2c1b9242b..f3b330f670 100644 --- a/tools/Azure.Mcp.Tools.Search/src/Services/SearchService.cs +++ b/tools/Azure.Mcp.Tools.Search/src/Services/SearchService.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Text; +using System.Text.Json; using System.Text.RegularExpressions; using Azure.Core.Pipeline; using Azure.Mcp.Core.Services.Azure; @@ -16,7 +17,6 @@ using Azure.Search.Documents.KnowledgeBases; using Azure.Search.Documents.KnowledgeBases.Models; using Azure.Search.Documents.Models; -using Microsoft.Extensions.Logging; using Microsoft.Mcp.Core.Options; using Microsoft.Mcp.Core.Services.Azure.Authentication; using Microsoft.Mcp.Core.Services.Caching; @@ -26,14 +26,12 @@ namespace Azure.Mcp.Tools.Search.Services; public sealed partial class SearchService( ISubscriptionService subscriptionService, ICacheService cacheService, - ITenantService tenantService, - ILogger logger) + ITenantService tenantService) : BaseAzureService(tenantService), ISearchService { private readonly ITenantService _tenantService = tenantService ?? throw new ArgumentNullException(nameof(tenantService)); private readonly ISubscriptionService _subscriptionService = subscriptionService ?? throw new ArgumentNullException(nameof(subscriptionService)); private readonly ICacheService _cacheService = cacheService ?? throw new ArgumentNullException(nameof(cacheService)); - private readonly ILogger _logger = logger ?? throw new ArgumentNullException(nameof(logger)); private const string CacheGroup = "search"; private const string SearchServicesCacheKey = "services"; private static readonly TimeSpan s_cacheDurationServices = CacheDurations.ServiceData; diff --git a/tools/Azure.Mcp.Tools.Search/tests/Azure.Mcp.Tools.Search.Tests/Service/SearchServiceTests.cs b/tools/Azure.Mcp.Tools.Search/tests/Azure.Mcp.Tools.Search.Tests/Service/SearchServiceTests.cs index 5d059c2304..b651c14a9c 100644 --- a/tools/Azure.Mcp.Tools.Search/tests/Azure.Mcp.Tools.Search.Tests/Service/SearchServiceTests.cs +++ b/tools/Azure.Mcp.Tools.Search/tests/Azure.Mcp.Tools.Search.Tests/Service/SearchServiceTests.cs @@ -7,7 +7,6 @@ using Azure.Mcp.Tools.Search.Services; using Azure.ResourceManager; using Azure.Search.Documents.KnowledgeBases.Models; -using Microsoft.Extensions.Logging; using Microsoft.Mcp.Core.Options; using Microsoft.Mcp.Core.Services.Azure.Authentication; using Microsoft.Mcp.Core.Services.Caching; @@ -35,11 +34,7 @@ public SearchServiceCacheTests() cloudConfig.ArmEnvironment.Returns(ArmEnvironment.AzurePublicCloud); _tenantService.CloudConfiguration.Returns(cloudConfig); - _service = new SearchService( - _subscriptionService, - _cacheService, - _tenantService, - Substitute.For>()); + _service = new SearchService(_subscriptionService, _cacheService, _tenantService); } [Fact] diff --git a/tools/Azure.Mcp.Tools.Search/tests/Azure.Mcp.Tools.Search.Tests/Service/ServiceListCommandTests.cs b/tools/Azure.Mcp.Tools.Search/tests/Azure.Mcp.Tools.Search.Tests/Service/ServiceListCommandTests.cs index 10a523ec75..94ee5834ce 100644 --- a/tools/Azure.Mcp.Tools.Search/tests/Azure.Mcp.Tools.Search.Tests/Service/ServiceListCommandTests.cs +++ b/tools/Azure.Mcp.Tools.Search/tests/Azure.Mcp.Tools.Search.Tests/Service/ServiceListCommandTests.cs @@ -2,18 +2,18 @@ // Licensed under the MIT License. using System.Net; +using Azure.Mcp.Tests.Commands; using Azure.Mcp.Tools.Search.Commands; using Azure.Mcp.Tools.Search.Commands.Service; using Azure.Mcp.Tools.Search.Services; using Microsoft.Mcp.Core.Options; -using Microsoft.Mcp.Tests.Client; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; namespace Azure.Mcp.Tools.Search.Tests.Service; -public class ServiceListCommandTests : CommandUnitTestsBase +public class ServiceListCommandTests : SubscriptionCommandUnitTestsBase { [Fact] public async Task ExecuteAsync_ReturnsServices_WhenServicesExist()