Discover and execute server-side tools with the FastMCP client.
New in version:2.0.0Tools are executable functions exposed by MCP servers. The FastMCP client provides methods to discover available tools and execute them with arguments.
New in version:2.11.0You can use the meta field to filter tools based on their tags:
Copy
async with client: tools = await client.list_tools() # Filter tools by tag analysis_tools = [ tool for tool in tools if hasattr(tool, 'meta') and tool.meta and tool.meta.get('_fastmcp', {}) and 'analysis' in tool.meta.get('_fastmcp', {}).get('tags', []) ] print(f"Found {len(analysis_tools)} analysis tools")
The meta field is part of the standard MCP specification. FastMCP servers include tags and other metadata within a _fastmcp namespace (e.g., meta._fastmcp.tags) to avoid conflicts with user-defined metadata. This behavior can be controlled with the server’s include_fastmcp_meta setting - when disabled, the _fastmcp namespace won’t be included. Other MCP server implementations may not provide this metadata structure.
Execute a tool using call_tool() with the tool name and arguments:
Copy
async with client: # Simple tool call 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 (int) or {"result": 8} for primitive types # Access traditional content blocks print(result.content[0].text) # "8" (TextContent)
New in version:2.10.0Tool 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:
Copy
async with client: result = await client.call_tool("legacy_tool", {"param": "value"}) if result.data is not None: # Structured output available and successfully deserialized print(f"Structured: {result.data}") else: # No structured output or deserialization failed - use content blocks for content in result.content: if hasattr(content, 'text'): print(f"Text result: {content.text}") elif hasattr(content, 'data'): print(f"Binary data: {len(content.data)} bytes")
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.
Copy
async with client: result = await client.call_tool("calculate_sum", {"a": 5, "b": 3}) # FastMCP client automatically unwraps for convenience print(result.data) # 8 (int) - the original value # Raw structured content shows the server-side wrapping print(result.structured_content) # {"result": 8} # Other MCP clients would need to manually access ["result"] # value = result.structured_content["result"] # Not needed with FastMCP!
For complete control, use call_tool_mcp() which returns the raw MCP protocol object:
Copy
async with client: result = await client.call_tool_mcp("potentially_failing_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()
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).