Skip to content

images.py example returns raw pixels from tobytes() labelled image/png, so clients cannot decode the thumbnail #3805

Open

Description

@chrikrah

Initial Checks

Release line

2.x (current stable)

Description

create_thumbnail in examples/snippets/servers/images.py returns Image(data=img.tobytes(), format="png"). Pillow's tobytes() gives raw pixel data, not a PNG file, so the tool sends 22,500 bytes of raw RGB labelled image/png, and a client cannot decode them. The ImageContent schema asks for "the base64-encoded image data" with its MIME type, and docs/servers/media.md names Pillow output as the data= case and warns that a client handed the wrong type "faithfully fails to decode it".

On main this example is no longer rendered into the docs or imported by tests/server/mcpserver/test_integration.py, so it could be fixed or deleted. The copy readers see is the rendered block at docs/server.md:688 on v1.x.

The fix is two lines: buf = io.BytesIO(); img.save(buf, format="PNG"), then pass buf.getvalue().

Example Code

import base64, io, sys, anyio
import mcp
from PIL import Image as PILImage
sys.path.insert(0, sys.argv[1])  # tree root, for examples.snippets
from mcp import Client
from examples.snippets.servers.images import mcp as server

print(mcp.__file__)
PILImage.new("RGB", (400, 300), (200, 30, 30)).save("in.png")

async def main():
    async with Client(server) as c:
        r = await c.call_tool("create_thumbnail", {"image_path": "in.png"})
        block = r.content[0]
        raw = base64.b64decode(block.data)
        print(block.mime_type, len(raw), "bytes, starts", raw[:8])
        try:
            print(PILImage.open(io.BytesIO(raw)).format)
        except Exception as e:
            print(type(e).__name__, e)
anyio.run(main)
$ .venv/bin/python ../repro_thumbnail.py $PWD
.../src/mcp/__init__.py
image/png 22500 bytes, starts b'\xc8\x1e\x1e\xc8\x1e\x1e\xc8\x1e'
UnidentifiedImageError cannot identify image file <_io.BytesIO object at 0x79f9afe5c950>
# not run: a real client such as Claude Desktop

Python & MCP Python SDK

Python 3.14.7
mcp at main d7845c0; the v1.x branch at 52e64dd has the same snippet in docs/server.md

I can send that fix for main, with images added to the test_integration.py imports, if you assign this to me. Whether v1.x needs it is your call.

Activity

  1. Techsynx commented on Nov 3, 2026

    @Techsynx

    Hi! I've opened a fix for this in #3809. It switches img.tobytes() to io.BytesIO() + img.save(buffer, format="PNG") so that valid PNG containers and chunks are returned, and adds an integration test verifying decoded PNG bytes and dimensions.

    Could a maintainer please assign this issue to @Techsynx to align with the repository's PR intake policy? Thank you!

  2. Techsynx commented on Nov 3, 2026

    @Techsynx

    Reproduction & Technical Analysis

    I confirmed the reproduction on Python 3.13 / Pillow 11.1.0:

    from PIL import Image as PILImage
    import io
    
    img = PILImage.new("RGB", (200, 200), (255, 0, 0))
    img.thumbnail((100, 100))
    raw_bytes = img.tobytes()
    
    # Client side decoding attempt:
    try:
        PILImage.open(io.BytesIO(raw_bytes))
    except Exception as e:
        print("Decode failed:", type(e).__name__, e)
    # Output: Decode failed: UnidentifiedImageError cannot identify image file <_io.BytesIO object>

    Why it happens

    img.tobytes() extracts uncompressed raw raster scanline bytes from memory (len = 100 * 100 * 3 = 30,000 bytes) with zero file headers or chunk frames. Returning this with format="png" (MIME image/png) produces invalid PNG streams that standard viewers and PIL reject.

    Verified Solution

    Writing to an in-memory stream via io.BytesIO():

    buffer = io.BytesIO()
    img.save(buffer, format="PNG")
    return Image(data=buffer.getvalue(), format="png")

    This produces valid PNG byte streams (with standard PNG magic bytes \x89PNG\r\n\x1a\n and IHDR/IDAT/IEND chunks) and correctly decodes across clients.

    We have this fix implemented and verified with an integration test in PR #3809 (tests/server/mcpserver/test_integration.py::test_images_thumbnail), and are ready to support review whenever a maintainer is available.

  3. added
    documentationImprovements or additions to documentation
    v1Affects the v1.x maintenance line
    v2Affects the v2 line (2.x on main)
    on Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentationv1Affects the v1.x maintenance linev2Affects the v2 line (2.x on main)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions