From 865912a3338b55c1f6aa18cba90280749124182a Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Sun, 20 Apr 2025 10:09:36 -0700 Subject: [PATCH] replace inefficient use of "to_jsonable_python" --- src/mcp/server/fastmcp/prompts/base.py | 5 +++-- src/mcp/server/fastmcp/resources/types.py | 13 +++++-------- src/mcp/server/fastmcp/server.py | 7 ++----- .../fastmcp/resources/test_function_resources.py | 2 +- .../fastmcp/resources/test_resource_template.py | 2 +- tests/server/fastmcp/servers/test_file_server.py | 8 ++------ 6 files changed, 14 insertions(+), 23 deletions(-) diff --git a/src/mcp/server/fastmcp/prompts/base.py b/src/mcp/server/fastmcp/prompts/base.py index 71c48724..aa3d1eac 100644 --- a/src/mcp/server/fastmcp/prompts/base.py +++ b/src/mcp/server/fastmcp/prompts/base.py @@ -1,7 +1,6 @@ """Base classes for FastMCP prompts.""" import inspect -import json from collections.abc import Awaitable, Callable, Sequence from typing import Any, Literal @@ -155,7 +154,9 @@ async def render(self, arguments: dict[str, Any] | None = None) -> list[Message] content = TextContent(type="text", text=msg) messages.append(UserMessage(content=content)) else: - content = json.dumps(pydantic_core.to_jsonable_python(msg)) + content = pydantic_core.to_json( + msg, fallback=str, indent=2 + ).decode() messages.append(Message(role="user", content=content)) except Exception: raise ValueError( diff --git a/src/mcp/server/fastmcp/resources/types.py b/src/mcp/server/fastmcp/resources/types.py index d9fe2de6..2ab39b07 100644 --- a/src/mcp/server/fastmcp/resources/types.py +++ b/src/mcp/server/fastmcp/resources/types.py @@ -9,7 +9,7 @@ import anyio import anyio.to_thread import httpx -import pydantic.json +import pydantic import pydantic_core from pydantic import Field, ValidationInfo @@ -59,15 +59,12 @@ async def read(self) -> str | bytes: ) if isinstance(result, Resource): return await result.read() - if isinstance(result, bytes): + elif isinstance(result, bytes): return result - if isinstance(result, str): + elif isinstance(result, str): return result - try: - return json.dumps(pydantic_core.to_jsonable_python(result)) - except (TypeError, pydantic_core.PydanticSerializationError): - # If JSON serialization fails, try str() - return str(result) + else: + return pydantic_core.to_json(result, fallback=str, indent=2).decode() except Exception as e: raise ValueError(f"Error reading resource {self.uri}: {e}") diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index f3bb2586..aa240da7 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -3,7 +3,6 @@ from __future__ import annotations as _annotations import inspect -import json import re from collections.abc import AsyncIterator, Callable, Iterable, Sequence from contextlib import ( @@ -466,6 +465,7 @@ async def run_stdio_async(self) -> None: async def run_sse_async(self) -> None: """Run the server using SSE transport.""" import uvicorn + starlette_app = self.sse_app() config = uvicorn.Config( @@ -550,10 +550,7 @@ def _convert_to_content( return list(chain.from_iterable(_convert_to_content(item) for item in result)) # type: ignore[reportUnknownVariableType] if not isinstance(result, str): - try: - result = json.dumps(pydantic_core.to_jsonable_python(result)) - except Exception: - result = str(result) + result = pydantic_core.to_json(result, fallback=str, indent=2).decode() return [TextContent(type="text", text=result)] diff --git a/tests/server/fastmcp/resources/test_function_resources.py b/tests/server/fastmcp/resources/test_function_resources.py index 5bfc72bf..f0fe22bf 100644 --- a/tests/server/fastmcp/resources/test_function_resources.py +++ b/tests/server/fastmcp/resources/test_function_resources.py @@ -100,7 +100,7 @@ class MyModel(BaseModel): fn=lambda: MyModel(name="test"), ) content = await resource.read() - assert content == '{"name": "test"}' + assert content == '{\n "name": "test"\n}' @pytest.mark.anyio async def test_custom_type_conversion(self): diff --git a/tests/server/fastmcp/resources/test_resource_template.py b/tests/server/fastmcp/resources/test_resource_template.py index 09bc600d..f4724436 100644 --- a/tests/server/fastmcp/resources/test_resource_template.py +++ b/tests/server/fastmcp/resources/test_resource_template.py @@ -185,4 +185,4 @@ def get_data(value: str) -> CustomData: assert isinstance(resource, FunctionResource) content = await resource.read() - assert content == "hello" + assert content == '"hello"' diff --git a/tests/server/fastmcp/servers/test_file_server.py b/tests/server/fastmcp/servers/test_file_server.py index c1f51cab..b40778ea 100644 --- a/tests/server/fastmcp/servers/test_file_server.py +++ b/tests/server/fastmcp/servers/test_file_server.py @@ -114,17 +114,13 @@ async def test_read_resource_file(mcp: FastMCP): @pytest.mark.anyio async def test_delete_file(mcp: FastMCP, test_dir: Path): - await mcp.call_tool( - "delete_file", arguments={"path": str(test_dir / "example.py")} - ) + await mcp.call_tool("delete_file", arguments={"path": str(test_dir / "example.py")}) assert not (test_dir / "example.py").exists() @pytest.mark.anyio async def test_delete_file_and_check_resources(mcp: FastMCP, test_dir: Path): - await mcp.call_tool( - "delete_file", arguments={"path": str(test_dir / "example.py")} - ) + await mcp.call_tool("delete_file", arguments={"path": str(test_dir / "example.py")}) res_iter = await mcp.read_resource("file://test_dir/example.py") res_list = list(res_iter) assert len(res_list) == 1