fix: allow MIME type parameters in resource validation (RFC 2045) (#1755)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
b7cc25493c
commit
8ac11ec604
2 changed files with 71 additions and 1 deletions
|
|
@ -28,7 +28,7 @@ class Resource(BaseModel, abc.ABC):
|
||||||
mime_type: str = Field(
|
mime_type: str = Field(
|
||||||
default="text/plain",
|
default="text/plain",
|
||||||
description="MIME type of the resource content",
|
description="MIME type of the resource content",
|
||||||
pattern=r"^[a-zA-Z0-9]+/[a-zA-Z0-9\-+.]+$",
|
pattern=r"^[a-zA-Z0-9]+/[a-zA-Z0-9\-+.]+(;\s*[a-zA-Z0-9\-_.]+=[a-zA-Z0-9\-_.]+)*$",
|
||||||
)
|
)
|
||||||
icons: list[Icon] | None = Field(default=None, description="Optional list of icons for this resource")
|
icons: list[Icon] | None = Field(default=None, description="Optional list of icons for this resource")
|
||||||
annotations: Annotations | None = Field(default=None, description="Optional annotations for the resource")
|
annotations: Annotations | None = Field(default=None, description="Optional annotations for the resource")
|
||||||
|
|
|
||||||
70
tests/issues/test_1754_mime_type_parameters.py
Normal file
70
tests/issues/test_1754_mime_type_parameters.py
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
"""Test for GitHub issue #1754: MIME type validation rejects valid RFC 2045 parameters.
|
||||||
|
|
||||||
|
The MIME type validation regex was too restrictive and rejected valid MIME types
|
||||||
|
with parameters like 'text/html;profile=mcp-app' which are valid per RFC 2045.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from pydantic import AnyUrl
|
||||||
|
|
||||||
|
from mcp.server.fastmcp import FastMCP
|
||||||
|
from mcp.shared.memory import (
|
||||||
|
create_connected_server_and_client_session as client_session,
|
||||||
|
)
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.anyio
|
||||||
|
|
||||||
|
|
||||||
|
async def test_mime_type_with_parameters():
|
||||||
|
"""Test that MIME types with parameters are accepted (RFC 2045)."""
|
||||||
|
mcp = FastMCP("test")
|
||||||
|
|
||||||
|
# This should NOT raise a validation error
|
||||||
|
@mcp.resource("ui://widget", mime_type="text/html;profile=mcp-app")
|
||||||
|
def widget() -> str:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
resources = await mcp.list_resources()
|
||||||
|
assert len(resources) == 1
|
||||||
|
assert resources[0].mimeType == "text/html;profile=mcp-app"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_mime_type_with_parameters_and_space():
|
||||||
|
"""Test MIME type with space after semicolon."""
|
||||||
|
mcp = FastMCP("test")
|
||||||
|
|
||||||
|
@mcp.resource("data://json", mime_type="application/json; charset=utf-8")
|
||||||
|
def data() -> str:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
resources = await mcp.list_resources()
|
||||||
|
assert len(resources) == 1
|
||||||
|
assert resources[0].mimeType == "application/json; charset=utf-8"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_mime_type_with_multiple_parameters():
|
||||||
|
"""Test MIME type with multiple parameters."""
|
||||||
|
mcp = FastMCP("test")
|
||||||
|
|
||||||
|
@mcp.resource("data://multi", mime_type="text/plain; charset=utf-8; format=fixed")
|
||||||
|
def data() -> str:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
resources = await mcp.list_resources()
|
||||||
|
assert len(resources) == 1
|
||||||
|
assert resources[0].mimeType == "text/plain; charset=utf-8; format=fixed"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_mime_type_preserved_in_read_resource():
|
||||||
|
"""Test that MIME type with parameters is preserved when reading resource."""
|
||||||
|
mcp = FastMCP("test")
|
||||||
|
|
||||||
|
@mcp.resource("ui://my-widget", mime_type="text/html;profile=mcp-app")
|
||||||
|
def my_widget() -> str:
|
||||||
|
return "<html><body>Hello MCP-UI</body></html>"
|
||||||
|
|
||||||
|
async with client_session(mcp._mcp_server) as client:
|
||||||
|
# Read the resource
|
||||||
|
result = await client.read_resource(AnyUrl("ui://my-widget"))
|
||||||
|
assert len(result.contents) == 1
|
||||||
|
assert result.contents[0].mimeType == "text/html;profile=mcp-app"
|
||||||
Loading…
Reference in a new issue