diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ToolLoading/NamespaceToolLoaderTests.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ToolLoading/NamespaceToolLoaderTests.cs index 36d7e14bef..9fd5d29c1a 100644 --- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ToolLoading/NamespaceToolLoaderTests.cs +++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ToolLoading/NamespaceToolLoaderTests.cs @@ -419,6 +419,39 @@ public async Task CallToolHandler_ThreadSafeLazyLoading() }); } + [Fact] + public async Task CallToolHandler_PreservesSpecificErrorMessageForMissingParameters() + { + // Arrange + var loader = new NamespaceToolLoader(_commandFactory, _options, _serviceProvider, _logger); + // Use subscription list command which requires --subscription or AZURE_SUBSCRIPTION_ID + var toolName = "subscription"; + + // Create request without required subscription parameter + var request = CreateCallToolRequest(toolName, new Dictionary + { + ["command"] = "list", + ["parameters"] = new Dictionary() + }); + + // Act + var result = await loader.CallToolHandler(request, TestContext.Current.CancellationToken); + + // Assert + Assert.NotNull(result); + Assert.True(result.IsError); + + var textContent = result.Content[0] as TextContentBlock; + Assert.NotNull(textContent); + + // Verify the specific error message is preserved (not replaced with generic message) + Assert.Contains("Missing Required options:", textContent.Text); + + // Verify the command spec guidance is still included + Assert.Contains("Review the following command spec", textContent.Text); + Assert.Contains("Command Spec:", textContent.Text); + } + [Fact] public async Task DisposeAsync_ClearsCaches() { @@ -556,6 +589,35 @@ await Assert.ThrowsAsync(async () => await options.Handlers.ElicitationHandler.Invoke(null!, TestContext.Current.CancellationToken)); } + [Fact] + public async Task CallToolHandler_WithMissingRequiredOptions_PreservesSpecificErrorMessage() + { + // Arrange + // Service Bus commands require --subscription parameter + // When calling without it, we should get a specific error message, not a generic one + const string command = "servicebus_namespace_list"; + var args = new Dictionary(); // Missing required --subscription parameter + + var request = CreateCallToolRequest(command, args); + + // Act + var response = await _loader.CallToolHandler(request, TestContext.Current.CancellationToken); + + // Assert + Assert.NotNull(response); + Assert.True(response.IsError); + Assert.Single(response.Content); + + var textContent = response.Content[0] as TextContent; + Assert.NotNull(textContent); + + // Verify the specific error message is preserved (not the generic "missing required parameters") + Assert.Contains("Missing Required options: --subscription", textContent.Text); + + // Verify it still includes the command spec guidance + Assert.Contains("Review the following command spec", textContent.Text); + } + // Helper methods private string GetFirstAvailableNamespace() diff --git a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ToolLoading/ServerToolLoaderTests.cs b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ToolLoading/ServerToolLoaderTests.cs index 4aa1ac52ff..05e43af515 100644 --- a/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ToolLoading/ServerToolLoaderTests.cs +++ b/core/Azure.Mcp.Core/tests/Azure.Mcp.Core.UnitTests/Areas/Server/Commands/ToolLoading/ServerToolLoaderTests.cs @@ -136,4 +136,47 @@ public async Task ListToolsHandler_WithRealRegistryDiscovery_ReturnsExpectedStru Assert.True(tool.InputSchema.ValueKind != JsonValueKind.Undefined, "InputSchema should be defined"); } } + + [Fact] + public async Task CallToolHandler_PreservesSpecificErrorMessageForMissingParameters() + { + // Arrange - use real RegistryDiscoveryStrategy + var serviceProvider = new ServiceCollection().AddLogging().BuildServiceProvider(); + var loggerFactory = serviceProvider.GetRequiredService(); + var serviceStartOptions = Microsoft.Extensions.Options.Options.Create(new ServiceStartOptions()); + var toolLoaderOptions = Microsoft.Extensions.Options.Options.Create(new ToolLoaderOptions()); + var discoveryLogger = loggerFactory.CreateLogger(); + var discoveryStrategy = RegistryDiscoveryStrategyHelper.CreateStrategy(serviceStartOptions.Value, discoveryLogger); + var logger = loggerFactory.CreateLogger(); + + var toolLoader = new ServerToolLoader(discoveryStrategy, toolLoaderOptions, logger); + + // Create request for documentation tool with missing required parameters + var request = CreateCallToolRequest("documentation", + new Dictionary + { + { "command", JsonDocument.Parse("\"microsoft_docs_search\"").RootElement }, + // Missing 'question' parameter which is required + { "parameters", JsonDocument.Parse("{}").RootElement } + }); + + // Act + var result = await toolLoader.CallToolHandler(request, TestContext.Current.CancellationToken); + + // Assert + Assert.NotNull(result); + Assert.NotNull(result.Content); + Assert.NotEmpty(result.Content); + + var textContent = result.Content[0] as TextContentBlock; + Assert.NotNull(textContent); + + // Verify the specific error message is preserved (not replaced with generic message) + // Should contain "Missing required options:" or similar specific error, not generic "missing required parameters" + Assert.Contains("Missing", textContent.Text); + + // Verify the command spec guidance is still included + Assert.Contains("Review the following command spec", textContent.Text); + Assert.Contains("Command Spec:", textContent.Text); + } } diff --git a/core/Microsoft.Mcp.Core/src/Areas/Server/Commands/ToolLoading/NamespaceToolLoader.cs b/core/Microsoft.Mcp.Core/src/Areas/Server/Commands/ToolLoading/NamespaceToolLoader.cs index 75ca5290f8..130cc2b990 100644 --- a/core/Microsoft.Mcp.Core/src/Areas/Server/Commands/ToolLoading/NamespaceToolLoader.cs +++ b/core/Microsoft.Mcp.Core/src/Areas/Server/Commands/ToolLoading/NamespaceToolLoader.cs @@ -380,13 +380,19 @@ private async Task InvokeChildToolAsync( var childToolSpecJson = GetChildToolJson(request, namespaceName, command); _logger.LogWarning("Namespace {Namespace} command {Command} requires additional parameters.", namespaceName, command); + + // Extract the specific error message from the response + var errorMessage = string.IsNullOrEmpty(commandResponse.Message) + ? $"The '{command}' command is missing required parameters." + : commandResponse.Message; + var finalResponse = new CallToolResult { Content = [ new TextContentBlock { Text = $""" - The '{command}' command is missing required parameters. + {errorMessage} - Review the following command spec and identify the required arguments from the input schema. - Omit any arguments that are not required or do not apply to your use case. diff --git a/core/Microsoft.Mcp.Core/src/Areas/Server/Commands/ToolLoading/ServerToolLoader.cs b/core/Microsoft.Mcp.Core/src/Areas/Server/Commands/ToolLoading/ServerToolLoader.cs index 01d8b01c2c..1c8a90aa56 100644 --- a/core/Microsoft.Mcp.Core/src/Areas/Server/Commands/ToolLoading/ServerToolLoader.cs +++ b/core/Microsoft.Mcp.Core/src/Areas/Server/Commands/ToolLoading/ServerToolLoader.cs @@ -283,7 +283,7 @@ private async Task InvokeChildToolAsync(RequestContext