Execute server-side tools and handle structured results.
New in version 2.0.0Use this when you need to execute server-side functions and process their results.Tools are executable functions exposed by MCP servers. The client’s call_tool() method executes a tool by name with arguments and returns structured results.
async with client: result = await client.call_tool("add", {"a": 5, "b": 3}) # result -> CallToolResult with structured and unstructured data # Access structured data (automatically deserialized) print(result.data) # 8 # Access traditional content blocks print(result.content[0].text) # "8"
Arguments are passed as a dictionary. For multi-server clients, tool names are automatically prefixed with the server name (e.g., weather_get_forecast for a tool named get_forecast on the weather server).
New in version 2.10.0Tool execution returns a CallToolResult object. The .data property provides fully hydrated Python objects including complex types like datetimes and UUIDs, reconstructed from the server’s output schema.
Copy
from datetime import datetimefrom uuid import UUIDasync with client: result = await client.call_tool("get_weather", {"city": "London"}) # FastMCP reconstructs complete Python objects weather = result.data print(f"Temperature: {weather.temperature}C at {weather.timestamp}") # Complex types are properly deserialized assert isinstance(weather.timestamp, datetime) assert isinstance(weather.station_id, UUID) # Raw structured JSON is also available print(f"Raw JSON: {result.structured_content}")
For tools without output schemas or when deserialization fails, .data will be None. Fall back to content blocks in that case:
Copy
async with client: result = await client.call_tool("legacy_tool", {"param": "value"}) if result.data is not None: print(f"Structured: {result.data}") else: for content in result.content: if hasattr(content, 'text'): print(f"Text result: {content.text}")
FastMCP servers automatically wrap primitive results (like int, str, bool) in a {"result": value} structure. FastMCP clients automatically unwrap this, so you get the original value in .data.
For complete control, use call_tool_mcp() which returns the raw MCP protocol object:
Copy
async with client: result = await client.call_tool_mcp("my_tool", {"param": "value"}) # result -> mcp.types.CallToolResult if result.isError: print(f"Tool failed: {result.content}") else: print(f"Tool succeeded: {result.content}") # Note: No automatic deserialization with call_tool_mcp()