diff --git a/src/fastmcp/utilities/types.py b/src/fastmcp/utilities/types.py index d78d943dd..2397f73bf 100644 --- a/src/fastmcp/utilities/types.py +++ b/src/fastmcp/utilities/types.py @@ -234,7 +234,11 @@ class File: def _get_mime_type(self) -> str: """Get MIME type from format or guess from file extension.""" if self._format: - return f"application/{self._format.lower()}" + fmt = self._format.lower() + # Map common text formats to text/plain + if fmt in {"plain", "txt", "text"}: + return "text/plain" + return f"application/{fmt}" if self.path: mime_type, _ = mimetypes.guess_type(self.path) diff --git a/tests/server/test_server_interactions.py b/tests/server/test_server_interactions.py index 23e7b9957..6198a9f70 100644 --- a/tests/server/test_server_interactions.py +++ b/tests/server/test_server_interactions.py @@ -99,6 +99,11 @@ def tool_server(): TextContent(type="text", text="direct content"), ] + @mcp.tool + def file_text_tool() -> File: + # Return a File with text data and text/plain format + return File(data=b"hello world", format="plain") + return mcp @@ -110,7 +115,7 @@ class TestTools: async def test_list_tools(self, tool_server: FastMCP): async with Client(tool_server) as client: - assert len(await client.list_tools()) == 10 + assert len(await client.list_tools()) == 11 async def test_call_tool(self, tool_server: FastMCP): async with Client(tool_server) as client: @@ -151,6 +156,17 @@ class TestTools: result = await client.call_tool("list_tool", {}) assert result[0].text == '[\n "x",\n 2\n]' # type: ignore[attr-defined] + async def test_file_text_tool(self, tool_server: FastMCP): + async with Client(tool_server) as client: + result = await client.call_tool("file_text_tool", {}) + assert len(result) == 1 + embedded = result[0] + assert isinstance(embedded, EmbeddedResource) + resource = embedded.resource + assert isinstance(resource, TextResourceContents) + assert resource.mimeType == "text/plain" + assert resource.text == "hello world" + class TestToolTags: def create_server(self, include_tags=None, exclude_tags=None): diff --git a/tests/tools/test_tool.py b/tests/tools/test_tool.py index 6960b060c..54ac2061e 100644 --- a/tests/tools/test_tool.py +++ b/tests/tools/test_tool.py @@ -502,6 +502,19 @@ class TestConvertResultToContent: # Convert URI to string for startswith check assert str(resource.uri).startswith("file:///resource.octet-stream") + def test_file_object_text_result(self): + """Test that a File object with text data is converted to EmbeddedResource with TextResourceContents.""" + file_obj = File(data=b"sometext", format="plain") + result = _convert_to_content(file_obj) + assert isinstance(result, list) + assert len(result) == 1 + assert isinstance(result[0], EmbeddedResource) + assert result[0].type == "resource" + resource = result[0].resource + assert isinstance(resource, TextResourceContents) + assert resource.mimeType == "text/plain" + assert resource.text == "sometext" + def test_basic_type_result(self): """Test that a basic type is converted to TextContent.""" result = _convert_to_content(123) diff --git a/tests/utilities/test_types.py b/tests/utilities/test_types.py index 044cc3d31..1f8338318 100644 --- a/tests/utilities/test_types.py +++ b/tests/utilities/test_types.py @@ -3,7 +3,7 @@ from types import EllipsisType from typing import Annotated, Any import pytest -from mcp.types import BlobResourceContents +from mcp.types import BlobResourceContents, TextResourceContents from fastmcp.utilities.types import ( Audio, @@ -382,6 +382,17 @@ class TestFile: if isinstance(resource.resource, BlobResourceContents): assert resource.resource.blob == base64.b64encode(test_data).decode() + def test_to_resource_content_with_text_data(self): + """Test conversion to ResourceContent with text data (TextResourceContents).""" + test_data = b"hello world" + file = File(data=test_data, format="plain") + resource = file.to_resource_content() + assert resource.type == "resource" + # Should be TextResourceContents for text/plain + assert isinstance(resource.resource, TextResourceContents) + assert resource.resource.mimeType == "text/plain" + assert resource.resource.text == "hello world" + def test_to_resource_content_error(self, monkeypatch): """Test error case in to_resource_content.""" file = File(data=b"test")