From 02582f5d57ea987414c1251cdfeb5fcc6538b90e Mon Sep 17 00:00:00 2001 From: Luis Rodriguez <25299418+luisangelrod@users.noreply.github.com> Date: Tue, 28 Jul 2026 08:33:55 -0400 Subject: [PATCH] Document embedded binary resource client handling --- docs/concepts/tools/tools.md | 2 + .../Protocol/CallToolResultTests.cs | 39 +++++++++ .../Server/McpServerTests.cs | 79 +++++++++++++++++++ 3 files changed, 120 insertions(+) diff --git a/docs/concepts/tools/tools.md b/docs/concepts/tools/tools.md index f943d5270..1e31bb252 100644 --- a/docs/concepts/tools/tools.md +++ b/docs/concepts/tools/tools.md @@ -121,6 +121,8 @@ public static EmbeddedResourceBlock GetBinaryData(string id) } ``` +The SDK sends binary embedded resources using the MCP resource shape: a URI, MIME type, and base64-encoded `blob`. It does not convert formats such as `application/pdf` into images. How an embedded resource is rendered or made available to a model depends on the client. When targeting clients that do not support a binary format, consider also returning a text representation or another client-supported content block. + #### Mixed content Tools can return multiple content blocks by returning `IEnumerable`: diff --git a/tests/ModelContextProtocol.Tests/Protocol/CallToolResultTests.cs b/tests/ModelContextProtocol.Tests/Protocol/CallToolResultTests.cs index b1ac90c9d..7dca60040 100644 --- a/tests/ModelContextProtocol.Tests/Protocol/CallToolResultTests.cs +++ b/tests/ModelContextProtocol.Tests/Protocol/CallToolResultTests.cs @@ -1,4 +1,5 @@ using ModelContextProtocol.Protocol; +using System.Text; using System.Text.Json; using System.Text.Json.Nodes; @@ -45,4 +46,42 @@ public static void CallToolResult_SerializationRoundTrip_WithMinimalProperties() Assert.Null(deserialized.IsError); Assert.Null(deserialized.Meta); } + + [Fact] + public static void CallToolResult_SerializationRoundTrip_PreservesEmbeddedPdfResource() + { + byte[] pdfBytes = Encoding.ASCII.GetBytes("%PDF-1.7\n"); + var original = new CallToolResult + { + Content = + [ + new EmbeddedResourceBlock + { + Resource = BlobResourceContents.FromBytes( + pdfBytes, + "file:///mypdf.pdf", + "application/pdf") + } + ] + }; + + string json = JsonSerializer.Serialize(original, McpJsonUtilities.DefaultOptions); + + using JsonDocument document = JsonDocument.Parse(json); + JsonElement resourceBlock = document.RootElement.GetProperty("content")[0]; + Assert.Equal("resource", resourceBlock.GetProperty("type").GetString()); + + JsonElement resource = resourceBlock.GetProperty("resource"); + Assert.Equal("file:///mypdf.pdf", resource.GetProperty("uri").GetString()); + Assert.Equal("application/pdf", resource.GetProperty("mimeType").GetString()); + Assert.Equal(Convert.ToBase64String(pdfBytes), resource.GetProperty("blob").GetString()); + + var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions); + Assert.NotNull(deserialized); + var embeddedResource = Assert.IsType(Assert.Single(deserialized.Content)); + var pdfResource = Assert.IsType(embeddedResource.Resource); + Assert.Equal("file:///mypdf.pdf", pdfResource.Uri); + Assert.Equal("application/pdf", pdfResource.MimeType); + Assert.Equal(pdfBytes, pdfResource.DecodedData.ToArray()); + } } diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs index d1f8443e7..e54f40dcb 100644 --- a/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs @@ -4,6 +4,7 @@ using ModelContextProtocol.Tests.Utils; using System.Reflection; using System.Runtime.InteropServices; +using System.Text; using System.Text.Json; using System.Text.Json.Nodes; @@ -977,6 +978,84 @@ await Can_Handle_Requests( }); } + [Fact] + public async Task Can_Handle_Call_Tool_Requests_With_Embedded_Pdf_Resource_On_Wire() + { + byte[] pdfBytes = Encoding.ASCII.GetBytes("%PDF-1.7\n"); + await using var transport = new TestServerTransport(); + var options = CreateOptions(new ServerCapabilities { Tools = new() }); + options.Handlers.CallToolHandler = async (request, ct) => + { + return new CallToolResult + { + Content = + [ + new EmbeddedResourceBlock + { + Resource = BlobResourceContents.FromBytes( + pdfBytes, + "file:///mypdf.pdf", + "application/pdf") + } + ] + }; + }; + options.Handlers.ListToolsHandler = (request, ct) => throw new NotImplementedException(); + + await using var server = McpServer.Create(transport, options, LoggerFactory); + var runTask = server.RunAsync(TestContext.Current.CancellationToken); + var receivedMessage = new TaskCompletionSource(); + + transport.OnMessageSent = message => + { + if (message is JsonRpcResponse response && response.Id.ToString() == "55") + { + receivedMessage.SetResult(response); + } + }; + + await transport.SendMessageAsync( + new JsonRpcRequest + { + Method = RequestMethods.ToolsCall, + Id = new RequestId(55) + }, + TestContext.Current.CancellationToken); + + var response = await receivedMessage.Task.WaitAsync( + TestConstants.DefaultTimeout, + TestContext.Current.CancellationToken); + string wireJson = JsonSerializer.Serialize( + response, + McpJsonUtilities.DefaultOptions); + + using JsonDocument document = JsonDocument.Parse(wireJson); + JsonElement root = document.RootElement; + Assert.Equal("2.0", root.GetProperty("jsonrpc").GetString()); + Assert.Equal(55, root.GetProperty("id").GetInt32()); + + JsonElement resourceBlock = root.GetProperty("result").GetProperty("content")[0]; + Assert.Equal("resource", resourceBlock.GetProperty("type").GetString()); + JsonElement resource = resourceBlock.GetProperty("resource"); + Assert.Equal("file:///mypdf.pdf", resource.GetProperty("uri").GetString()); + Assert.Equal("application/pdf", resource.GetProperty("mimeType").GetString()); + Assert.Equal(Convert.ToBase64String(pdfBytes), resource.GetProperty("blob").GetString()); + + var roundTrippedMessage = JsonSerializer.Deserialize( + wireJson, + McpJsonUtilities.DefaultOptions); + var roundTrippedResponse = Assert.IsType(roundTrippedMessage); + var result = roundTrippedResponse.Result.Deserialize( + McpJsonUtilities.DefaultOptions); + Assert.NotNull(result); + var embeddedResource = Assert.IsType(Assert.Single(result.Content)); + var pdfResource = Assert.IsType(embeddedResource.Resource); + Assert.Equal(pdfBytes, pdfResource.DecodedData.ToArray()); + + await transport.DisposeAsync(); + await runTask; + } + [Fact] public async Task Can_Handle_Call_Tool_Requests_Throws_Exception_If_No_Handler_Assigned() {