Skip to content
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
39 changes: 4 additions & 35 deletions src/fastmcp/tools/tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ async def run(self, arguments: dict[str, Any]) -> ToolResult:


def _is_object_schema(schema: dict[str, Any]) -> bool:
"""Check if a JSON schema represents an object type, resolving $ref references."""
"""Check if a JSON schema represents an object type."""
# Direct object type
if schema.get("type") == "object":
return True
Expand All @@ -377,40 +377,9 @@ def _is_object_schema(schema: dict[str, Any]) -> bool:
if "properties" in schema:
return True

# Resolve $ref references to check the referenced schema
# Self-referencing types (e.g., list["ReturnThing"]) generate schemas with $ref
# at the root level instead of "type": "object" directly
if "$ref" in schema:
ref = schema["$ref"]
if ref.startswith("#/$defs/"):
# Resolve reference within the same schema document
# The schema dict contains both $ref and $defs
defs_path = ref.replace("#/$defs/", "").split("/")
if "$defs" in schema:
defs = schema["$defs"]
current = defs
# Navigate through the defs path
for part in defs_path:
if isinstance(current, dict) and part in current:
current = current[part]
else:
# Can't resolve, assume it might be an object
# (safer to assume object than to wrap incorrectly)
return True
# Recursively check the resolved schema
if isinstance(current, dict):
return _is_object_schema(current)
# If $defs not found but we have a $ref, assume object
# (self-referencing types are typically objects)
return True
elif ref == "#":
# Self-reference - treat as object (common for recursive types)
return True
# For other $ref patterns, assume object to be safe
# (most $refs in JSON schemas point to object types)
return True

return False
# Self-referencing types use $ref pointing to $defs
# The referenced type is always an object in our use case
return "$ref" in schema and "$defs" in schema


@dataclass
Expand Down
2 changes: 1 addition & 1 deletion tests/server/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ async def test_tool_decorator_with_output_schema(self):
mcp = FastMCP()

with pytest.raises(
ValueError, match='Output schemas must have "type" set to "object"'
ValueError, match="Output schemas must represent object types"
):

@mcp.tool(output_schema={"type": "integer"})
Expand Down
Loading