diff --git a/docs/clients/sampling.mdx b/docs/clients/sampling.mdx index 11a9a1bd2..5097faf43 100644 --- a/docs/clients/sampling.mdx +++ b/docs/clients/sampling.mdx @@ -269,4 +269,93 @@ async def sampling_handler_with_tools( Tool execution happens on the server side. The client's role is to pass tools to the LLM and return the LLM's response (which may include tool use requests). The server then executes the tools and may send follow-up sampling requests with tool results. - \ No newline at end of file + + +## Pre-built Handlers + + + +FastMCP provides ready-to-use sampling handlers for Anthropic and OpenAI that handle all the complexity of message conversion, tool formatting, and response parsing. + +### Anthropic Handler + +The Anthropic handler uses the Claude API: + +```python +from anthropic import Anthropic +from fastmcp import Client +from fastmcp.server.sampling.anthropic import AnthropicSamplingHandler + +handler = AnthropicSamplingHandler( + default_model="claude-sonnet-4-20250514", + client=Anthropic(), # Uses ANTHROPIC_API_KEY env var +) + +async with Client("server.py", sampling_handler=handler) as client: + result = await client.call_tool("summarize", {"text": "..."}) +``` + +The handler automatically: +- Converts MCP messages to Anthropic's format +- Translates tool definitions to Anthropic's tool format +- Maps stop reasons (`tool_use` → `toolUse`, `end_turn` → `endTurn`) +- Selects models based on server preferences (any model starting with `claude` is accepted) + + +Requires the `anthropic` package. Install with `pip install fastmcp[anthropic]` or `pip install anthropic`. + + +### OpenAI Handler + +The OpenAI handler works with the OpenAI API and compatible providers: + +```python +from openai import OpenAI +from fastmcp import Client +from fastmcp.server.sampling.openai import OpenAISamplingHandler + +handler = OpenAISamplingHandler( + default_model="gpt-4o-mini", + client=OpenAI(), # Uses OPENAI_API_KEY env var +) + +async with Client("server.py", sampling_handler=handler) as client: + result = await client.call_tool("analyze", {"data": "..."}) +``` + +The handler automatically: +- Converts MCP messages to OpenAI's chat format +- Translates tool definitions to OpenAI's function calling format +- Maps stop reasons (`tool_calls` → `toolUse`, `stop` → `endTurn`) +- Selects models based on server preferences + + +Requires the `openai` package. Install with `pip install fastmcp[openai]` or `pip install openai`. + + +### Using with Compatible Providers + +The OpenAI handler works with any OpenAI-compatible API: + +```python +from openai import OpenAI +from fastmcp.server.sampling.openai import OpenAISamplingHandler + +# Azure OpenAI +handler = OpenAISamplingHandler( + default_model="gpt-4o", + client=OpenAI( + api_key=os.getenv("AZURE_OPENAI_API_KEY"), + base_url="https://your-resource.openai.azure.com/openai/deployments/your-deployment", + ), +) + +# Local models (Ollama, vLLM, etc.) +handler = OpenAISamplingHandler( + default_model="llama3", + client=OpenAI( + api_key="not-needed", + base_url="http://localhost:11434/v1", + ), +) +``` \ No newline at end of file diff --git a/docs/servers/sampling.mdx b/docs/servers/sampling.mdx index 4b6dc2c68..a5ede6c8d 100644 --- a/docs/servers/sampling.mdx +++ b/docs/servers/sampling.mdx @@ -455,12 +455,24 @@ However, you can provide a `sampling_handler` to the FastMCP server, which sends - **`"fallback"`** (default): Uses the handler only when the client doesn't support sampling. Requests go to the client first, falling back to the handler if needed. - **`"always"`**: Always uses the handler, bypassing the client entirely. Useful when you want full control over the LLM used for sampling. -### OpenAI Handler +### Using Pre-built Handlers -FastMCP provides an OpenAI-compatible sampling handler that supports both basic sampling and tool use: +The server fallback handler uses the same signature as client sampling handlers. FastMCP provides pre-built handlers for [Anthropic and OpenAI](/clients/sampling#pre-built-handlers) that work in both contexts: + +```python +from fastmcp import FastMCP +from fastmcp.server.sampling.anthropic import AnthropicSamplingHandler + +server = FastMCP( + name="Sampling Server", + sampling_handler=AnthropicSamplingHandler( + default_model="claude-sonnet-4-20250514", + ), + sampling_handler_behavior="fallback", +) +``` ```python -import os from openai import OpenAI from fastmcp import FastMCP from fastmcp.server.sampling.openai import OpenAISamplingHandler @@ -469,109 +481,12 @@ server = FastMCP( name="Sampling Server", sampling_handler=OpenAISamplingHandler( default_model="gpt-4o-mini", - client=OpenAI(api_key=os.getenv("OPENAI_API_KEY")), + client=OpenAI(), ), - sampling_handler_behavior="fallback", + sampling_handler_behavior="always", # Always use server's LLM ) ``` -The OpenAI handler automatically handles tool conversion - when you pass tools to `ctx.sample()`, they're converted to OpenAI's function calling format. - -### Fallback Mode (Default) - -Uses the handler only when the client doesn't support sampling: - -```python -import asyncio -import os -from openai import OpenAI - -from fastmcp import FastMCP, Context -from fastmcp.server.sampling.openai import OpenAISamplingHandler - - -async def async_main(): - server = FastMCP( - name="OpenAI Sampling Fallback Example", - sampling_handler=OpenAISamplingHandler( - default_model="gpt-4o-mini", - client=OpenAI(api_key=os.getenv("OPENAI_API_KEY")), - ), - sampling_handler_behavior="fallback", - ) - - @server.tool - async def summarize(text: str, ctx: Context) -> str: - # Uses client's LLM if available, falls back to OpenAI - result = await ctx.sample( - messages=f"Summarize: {text}", - ) - return result.text - - await server.run_http_async() - - -if __name__ == "__main__": - asyncio.run(async_main()) -``` - -### Always Mode - -Always uses the handler, bypassing the client: - -```python -server = FastMCP( - name="Server-Controlled Sampling", - sampling_handler=OpenAISamplingHandler( - default_model="gpt-4o-mini", - client=OpenAI(api_key=os.getenv("API_KEY")), - ), - sampling_handler_behavior="always", -) - -@server.tool -async def analyze_data(data: str, ctx: Context) -> str: - # Always uses the server's configured LLM - result = await ctx.sample( - messages=f"Analyze this data: {data}", - system_prompt="You are a data analyst.", - ) - return result.text -``` - -### Fallback with Tools - -The fallback handler fully supports sampling with tools: - -```python -from fastmcp import FastMCP, Context -from fastmcp.server.sampling.openai import OpenAISamplingHandler -from openai import OpenAI -import os - -def search(query: str) -> str: - """Search for information.""" - return f"Results for: {query}" - -server = FastMCP( - name="Agentic Server", - sampling_handler=OpenAISamplingHandler( - default_model="gpt-4o-mini", - client=OpenAI(api_key=os.getenv("OPENAI_API_KEY")), - ), - sampling_handler_behavior="always", -) - -@server.tool -async def research(question: str, ctx: Context) -> str: - """Research a topic using tools.""" - result = await ctx.sample( - messages=question, - tools=[search], - ) - return result.text -``` - ## Client Requirements By default, LLM sampling requires client support: