60e0ffc959
Upgrade checks / Notify on failure (push) Has been cancelled
Upgrade checks / Close issue on success (push) Has been cancelled
Schema Crash Test / Real-world schema crash test (232K schemas) (push) Has been cancelled
Run static analysis / static_analysis (push) Has been cancelled
Tests / Tests: Python 3.10 on ubuntu-latest (push) Has been cancelled
Tests / Tests: Python 3.13 on ubuntu-latest (push) Has been cancelled
Tests / Tests: Python 3.10 on windows-latest (push) Has been cancelled
Tests / Tests with lowest-direct dependencies (push) Has been cancelled
Tests / MCP conformance tests (push) Has been cancelled
Tests / Integration tests (push) Has been cancelled
Tests / Package install smoke (push) Has been cancelled
Upgrade checks / Static analysis (push) Has been cancelled
Upgrade checks / Tests: Python 3.10 on ubuntu-latest (push) Has been cancelled
Upgrade checks / Tests: Python 3.13 on ubuntu-latest (push) Has been cancelled
Upgrade checks / Tests: Python 3.10 on windows-latest (push) Has been cancelled
Upgrade checks / Integration tests (push) Has been cancelled
Update MCPServerConfig Schema / update-config-schema (push) Has been cancelled
Update SDK Documentation / update-sdk-docs (push) Has been cancelled
126 lines
4.0 KiB
Python
126 lines
4.0 KiB
Python
# /// 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())
|