diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/DefaultMcpToolHandler.cs b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/DefaultMcpToolHandler.cs index 61aedec347f..9bd67e55eb4 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/DefaultMcpToolHandler.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/DefaultMcpToolHandler.cs @@ -104,6 +104,21 @@ public async Task InvokeToolAsync( ThrowIfListToolsArgumentsSpecified(arguments); } + if (headers is not null) + { + foreach (KeyValuePair header in headers) + { + HttpHeaderValidation.ValidateNoProhibitedCharacters( + header.Key, + nameof(headers), + "HTTP header name contains invalid characters."); + HttpHeaderValidation.ValidateNoProhibitedCharacters( + header.Value, + nameof(headers), + "HTTP header value contains invalid characters."); + } + } + if (this._httpClientProvider is not null) { TaskCompletionSource invocationCompleted = new(TaskCreationOptions.RunContinuationsAsynchronously); diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/Microsoft.Agents.AI.Workflows.Declarative.Mcp.csproj b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/Microsoft.Agents.AI.Workflows.Declarative.Mcp.csproj index 08738266c09..0bb04cc608e 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/Microsoft.Agents.AI.Workflows.Declarative.Mcp.csproj +++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/Microsoft.Agents.AI.Workflows.Declarative.Mcp.csproj @@ -7,6 +7,7 @@ true + true true true true diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/DefaultMcpToolHandlerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/DefaultMcpToolHandlerTests.cs index 610f7393eda..aa1fb0440aa 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/DefaultMcpToolHandlerTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.Mcp.UnitTests/DefaultMcpToolHandlerTests.cs @@ -90,6 +90,59 @@ public async Task DisposeAsync_WhenCalledMultipleTimes_ShouldHandleGracefullyAsy #region HttpClientProvider Tests + [Theory] + [InlineData("\r", false, false)] + [InlineData("\n", false, false)] + [InlineData("\0", false, false)] + [InlineData("\r", true, false)] + [InlineData("\n", true, false)] + [InlineData("\0", true, false)] + [InlineData("\r", false, true)] + [InlineData("\n", false, true)] + [InlineData("\0", false, true)] + [InlineData("\r", true, true)] + [InlineData("\n", true, true)] + [InlineData("\0", true, true)] + public async Task InvokeToolAsync_WithInvalidHeader_ShouldRejectBeforeClientCreationAsync( + string invalidCharacter, + bool invalidName, + bool useProvider) + { + // Arrange + bool clientCreationAttempted = false; + Task ProviderAsync(string url, CancellationToken ct) + { + clientCreationAttempted = true; + throw new InvalidOperationException("Client creation should not be reached."); + } + + HttpMessageHandler CreateHandler() + { + clientCreationAttempted = true; + throw new InvalidOperationException("Client creation should not be reached."); + } + + await using DefaultMcpToolHandler handler = new(useProvider ? ProviderAsync : null, CreateHandler); + Dictionary headers = new() + { + [invalidName ? $"X-Test{invalidCharacter}" : "X-Test"] = invalidName ? "value" : $"value{invalidCharacter}" + }; + + // Act + async Task actAsync() => await handler.InvokeToolAsync( + serverUrl: "https://example.com/mcp", + serverLabel: null, + toolName: "testTool", + arguments: null, + headers: headers, + connectionName: null); + + // Assert + ArgumentException exception = await Assert.ThrowsAsync(actAsync); + Assert.Equal("headers", exception.ParamName); + Assert.False(clientCreationAttempted); + } + [Fact] public async Task InvokeToolAsync_WithHttpClientProvider_ShouldCallProviderAsync() {