You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
# Access structured data (automatically deserialized)
43
+
print(result.data) # 8 (int) or {"result": 8} for primitive types
44
+
45
+
# Access traditional content blocks
46
+
print(result.content[0].text) # "8" (TextContent)
44
47
```
45
48
46
49
### Advanced Execution Options
@@ -72,21 +75,97 @@ async with client:
72
75
73
76
## Handling Results
74
77
75
-
Tool execution returns a list of content objects. The most common types are:
78
+
<VersionBadgeversion="2.10.0" />
79
+
80
+
Tool execution returns a `CallToolResult` object with both structured and traditional content. FastMCP's standout feature is the `.data` property, which doesn't just provide raw JSON but actually hydrates complete Python objects including complex types like datetimes, UUIDs, and custom classes.
**FastMCP exclusive**: Fully hydrated Python objects with complex type support (datetimes, UUIDs, custom classes). Goes beyond JSON to provide complete object reconstruction from output schemas.
For tools without output schemas or when deserialization fails, `.data` will be `None`:
134
+
135
+
```python
136
+
asyncwith client:
137
+
result =await client.call_tool("legacy_tool", {"param": "value"})
138
+
139
+
if result.data isnotNone:
140
+
# Structured output available and successfully deserialized
141
+
print(f"Structured: {result.data}")
142
+
else:
143
+
# No structured output or deserialization failed - use content blocks
144
+
for content in result.content:
145
+
ifhasattr(content, 'text'):
146
+
print(f"Text result: {content.text}")
147
+
elifhasattr(content, 'data'):
148
+
print(f"Binary data: {len(content.data)} bytes")
149
+
```
150
+
151
+
### Primitive Type Unwrapping
152
+
153
+
<Tip>
154
+
FastMCP servers automatically wrap non-object results (like `int`, `str`, `bool`) in a `{"result": value}` structure to create valid structured outputs. FastMCP clients understand this convention and automatically unwrap the value in `.data` for convenience, so you get the original primitive value instead of a wrapper object.
155
+
</Tip>
156
+
157
+
```python
158
+
asyncwith client:
159
+
result =await client.call_tool("calculate_sum", {"a": 5, "b": 3})
160
+
161
+
# FastMCP client automatically unwraps for convenience
162
+
print(result.data) # 8 (int) - the original value
163
+
164
+
# Raw structured content shows the server-side wrapping
165
+
print(result.structured_content) # {"result": 8}
166
+
167
+
# Other MCP clients would need to manually access ["result"]
168
+
# value = result.structured_content["result"] # Not needed with FastMCP!
90
169
```
91
170
92
171
## Error Handling
@@ -101,14 +180,32 @@ from fastmcp.exceptions import ToolError
101
180
asyncwith client:
102
181
try:
103
182
result =await client.call_tool("potentially_failing_tool", {"param": "value"})
104
-
print("Tool succeeded:", result)
183
+
print("Tool succeeded:", result.data)
105
184
except ToolError as e:
106
185
print(f"Tool failed: {e}")
107
186
```
108
187
109
188
### Manual Error Checking
110
189
111
-
For more granular control, use `call_tool_mcp()` which returns the raw MCP protocol object with an `isError` flag:
190
+
You can disable automatic error raising and manually check the result:
191
+
192
+
```python
193
+
asyncwith client:
194
+
result =await client.call_tool(
195
+
"potentially_failing_tool",
196
+
{"param": "value"},
197
+
raise_on_error=False
198
+
)
199
+
200
+
if result.is_error:
201
+
print(f"Tool failed: {result.content[0].text}")
202
+
else:
203
+
print(f"Tool succeeded: {result.data}")
204
+
```
205
+
206
+
### Raw MCP Protocol Access
207
+
208
+
For complete control, use `call_tool_mcp()` which returns the raw MCP protocol object:
112
209
113
210
```python
114
211
asyncwith client:
@@ -119,6 +216,7 @@ async with client:
119
216
print(f"Tool failed: {result.content}")
120
217
else:
121
218
print(f"Tool succeeded: {result.content}")
219
+
# Note: No automatic deserialization with call_tool_mcp()
Copy file name to clipboardExpand all lines: docs/patterns/tool-transformation.mdx
+39-1Lines changed: 39 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -89,6 +89,7 @@ The `Tool.from_tool()` class method is the primary way to create a transformed t
89
89
-`description`: An optional description for the new tool.
90
90
-`transform_args`: A dictionary of `ArgTransform` objects, one for each argument you want to modify.
91
91
-`transform_fn`: An optional function that will be called instead of the parent tool's logic.
92
+
-`output_schema`: Control output schema and structured outputs (see [Output Schema Control](#output-schema-control)).
92
93
-`tags`: An optional set of tags for the new tool.
93
94
-`annotations`: An optional set of `ToolAnnotations` for the new tool.
94
95
-`serializer`: An optional function that will be called to serialize the result of the new tool.
@@ -439,7 +440,44 @@ mcp.add_tool(new_tool)
439
440
440
441
<Tip>
441
442
In the above example, `**kwargs` receives the renamed argument `b`, not the original argument `y`. It is therefore recommended to use with `forward()`, not `forward_raw()`.
442
-
</Tip>
443
+
</Tip>
444
+
445
+
## Output Schema Control
446
+
447
+
<VersionBadgeversion="2.10.0" />
448
+
449
+
Transformed tools inherit output schemas from their parent by default, but you can control this behavior:
450
+
451
+
**Inherit from Parent (Default)**
452
+
```python
453
+
Tool.from_tool(parent_tool, name="renamed_tool")
454
+
```
455
+
The transformed tool automatically uses the parent tool's output schema and structured output behavior.
456
+
457
+
**Custom Output Schema**
458
+
```python
459
+
Tool.from_tool(parent_tool, output_schema={
460
+
"type": "object",
461
+
"properties": {"status": {"type": "string"}}
462
+
})
463
+
```
464
+
Provide your own schema that differs from the parent. The tool must return data matching this schema.
465
+
466
+
**Remove Output Schema**
467
+
```python
468
+
Tool.from_tool(parent_tool, output_schema=False)
469
+
```
470
+
Removes the output schema declaration. Automatic structured content still works for object-like returns (dict, dataclass, Pydantic models) but primitive types won't be structured.
0 commit comments