Skip to content

replace inefficient use of to_jsonable_python #545

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/mcp/server/fastmcp/prompts/base.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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(
Expand Down
13 changes: 5 additions & 8 deletions src/mcp/server/fastmcp/resources/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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}")

Expand Down
7 changes: 2 additions & 5 deletions src/mcp/server/fastmcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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)]

Expand Down
2 changes: 1 addition & 1 deletion tests/server/fastmcp/resources/test_function_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion tests/server/fastmcp/resources/test_resource_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,4 +185,4 @@ def get_data(value: str) -> CustomData:

assert isinstance(resource, FunctionResource)
content = await resource.read()
assert content == "hello"
assert content == '"hello"'
8 changes: 2 additions & 6 deletions tests/server/fastmcp/servers/test_file_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading