Skip to content

Fix case-insensitive handling of text MIME types for FileResource #3131

Description

@dchou1618

Initial Checks

Description

Summary

On current main (v2, tested at 3a6f299), FileResource treats MIME types as text only when the value starts with text/ using a case-sensitive comparison. As a result, mixed-case or uppercase text MIME types such as Text/Markdown or TEXT/plain are incorrectly classified as binary.

This is inconsistent with RFC 9110 §8.3.1, which states that media types are case-insensitive. In other words, application/json, Application/JSON, and APPLICATION/JSON are equivalent, and the same principle applies to text/* media types.

Root cause

The current logic checks whether the MIME type starts with text/ directly, without normalizing the value first. Because the comparison is case-sensitive, uppercase or mixed-case text media types do not match and are misclassified as binary.

Why it matters

This can cause text resources to be read and returned as bytes instead of text when the caller provides a non-lowercase text/* MIME type. That can break downstream consumers that expect text content to be handled as text.

Proposed fix

Normalize the MIME type to lowercase before checking whether it begins with text/, so text media types are detected case-insensitively.

This fix is already covered by this PR:

Example

from mcp.server.mcpserver.resources.types import FileResource

resource = FileResource(
    uri="file:///tmp/example.md",
    name="example",
    path="/tmp/example.md",
    mime_type="Text/Markdown",
)

assert resource.is_binary is False

Example Code

class DummyRequest:
    def __init__(self, headers):
        self.headers = headers


def _check_content_type(request):
    content_type = request.headers.get("content-type", "")

    media_types = [
        part.strip().lower()
        for part in content_type.split(";", 1)[0].split(",")
    ]

    return "application/json" in media_types


def test(ct):
    req = DummyRequest({"content-type": ct})
    result = _check_content_type(req)
    print(f'"{ct}" -> {result}')


if __name__ == '__main__':
    cases = [
        'application/json',
        'Application/JSON',
        'application/json; charset=utf-8',
        'APPLICATION/JSON; CHARSET=UTF-8',
        'text/plain',
    ]
    for c in cases:
        test(c)

Result:

"application/json" -> True
"Application/JSON" -> True
"application/json; charset=utf-8" -> True
"APPLICATION/JSON; CHARSET=UTF-8" -> True
"text/plain" -> False

Python & MCP Python SDK

Python 3.12.13
MCP Python SDK 1.28.1

Activity

  1. maxisbey commented on Aug 14, 2026

    @maxisbey
    Contributor

    The FileResource side of this was fixed by #3171, which replaced is_binary with an encoding field and lowercases the media type before the text/* check, so Text/Markdown and friends are read as text now. That shipped in v2.0.0. The Content-Type header comparison in the streamable HTTP server that your example snippet touches is a separate spot and is still being handled over in #2918, so I'll close this one as resolved for the resource case.

    Feel free to reopen if you still see a mixed-case text/* type coming back as a blob on 2.x.

    AI Disclaimer

  2. dchou1618 commented on Aug 14, 2026

    @dchou1618
    Author

    I can't seem to reopen.

    Running with v2.0.0 gives the mixed-case type. I believe this is tied to this line that isn't standardized .lower()

    def _check_content_type(self, request: Request) -> bool:
    """Check if the request has the correct Content-Type."""
    content_type = request.headers.get("content-type", "")
    content_type_parts = [part.strip() for part in content_type.split(";")[0].split(",")]

    but is addressed here.

  3. added
    v2Affects the v2 line (2.x on main)
    v1Affects the v1.x maintenance line
    on Sep 30, 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

    v1Affects 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