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 13474d7be5..c133b38bbd 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/DefaultMcpToolHandler.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.Mcp/DefaultMcpToolHandler.cs @@ -28,6 +28,8 @@ namespace Microsoft.Agents.AI.Workflows.Declarative.Mcp; /// public sealed class DefaultMcpToolHandler : IMcpToolHandler, IAsyncDisposable { + private const string FilenameAdditionalPropertyName = "filename"; + /// /// Reserved toolName value that maps an request /// to the MCP protocol tools/list discovery operation. @@ -280,11 +282,41 @@ public sealed class DefaultMcpToolHandler : IMcpToolHandler, IAsyncDisposable // UriContent here so callers always receive a usable AIContent. return block.ToAIContent() ?? block switch { - ResourceLinkBlock link => new UriContent(link.Uri, link.MimeType ?? "application/octet-stream") { RawRepresentation = link }, - _ => new TextContent(block.ToString() ?? string.Empty) { RawRepresentation = block }, + ResourceLinkBlock link => new UriContent(link.Uri, link.MimeType ?? "application/octet-stream") + { + RawRepresentation = link, + AdditionalProperties = CreateAdditionalProperties(link), + }, + _ => new TextContent(block.ToString() ?? string.Empty) + { + RawRepresentation = block, + AdditionalProperties = CreateAdditionalProperties(block), + }, }; } + private static AdditionalPropertiesDictionary? CreateAdditionalProperties(ContentBlock block) + { + AdditionalPropertiesDictionary? properties = null; + + if (block.Meta is not null) + { + foreach (var property in block.Meta) + { + properties ??= new AdditionalPropertiesDictionary(); + properties.Add(property.Key, property.Value); + } + } + + if (block is ResourceLinkBlock { Name: { Length: > 0 } name }) + { + properties ??= new AdditionalPropertiesDictionary(); + properties.TryAdd(FilenameAdditionalPropertyName, name); + } + + return properties; + } + private static string SerializeToolsList(IEnumerable tools) { using MemoryStream stream = new(); 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 2e6f9d9ea1..1327c3df48 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 @@ -616,6 +616,53 @@ public sealed class DefaultMcpToolHandlerTests uriContent.MediaType.Should().Be("application/octet-stream"); } + [Fact] + public void ConvertContentBlock_ResourceLinkBlock_WithMeta_ShouldPropagateToAdditionalProperties() + { + // Arrange + ResourceLinkBlock block = new() + { + Uri = "https://example.com/resource.bin", + Name = string.Empty, + MimeType = "application/zip", + Meta = new System.Text.Json.Nodes.JsonObject + { + ["traceId"] = "abc-123", + ["priority"] = 7, + }, + }; + + // Act + AIContent result = DefaultMcpToolHandler.ConvertContentBlock(block); + + // Assert + UriContent uriContent = result.Should().BeOfType().Subject; + uriContent.AdditionalProperties.Should().NotBeNull(); + uriContent.AdditionalProperties!.Should().HaveCount(2); + uriContent.AdditionalProperties["traceId"].Should().BeSameAs(block.Meta!["traceId"]); + uriContent.AdditionalProperties["priority"].Should().BeSameAs(block.Meta["priority"]); + } + + [Fact] + public void ConvertContentBlock_ResourceLinkBlock_WithName_ShouldMapNameToFilenameAdditionalProperty() + { + // Arrange + ResourceLinkBlock block = new() + { + Uri = "https://example.com/resource.bin", + Name = "resource.bin", + MimeType = "application/zip", + }; + + // Act + AIContent result = DefaultMcpToolHandler.ConvertContentBlock(block); + + // Assert + UriContent uriContent = result.Should().BeOfType().Subject; + uriContent.AdditionalProperties.Should().NotBeNull(); + uriContent.AdditionalProperties!["filename"].Should().Be("resource.bin"); + } + [Fact] public void ConvertContentBlock_ToolUseContentBlock_ShouldReturnFunctionCallContent() {