Skip to content

Image/Audio format= emits image/jpg, audio/mp3, audio/m4a — unregistered MIME types that disagree with the path= suffix table #3585

Description

@JoeyTan21

Initial Checks

  • I confirm that I'm using the newest release of my release line (2.2.0)
  • I have searched GitHub issues and found no similar issue

Release Line

2.x (current stable). The same code exists on v1.x (src/mcp/server/fastmcp/utilities/types.py).

Description

Image and Audio compute the MIME type two different ways. With path= they use a suffix table (.jpg -> image/jpeg, .mp3 -> audio/mpeg, .m4a -> audio/mp4). With format=, which docs/servers/media.md says to pass whenever you use data=, they just do f"image/{format}" / f"audio/{format}". So the same name gives different results depending on which keyword it arrives through, and format= puts non-registered types on the wire:

  • Image(data=..., format="jpg") -> mimeType: "image/jpg" (should be image/jpeg)
  • Audio(data=..., format="mp3") -> audio/mp3 (should be audio/mpeg)
  • Audio(data=..., format="m4a") -> audio/m4a (should be audio/mp4)

This is easy to hit: switching a tool from Image(path="chart.jpg") to Image(data=buf.getvalue(), format="jpg") silently changes the wire output from image/jpeg to image/jpg, and a consumer that validates image MIME types rejects it.

Expected: format="jpg" and path="x.jpg" give the same registered type.

Proposed fix: route format= through the same table the path= branch already has, keeping image/<format> / audio/<format> as the fallback for names outside it (so format="bmp" etc. are unchanged). I have this fix with parametrized tests ready locally (100% branch coverage on types.py, ruff/pyright clean) and would like to open the PR if this is accepted.

Example Code

from pathlib import Path
from mcp.server.mcpserver import Audio, Image

print(Image(data=b"\x00", format="jpg").to_image_content().mime_type)   # image/jpg   (expected image/jpeg)
print(Audio(data=b"\x00", format="mp3").to_audio_content().mime_type)   # audio/mp3   (expected audio/mpeg)
print(Audio(data=b"\x00", format="m4a").to_audio_content().mime_type)   # audio/m4a   (expected audio/mp4)

# The same helpers get it right when the name arrives as a file suffix:
print(Image(path=Path("x.jpg"))._mime_type)   # image/jpeg

Python & MCP Python SDK

Python 3.12.13
mcp 2.2.0 (also reproduced on main @ f1b6589)

Disclosure: found, reproduced and the draft fix tested locally with the help of an AI coding agent (Claude Code); I reviewed the report before filing it.

Activity

  1. added
    v2Affects the v2 line (2.x on main)
    v1Affects the v1.x maintenance line
    on Sep 26, 2026
  2. MustafaKemal0146 commented on Sep 29, 2026

    @MustafaKemal0146

    Hi maintainers,

    I reproduced this locally: Image(data=..., format="jpg") and Audio(data=..., format="mp3") emit unregistered MIME types (image/jpg, audio/mp3), differing from the suffix mapping used by path=.

    I have a fix with tests ready that normalizes format= through the same mapping table as path= while preserving generic fallbacks. If this approach looks good, could you assign this issue to me? (Assigning will also automatically reopen PR #3593, which was closed by the bot).

    Disclosure: Prepared with AI assistance under human review.

  3. Jaswanth1902 commented on Oct 2, 2026

    @Jaswanth1902
  4. i20000109 commented on Oct 2, 2026

    @i20000109

    Confirmed on main. The asymmetry is exactly as described.

    src/mcp/server/mcpserver/utilities/types.py, Image._get_mime_type():

    if self._format:
        return f"image/{self._format.lower()}"
    
    if self.path:
        suffix = self.path.suffix.lower()
        return {".png": "image/png", ".jpg": "image/jpeg", ...}.get(suffix, "application/octet-stream")

    The format= branch is an unchecked interpolation — it never consults the suffix table two lines below it, so format="jpg" emits image/jpg while path="x.jpg" emits image/jpeg. Same for Audio (audio/mp3 vs audio/mpeg, audio/m4a vs audio/mp4).

    One thing worth flagging for whoever picks this up, since it affects the scope: the path in this report is the v1 location. On main (v2) the file has moved to src/mcp/server/mcpserver/utilities/types.py — fastmcp no longer exists. So the fix needs landing on both lines (the v1.x maintenance branch and v2), and a v2-only fix would leave v1 emitting the bad types.

    Suggested shape, if useful: a small module-level suffix→MIME map that both branches share, plus mimetypes.guess_type() as the fallback for formats outside the table, so format= and path= cannot disagree by construction rather than by convention. Happy to also add the regression tests (format="jpg" == path=".jpg") if this gets assigned.

    I don't want to open a PR blind — AGENTS.md closes PRs that aren't linked to an issue assigned to the author, so assigning this to me first is the efficient path.

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

    bugSomething isn't workingv1Affects 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