Skip to content

Commit f302a99

Browse files
committed
refactor:
- strict serializability checking - simpler, more consistent handling of return type detection and conversion - centralized logic
1 parent 3ab59c5 commit f302a99

File tree

6 files changed

+264
-305
lines changed

6 files changed

+264
-305
lines changed

README.md

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -258,20 +258,23 @@ annotation is compatible. Otherwise, they will return unstructured results.
258258
Structured output supports these return types:
259259
- Pydantic models (BaseModel subclasses)
260260
- TypedDicts
261-
- Dataclasses
262-
- NamedTuples
263-
- `dict[str, T]`
264-
- Primitive types (str, int, float, bool) - wrapped in `{"result": value}`
265-
- Generic types (list, tuple, set) - wrapped in `{"result": value}`
266-
- Union types and Optional - wrapped in `{"result": value}`
261+
- Dataclasses and other classes with type hints
262+
- `dict[str, T]` (where T is any JSON-serializable type)
263+
- Primitive types (str, int, float, bool, bytes, None) - wrapped in `{"result": value}`
264+
- Generic types (list, tuple, Union, Optional, etc.) - wrapped in `{"result": value}`
265+
266+
Classes without type hints cannot be serialized for structured output. Only
267+
classes with properly annotated attributes will be converted to Pydantic models
268+
for schema generation and validation.
267269

268270
Structured results are automatically validated against the output schema
269271
generated from the annotation. This ensures the tool returns well-typed,
270-
validated data that clients can easily process:
272+
validated data that clients can easily process.
271273

272274
**Note:** For backward compatibility, unstructured results are also
273-
returned. Unstructured results are provided strictly for compatibility
274-
with previous versions of FastMCP.
275+
returned. Unstructured results are provided for backward compatibility
276+
with previous versions of the MCP specification, and are quirks-compatible
277+
with previous versions of FastMCP in the current version of the SDK.
275278

276279
**Note:** In cases where a tool function's return type annotation
277280
causes the tool to be classified as structured _and this is undesirable_,
@@ -322,6 +325,37 @@ def get_statistics(data_type: str) -> dict[str, float]:
322325
return {"mean": 42.5, "median": 40.0, "std_dev": 5.2}
323326

324327

328+
# Ordinary classes with type hints work for structured output
329+
class UserProfile:
330+
name: str
331+
age: int
332+
email: str | None = None
333+
334+
def __init__(self, name: str, age: int, email: str | None = None):
335+
self.name = name
336+
self.age = age
337+
self.email = email
338+
339+
340+
@mcp.tool()
341+
def get_user(user_id: str) -> UserProfile:
342+
"""Get user profile - returns structured data"""
343+
return UserProfile(name="Alice", age=30, email="[email protected]")
344+
345+
346+
# Classes WITHOUT type hints cannot be used for structured output
347+
class UntypedConfig:
348+
def __init__(self, setting1, setting2):
349+
self.setting1 = setting1
350+
self.setting2 = setting2
351+
352+
353+
@mcp.tool()
354+
def get_config() -> UntypedConfig:
355+
"""This returns unstructured output - no schema generated"""
356+
return UntypedConfig("value1", "value2")
357+
358+
325359
# Lists and other types are wrapped automatically
326360
@mcp.tool()
327361
def list_cities() -> list[str]:

src/mcp/server/fastmcp/tools/base.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,7 @@ class Tool(BaseModel):
3535

3636
@cached_property
3737
def output_schema(self) -> dict[str, Any] | None:
38-
if self.fn_metadata.output_model is not None:
39-
return self.fn_metadata.output_model.model_json_schema()
40-
return None
41-
42-
@property
43-
def is_structured(self) -> bool:
44-
return self.fn_metadata.output_model is not None
38+
return self.fn_metadata.output_schema
4539

4640
@classmethod
4741
def from_function(

0 commit comments

Comments
 (0)