[v1.x] Give recursive tool return types an object-rooted output schema (#3377)
Some checks failed
Main branch checks / checks (push) Has been cancelled

This commit is contained in:
Max 2026-08-24 19:12:08 +01:00 • committed by GitHub
commit b22271305c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 71 additions and 1 deletions

View file

@ -983,6 +983,33 @@ def test_structured_output_nested_models():
}
def test_structured_output_self_referential_model_gets_an_object_root():
"""pydantic publishes a recursive model as a bare root `$ref`; the definition is inlined onto the
root and `$defs` is kept for the nested reference."""
class Node(BaseModel):
name: str
children: list["Node"] = []
def tree() -> Node:
return Node(name="root", children=[Node(name="leaf")])
node_definition: dict[str, Any] = {
"properties": {
"name": {"title": "Name", "type": "string"},
"children": {"default": [], "items": {"$ref": "#/$defs/Node"}, "title": "Children", "type": "array"},
},
"required": ["name"],
"title": "Node",
"type": "object",
}
meta = func_metadata(tree)
assert meta.output_schema == {**node_definition, "$defs": {"Node": node_definition}}
_, structured_content = meta.convert_result(tree())
assert structured_content == {"name": "root", "children": [{"name": "leaf", "children": []}]}
def test_structured_output_unserializable_type_error():
"""Test error when structured_output=True is used with unserializable types"""
from typing import NamedTuple

View file

@ -531,6 +531,30 @@ class TestServerTools:
assert isinstance(result.content[0], TextContent)
assert '"name": "John Doe"' in result.content[0].text
@pytest.mark.anyio
async def test_tool_structured_output_self_referential_model(self):
"""A self-referential return type publishes an object-rooted outputSchema (required by the
2025-11-25 Tool shape) and its result validates client-side through the kept `$defs`."""
class Node(BaseModel):
name: str
children: list["Node"] = []
def tree() -> Node:
return Node(name="root", children=[Node(name="leaf")])
mcp = FastMCP()
mcp.add_tool(tree)
async with client_session(mcp._mcp_server) as client:
[tool] = (await client.list_tools()).tools
assert tool.outputSchema is not None
assert tool.outputSchema["type"] == "object"
assert tool.outputSchema["properties"]["children"]["items"] == {"$ref": "#/$defs/Node"}
result = await client.call_tool("tree", {})
assert result.isError is False
assert result.structuredContent == {"name": "root", "children": [{"name": "leaf", "children": []}]}
@pytest.mark.anyio
async def test_tool_structured_output_primitive(self):
"""Test tool with structured output returning primitive type"""