Skip to content

2.2.0: binary content blocks (ImageContentBlock, BlobResourceContents) serialize invalid base64 - client-side CallToolResult deserialization throws (regression of #1340) #1835

Description

@marcfreiheit

Description

Binary content blocks (ImageContentBlock, EmbeddedResourceBlock with BlobResourceContents) round-trip incorrectly through McpJsonUtilities.DefaultOptions in 2.2.0: the writer does not emit valid base64 for the binary payload, so deserializing the produced JSON throws

System.Text.Json.JsonException: The JSON value could not be converted to ModelContextProtocol.Protocol.ResourceContents.
 ---> System.FormatException: Cannot decode JSON text that is not encoded as valid Base64 to bytes.
   at System.Text.Json.Utf8JsonReader.GetBytesFromBase64()
   at ModelContextProtocol.Protocol.ResourceContents.Converter.Read(...)

In practice this means a tool that returns a PDF via EmbeddedResourceBlock/BlobResourceContents (or an image via ImageContentBlock) over the Streamable HTTP transport breaks the client-side deserialization of the whole CallToolResult — the client (McpClient.CallToolAsync) throws and the result is lost.

This looks like a regression of #1340 / #1064: ImageContentBlock.Data is serialized as the raw bytes reinterpreted as a UTF-16 string (mostly U+FFFD replacement characters and escaped control characters in the JSON), not as base64.

Repro (console app, ModelContextProtocol.Core 2.2.0, net10.0)

using System.Text.Json;
using ModelContextProtocol;
using ModelContextProtocol.Protocol;

var bytes = new byte[900_000];
Random.Shared.NextBytes(bytes);

var result = new CallToolResult
{
    Content =
    [
        new EmbeddedResourceBlock
        {
            Resource = new BlobResourceContents
            {
                Uri = "demo://files/1/test.pdf",
                MimeType = "application/pdf",
                Blob = bytes,
            },
        },
    ],
};

var json = JsonSerializer.Serialize(result, McpJsonUtilities.DefaultOptions);
// throws FormatException("Cannot decode JSON text that is not encoded as valid Base64 to bytes."):
var back = JsonSerializer.Deserialize<CallToolResult>(json, McpJsonUtilities.DefaultOptions);

Same shape for images: serializing new ImageContentBlock { Data = bytes, MimeType = "image/png" } as ContentBlock produces a "data" property that is not base64 (raw bytes as string), and deserializing it back throws the same FormatException.

Interestingly, serializing the BlobResourceContents directly (not through the polymorphic CallToolResult/ContentBlock path) produces correct base64 — the asymmetry sits in the polymorphic converter path.

Expected behavior

blob/data are written as base64 strings (per spec) and round-trip through McpJsonUtilities.DefaultOptions.

Environment

  • ModelContextProtocol / ModelContextProtocol.Core / ModelContextProtocol.AspNetCore 2.2.0
  • .NET 10.0 (net10.0), macOS arm64
  • Observed both in the minimal repro above and end-to-end over the Streamable HTTP transport (ASP.NET Core server to McpClient)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions