# /// script # dependencies = ["anthropic", "fastmcp", "rich"] # /// """ Sampling with Tools Demonstrates giving an LLM tools to use during sampling. The LLM can call helper functions to gather information before responding. Run: uv run examples/sampling/tool_use.py """ import asyncio import random from datetime import datetime from pydantic import BaseModel, Field from rich.console import Console from rich.panel import Panel 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 tools available to the LLM during sampling def add(a: float, b: float) -> str: """Add two numbers together.""" result = a + b console.print(f" [bold magenta]TOOL[/] add({a}, {b}) = {result}") return str(result) def multiply(a: float, b: float) -> str: """Multiply two numbers together.""" result = a * b console.print(f" [bold magenta]TOOL[/] multiply({a}, {b}) = {result}") return str(result) def get_current_time() -> str: """Get the current date and time.""" console.print(" [bold magenta]TOOL[/] get_current_time()") return datetime.now().strftime("%Y-%m-%d %H:%M:%S") def roll_dice(sides: int = 6) -> str: """Roll a die with the specified number of sides.""" result = random.randint(1, sides) console.print(f" [bold magenta]TOOL[/] roll_dice({sides}) = {result}") return str(result) # Structured output for the response class AssistantResponse(BaseModel): answer: str = Field(description="The answer to the user's question") tools_used: list[str] = Field(description="List of tools that were used") reasoning: str = Field( description="Brief explanation of how the answer was determined" ) # Create the MCP server mcp = FastMCP("Smart Assistant") @mcp.tool async def ask_assistant(question: str, ctx: Context) -> dict: """Ask the assistant a question. It can use tools to help answer.""" console.print(" [bold cyan]SERVER[/] Processing question...") result = await ctx.sample( messages=question, system_prompt="You are a helpful assistant with access to tools. Use them when needed to answer questions accurately.", tools=[add, multiply, get_current_time, roll_dice], result_type=AssistantResponse, ) console.print(" [bold cyan]SERVER[/] Response ready") return result.result.model_dump() # type: ignore[attr-defined] async def main(): console.print(Panel.fit("[bold]MCP Sampling Flow Demo[/]", subtitle="tool_use.py")) console.print() handler = LoggingAnthropicHandler(default_model="claude-sonnet-4-5") async with Client(mcp, sampling_handler=handler) as client: questions = [ "What is 15 times 7, plus 23?", "Roll a 20-sided dice for me", "What time is it right now?", ] for question in questions: console.print(f"[bold green]CLIENT[/] Question: {question}") console.print() result = await client.call_tool("ask_assistant", {"question": question}) data = result.data console.print(f"[bold green]CLIENT[/] Answer: {data['answer']}") # type: ignore[index] console.print( f" Tools used: {', '.join(data['tools_used']) or 'none'}" ) # type: ignore[index] console.print(f" Reasoning: {data['reasoning']}") # type: ignore[index] console.print() if __name__ == "__main__": asyncio.run(main())