Skip to content

Commit ef32710

Browse files
authored
Merge branch 'main' into main
2 parents c5270e7 + 3f0964d commit ef32710

32 files changed

+3743
-612
lines changed

.github/workflows/publish-docs-manually.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,4 @@ jobs:
3030
mkdocs-material-
3131
3232
- run: uv sync --frozen --group docs
33-
- run: uv run --no-sync mkdocs gh-deploy --force
33+
- run: uv run --frozen --no-sync mkdocs gh-deploy --force

.github/workflows/publish-pypi.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,4 @@ jobs:
7979
mkdocs-material-
8080
8181
- run: uv sync --frozen --group docs
82-
- run: uv run --no-sync mkdocs gh-deploy --force
82+
- run: uv run --frozen --no-sync mkdocs gh-deploy --force

.github/workflows/shared.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,5 @@ jobs:
4444
run: uv sync --frozen --all-extras --python ${{ matrix.python-version }}
4545

4646
- name: Run pytest
47-
run: uv run --no-sync pytest
47+
run: uv run --frozen --no-sync pytest
4848
continue-on-error: true

README.md

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
- [Server](#server)
2828
- [Resources](#resources)
2929
- [Tools](#tools)
30+
- [Structured Output](#structured-output)
3031
- [Prompts](#prompts)
3132
- [Images](#images)
3233
- [Context](#context)
@@ -249,6 +250,127 @@ async def fetch_weather(city: str) -> str:
249250
return response.text
250251
```
251252

253+
#### Structured Output
254+
255+
Tools will return structured results by default, if their return type
256+
annotation is compatible. Otherwise, they will return unstructured results.
257+
258+
Structured output supports these return types:
259+
- Pydantic models (BaseModel subclasses)
260+
- TypedDicts
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.
269+
270+
Structured results are automatically validated against the output schema
271+
generated from the annotation. This ensures the tool returns well-typed,
272+
validated data that clients can easily process.
273+
274+
**Note:** For backward compatibility, unstructured results are also
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.
278+
279+
**Note:** In cases where a tool function's return type annotation
280+
causes the tool to be classified as structured _and this is undesirable_,
281+
the classification can be suppressed by passing `structured_output=False`
282+
to the `@tool` decorator.
283+
284+
```python
285+
from mcp.server.fastmcp import FastMCP
286+
from pydantic import BaseModel, Field
287+
from typing import TypedDict
288+
289+
mcp = FastMCP("Weather Service")
290+
291+
292+
# Using Pydantic models for rich structured data
293+
class WeatherData(BaseModel):
294+
temperature: float = Field(description="Temperature in Celsius")
295+
humidity: float = Field(description="Humidity percentage")
296+
condition: str
297+
wind_speed: float
298+
299+
300+
@mcp.tool()
301+
def get_weather(city: str) -> WeatherData:
302+
"""Get structured weather data"""
303+
return WeatherData(
304+
temperature=22.5, humidity=65.0, condition="partly cloudy", wind_speed=12.3
305+
)
306+
307+
308+
# Using TypedDict for simpler structures
309+
class LocationInfo(TypedDict):
310+
latitude: float
311+
longitude: float
312+
name: str
313+
314+
315+
@mcp.tool()
316+
def get_location(address: str) -> LocationInfo:
317+
"""Get location coordinates"""
318+
return LocationInfo(latitude=51.5074, longitude=-0.1278, name="London, UK")
319+
320+
321+
# Using dict[str, Any] for flexible schemas
322+
@mcp.tool()
323+
def get_statistics(data_type: str) -> dict[str, float]:
324+
"""Get various statistics"""
325+
return {"mean": 42.5, "median": 40.0, "std_dev": 5.2}
326+
327+
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+
359+
# Lists and other types are wrapped automatically
360+
@mcp.tool()
361+
def list_cities() -> list[str]:
362+
"""Get a list of cities"""
363+
return ["London", "Paris", "Tokyo"]
364+
# Returns: {"result": ["London", "Paris", "Tokyo"]}
365+
366+
367+
@mcp.tool()
368+
def get_temperature(city: str) -> float:
369+
"""Get temperature as a simple float"""
370+
return 22.5
371+
# Returns: {"result": 22.5}
372+
```
373+
252374
### Prompts
253375

254376
Prompts are reusable templates that help LLMs interact with your server effectively:
@@ -829,6 +951,67 @@ if __name__ == "__main__":
829951

830952
Caution: The `mcp run` and `mcp dev` tool doesn't support low-level server.
831953

954+
#### Structured Output Support
955+
956+
The low-level server supports structured output for tools, allowing you to return both human-readable content and machine-readable structured data. Tools can define an `outputSchema` to validate their structured output:
957+
958+
```python
959+
from types import Any
960+
961+
import mcp.types as types
962+
from mcp.server.lowlevel import Server
963+
964+
server = Server("example-server")
965+
966+
967+
@server.list_tools()
968+
async def list_tools() -> list[types.Tool]:
969+
return [
970+
types.Tool(
971+
name="calculate",
972+
description="Perform mathematical calculations",
973+
inputSchema={
974+
"type": "object",
975+
"properties": {
976+
"expression": {"type": "string", "description": "Math expression"}
977+
},
978+
"required": ["expression"],
979+
},
980+
outputSchema={
981+
"type": "object",
982+
"properties": {
983+
"result": {"type": "number"},
984+
"expression": {"type": "string"},
985+
},
986+
"required": ["result", "expression"],
987+
},
988+
)
989+
]
990+
991+
992+
@server.call_tool()
993+
async def call_tool(name: str, arguments: dict[str, Any]) -> dict[str, Any]:
994+
if name == "calculate":
995+
expression = arguments["expression"]
996+
try:
997+
result = eval(expression) # Use a safe math parser
998+
structured = {"result": result, "expression": expression}
999+
1000+
# low-level server will validate structured output against the tool's
1001+
# output schema, and automatically serialize it into a TextContent block
1002+
# for backwards compatibility with pre-2025-06-18 clients.
1003+
return structured
1004+
except Exception as e:
1005+
raise ValueError(f"Calculation error: {str(e)}")
1006+
```
1007+
1008+
Tools can return data in three ways:
1009+
1. **Content only**: Return a list of content blocks (default behavior before spec revision 2025-06-18)
1010+
2. **Structured data only**: Return a dictionary that will be serialized to JSON (Introduced in spec revision 2025-06-18)
1011+
3. **Both**: Return a tuple of (content, structured_data) preferred option to use for backwards compatibility
1012+
1013+
When an `outputSchema` is defined, the server automatically validates the structured output against the schema. This ensures type safety and helps catch errors early.
1014+
8321015
### Writing MCP Clients
8331016

8341017
The SDK provides a high-level client interface for connecting to MCP servers using various [transports](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports):

0 commit comments

Comments
 (0)