# /// script # dependencies = ["anthropic", "fastmcp", "rich"] # /// """ Structured Output Sampling Demonstrates using `result_type` to get validated Pydantic models from an LLM. The server exposes a sentiment analysis tool that returns structured data. Run: uv run examples/sampling/structured_output.py """ import asyncio from pydantic import BaseModel from rich.console import Console from rich.panel import Panel from rich.table import Table from fastmcp import Client, Context, FastMCP from fastmcp.client.sampling import SamplingMessage, SamplingParams from fastmcp.client.sampling.handlers.anthropic import AnthropicSamplingHandler console = Console() class LoggingAnthropicHandler(AnthropicSamplingHandler): async def __call__( self, messages: list[SamplingMessage], params: SamplingParams, context ): # type: ignore[override] console.print(" [bold blue]SAMPLING[/] Calling Claude API...") result = await super().__call__(messages, params, context) console.print(" [bold blue]SAMPLING[/] Response received") return result # Define a structured output model class SentimentAnalysis(BaseModel): sentiment: str # "positive", "negative", or "neutral" confidence: float # 0.0 to 1.0 keywords: list[str] # Keywords that influenced the analysis explanation: str # Brief explanation of the analysis # Create the MCP server mcp = FastMCP("Sentiment Analyzer") @mcp.tool async def analyze_sentiment(text: str, ctx: Context) -> dict: """Analyze the sentiment of the given text.""" console.print(" [bold cyan]SERVER[/] Analyzing sentiment...") result = await ctx.sample( messages=f"Analyze the sentiment of this text:\n\n{text}", system_prompt="You are a sentiment analysis expert. Analyze text carefully.", result_type=SentimentAnalysis, ) console.print(" [bold cyan]SERVER[/] Analysis complete") return result.result.model_dump() # type: ignore[attr-defined] async def main(): console.print( Panel.fit("[bold]MCP Sampling Flow Demo[/]", subtitle="structured_output.py") ) console.print() handler = LoggingAnthropicHandler(default_model="claude-sonnet-4-5") async with Client(mcp, sampling_handler=handler) as client: texts = [ "I absolutely love this product! It exceeded all my expectations.", "The service was okay, nothing special but got the job done.", "This is the worst experience I've ever had. Never again.", ] for text in texts: console.print(f"[bold green]CLIENT[/] Analyzing: [italic]{text[:50]}...[/]") console.print() result = await client.call_tool("analyze_sentiment", {"text": text}) data = result.data # Display results in a table table = Table(show_header=False, box=None, padding=(0, 2)) table.add_column(style="bold") table.add_column() sentiment_color = { "positive": "green", "negative": "red", "neutral": "yellow", }.get( data["sentiment"], "white", # type: ignore[union-attr] ) table.add_row("Sentiment", f"[{sentiment_color}]{data['sentiment']}[/]") # type: ignore[index] table.add_row("Confidence", f"{data['confidence']:.0%}") # type: ignore[index] table.add_row("Keywords", ", ".join(data["keywords"])) # type: ignore[index] table.add_row("Explanation", data["explanation"]) # type: ignore[index] console.print(Panel(table, border_style=sentiment_color)) console.print() if __name__ == "__main__": asyncio.run(main())