feat: Add structured output support for tool functions (#993)
This commit is contained in:
parent
c8bbfc034d
commit
43bb24f62b
1 changed files with 1774 additions and 61 deletions
122
README.md
122
README.md
|
|
@ -27,6 +27,7 @@
|
|||
- [Server](#server)
|
||||
- [Resources](#resources)
|
||||
- [Tools](#tools)
|
||||
- [Structured Output](#structured-output)
|
||||
- [Prompts](#prompts)
|
||||
- [Images](#images)
|
||||
- [Context](#context)
|
||||
|
|
@ -249,6 +250,127 @@ async def fetch_weather(city: str) -> str:
|
|||
return response.text
|
||||
```
|
||||
|
||||
#### Structured Output
|
||||
|
||||
Tools will return structured results by default, if their return type
|
||||
annotation is compatible. Otherwise, they will return unstructured results.
|
||||
|
||||
Structured output supports these return types:
|
||||
- Pydantic models (BaseModel subclasses)
|
||||
- TypedDicts
|
||||
- Dataclasses and other classes with type hints
|
||||
- `dict[str, T]` (where T is any JSON-serializable type)
|
||||
- Primitive types (str, int, float, bool, bytes, None) - wrapped in `{"result": value}`
|
||||
- Generic types (list, tuple, Union, Optional, etc.) - wrapped in `{"result": value}`
|
||||
|
||||
Classes without type hints cannot be serialized for structured output. Only
|
||||
classes with properly annotated attributes will be converted to Pydantic models
|
||||
for schema generation and validation.
|
||||
|
||||
Structured results are automatically validated against the output schema
|
||||
generated from the annotation. This ensures the tool returns well-typed,
|
||||
validated data that clients can easily process.
|
||||
|
||||
**Note:** For backward compatibility, unstructured results are also
|
||||
returned. Unstructured results are provided for backward compatibility
|
||||
with previous versions of the MCP specification, and are quirks-compatible
|
||||
with previous versions of FastMCP in the current version of the SDK.
|
||||
|
||||
**Note:** In cases where a tool function's return type annotation
|
||||
causes the tool to be classified as structured _and this is undesirable_,
|
||||
the classification can be suppressed by passing `structured_output=False`
|
||||
to the `@tool` decorator.
|
||||
|
||||
```python
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import TypedDict
|
||||
|
||||
mcp = FastMCP("Weather Service")
|
||||
|
||||
|
||||
# Using Pydantic models for rich structured data
|
||||
class WeatherData(BaseModel):
|
||||
temperature: float = Field(description="Temperature in Celsius")
|
||||
humidity: float = Field(description="Humidity percentage")
|
||||
condition: str
|
||||
wind_speed: float
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_weather(city: str) -> WeatherData:
|
||||
"""Get structured weather data"""
|
||||
return WeatherData(
|
||||
temperature=22.5, humidity=65.0, condition="partly cloudy", wind_speed=12.3
|
||||
)
|
||||
|
||||
|
||||
# Using TypedDict for simpler structures
|
||||
class LocationInfo(TypedDict):
|
||||
latitude: float
|
||||
longitude: float
|
||||
name: str
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_location(address: str) -> LocationInfo:
|
||||
"""Get location coordinates"""
|
||||
return LocationInfo(latitude=51.5074, longitude=-0.1278, name="London, UK")
|
||||
|
||||
|
||||
# Using dict[str, Any] for flexible schemas
|
||||
@mcp.tool()
|
||||
def get_statistics(data_type: str) -> dict[str, float]:
|
||||
"""Get various statistics"""
|
||||
return {"mean": 42.5, "median": 40.0, "std_dev": 5.2}
|
||||
|
||||
|
||||
# Ordinary classes with type hints work for structured output
|
||||
class UserProfile:
|
||||
name: str
|
||||
age: int
|
||||
email: str | None = None
|
||||
|
||||
def __init__(self, name: str, age: int, email: str | None = None):
|
||||
self.name = name
|
||||
self.age = age
|
||||
self.email = email
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_user(user_id: str) -> UserProfile:
|
||||
"""Get user profile - returns structured data"""
|
||||
return UserProfile(name="Alice", age=30, email="alice@example.com")
|
||||
|
||||
|
||||
# Classes WITHOUT type hints cannot be used for structured output
|
||||
class UntypedConfig:
|
||||
def __init__(self, setting1, setting2):
|
||||
self.setting1 = setting1
|
||||
self.setting2 = setting2
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_config() -> UntypedConfig:
|
||||
"""This returns unstructured output - no schema generated"""
|
||||
return UntypedConfig("value1", "value2")
|
||||
|
||||
|
||||
# Lists and other types are wrapped automatically
|
||||
@mcp.tool()
|
||||
def list_cities() -> list[str]:
|
||||
"""Get a list of cities"""
|
||||
return ["London", "Paris", "Tokyo"]
|
||||
# Returns: {"result": ["London", "Paris", "Tokyo"]}
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_temperature(city: str) -> float:
|
||||
"""Get temperature as a simple float"""
|
||||
return 22.5
|
||||
# Returns: {"result": 22.5}
|
||||
```
|
||||
|
||||
### Prompts
|
||||
|
||||
Prompts are reusable templates that help LLMs interact with your server effectively:
|
||||
|
|
|
|||
Loading…
Reference in a new issue