fix(tools): honor serialize_by_alias in tool result serialization (#4391)

This commit is contained in:
Jeremiah Lowin 2026-06-27 10:24:39 -04:00 committed by GitHub
commit dc4b0e202d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 315 additions and 12 deletions

View file

@ -658,6 +658,53 @@ async def test_from_tool_decorated_function_via_client():
assert "Result 0 for hello" in result.content[0].text
async def test_transform_fn_result_respects_serialize_by_alias():
"""A model returned by a transform_fn honors serialize_by_alias when no schema."""
from pydantic import ConfigDict
class Item(BaseModel):
model_config = ConfigDict(serialize_by_alias=False)
id: str = Field(alias="_id")
def base() -> None:
pass
async def transform() -> Any:
return Item(_id="42")
transformed = Tool.from_tool(base, transform_fn=transform, output_schema=None)
result = await transformed.run({})
assert result.structured_content == {"id": "42"}
async def test_transform_fn_wrapped_result_respects_serialize_by_alias():
"""A wrapped transform result serializes the inner model before nesting.
Optional model returns get a wrap-result schema; the inner model must be
serialized with its own config before being placed under "result", or the
wrapped dict masks the config and the data no longer matches the schema.
"""
from pydantic import ConfigDict
class Item(BaseModel):
model_config = ConfigDict(serialize_by_alias=False)
id: str = Field(alias="_id")
def base() -> None:
pass
async def transform() -> Item | None:
return Item(_id="42")
transformed = Tool.from_tool(base, transform_fn=transform)
assert transformed.output_schema is not None
assert transformed.output_schema.get("x-fastmcp-wrap-result")
result = await transformed.run({})
assert result.structured_content == {"result": {"id": "42"}}
class TestProxy:
@pytest.fixture
def mcp_server(self) -> FastMCP: