Update documentation

This commit is contained in:
Jeremiah Lowin 2025-06-28 15:05:13 -04:00
commit 2a3fa142f8
6 changed files with 947 additions and 303 deletions

View file

@ -69,8 +69,18 @@
"pages": [
"servers/tools",
"servers/resources",
"servers/prompts",
"servers/context"
"servers/prompts"
]
},
{
"group": "Advanced Features",
"icon": "stars",
"pages": [
"servers/context",
"servers/elicitation",
"servers/logging",
"servers/progress",
"servers/sampling"
]
},
{

View file

@ -6,7 +6,7 @@ icon: rectangle-code
---
import { VersionBadge } from '/snippets/version-badge.mdx'
When defining FastMCP [tools](/servers/tools), [resources](/servers/resources), resource templates, or [prompts](/servers/prompts), your functions might need to interact with the underlying MCP session or access server capabilities. FastMCP provides the `Context` object for this purpose.
When defining FastMCP [tools](/servers/tools), [resources](/servers/resources), resource templates, or [prompts](/servers/prompts), your functions might need to interact with the underlying MCP session or access advanced server capabilities. FastMCP provides the `Context` object for this purpose.
## What Is Context?
@ -37,10 +37,10 @@ To use the context object within any of your functions, simply add a parameter t
#### Tools
```python
```python {1, 6}
from fastmcp import FastMCP, Context
mcp = FastMCP(name="ContextDemo")
mcp = FastMCP(name="Context Demo")
@mcp.tool
async def process_file(file_uri: str, ctx: Context) -> str:
@ -53,7 +53,11 @@ async def process_file(file_uri: str, ctx: Context) -> str:
<VersionBadge version="2.2.5" />
```python
```python {1, 6, 12}
from fastmcp import FastMCP, Context
mcp = FastMCP(name="Context Demo")
@mcp.resource("resource://user-data")
async def get_user_data(ctx: Context) -> dict:
"""Fetch personalized user data based on the request context."""
@ -71,7 +75,11 @@ async def get_user_profile(user_id: str, ctx: Context) -> dict:
<VersionBadge version="2.2.5" />
```python
```python {1, 6}
from fastmcp import FastMCP, Context
mcp = FastMCP(name="Context Demo")
@mcp.prompt
async def data_analysis_request(dataset: str, ctx: Context) -> str:
"""Generate a request to analyze data with contextual information."""
@ -89,10 +97,10 @@ While the simplest way to access context is through function parameter injection
FastMCP provides dependency functions that allow you to retrieve the active context from anywhere within a server request's execution flow:
```python {2,9}
from fastmcp import FastMCP, Context
from fastmcp import FastMCP
from fastmcp.server.dependencies import get_context
mcp = FastMCP(name="DependencyDemo")
mcp = FastMCP(name="Dependency Demo")
# Utility function that needs context but doesn't receive it as a parameter
async def process_data(data: list[float]) -> dict:
@ -114,279 +122,71 @@ async def analyze_dataset(dataset_name: str) -> dict:
## Context Capabilities
FastMCP provides several advanced capabilities through the context object. Each capability has dedicated documentation with comprehensive examples and best practices:
### Logging
Send log messages back to the MCP client. This is useful for debugging and providing visibility into function execution during a request.
Send debug, info, warning, and error messages back to the MCP client for visibility into function execution.
```python
@mcp.tool
async def analyze_data(data: list[float], ctx: Context) -> dict:
"""Analyze numerical data with logging."""
await ctx.debug("Starting analysis of numerical data")
await ctx.info(f"Analyzing {len(data)} data points")
try:
result = sum(data) / len(data)
await ctx.info(f"Analysis complete, average: {result}")
return {"average": result, "count": len(data)}
except ZeroDivisionError:
await ctx.warning("Empty data list provided")
return {"error": "Empty data list"}
except Exception as e:
await ctx.error(f"Analysis failed: {str(e)}")
raise
await ctx.debug("Starting analysis")
await ctx.info(f"Processing {len(data)} items")
await ctx.warning("Deprecated parameter used")
await ctx.error("Processing failed")
```
**Available Logging Methods:**
See [Server Logging](/servers/logging) for complete documentation and examples.
### Client Elicitation
- **`ctx.debug(message: str)`**: Low-level details useful for debugging
- **`ctx.info(message: str)`**: General information about execution
- **`ctx.warning(message: str)`**: Potential issues that didn't prevent execution
- **`ctx.error(message: str)`**: Errors that occurred during execution
- **`ctx.log(level: Literal["debug", "info", "warning", "error"], message: str, logger_name: str | None = None)`**: Generic log method supporting custom logger names
<VersionBadge version="2.10.0" />
### Progress Reporting
For long-running operations, notify the client about the progress. This allows clients to display progress indicators and provide a better user experience.
Request structured input from clients during tool execution, enabling interactive workflows and progressive disclosure. This is a new feature in the 6/18/2025 MCP spec.
```python
@mcp.tool
async def process_items(items: list[str], ctx: Context) -> dict:
"""Process a list of items with progress updates."""
total = len(items)
results = []
for i, item in enumerate(items):
# Report progress as percentage
await ctx.report_progress(progress=i, total=total)
# Process the item (simulated with a sleep)
await asyncio.sleep(0.1)
results.append(item.upper())
# Report 100% completion
await ctx.report_progress(progress=total, total=total)
return {"processed": len(results), "results": results}
result = await ctx.elicit("Enter your name:", response_type=str)
if result.action == "accept":
name = result.data
```
**Method signature:**
- **`ctx.report_progress(progress: float, total: float | None = None)`**
- `progress`: Current progress value (e.g., 24)
- `total`: Optional total value (e.g., 100). If provided, clients may interpret this as a percentage.
Progress reporting requires the client to have sent a `progressToken` in the initial request. If the client doesn't support progress reporting, these calls will have no effect.
### Resource Access
Read data from resources registered with your FastMCP server. This allows functions to access files, configuration, or dynamically generated content.
```python
@mcp.tool
async def summarize_document(document_uri: str, ctx: Context) -> str:
"""Summarize a document by its resource URI."""
# Read the document content
content_list = await ctx.read_resource(document_uri)
if not content_list:
return "Document is empty"
document_text = content_list[0].content
# Example: Generate a simple summary (length-based)
words = document_text.split()
total_words = len(words)
await ctx.info(f"Document has {total_words} words")
# Return a simple summary
if total_words > 100:
summary = " ".join(words[:100]) + "..."
return f"Summary ({total_words} words total): {summary}"
else:
return f"Full document ({total_words} words): {document_text}"
```
**Method signature:**
- **`ctx.read_resource(uri: str | AnyUrl) -> list[ReadResourceContents]`**
- `uri`: The resource URI to read
- Returns a list of resource content parts (usually containing just one item)
The returned content is typically accessed via `content_list[0].content` and can be text or binary data depending on the resource.
See [User Elicitation](/servers/elicitation) for detailed examples and supported response types.
### LLM Sampling
<VersionBadge version="2.0.0" />
Request the client's LLM to generate text based on provided messages. This is useful when your function needs to leverage the LLM's capabilities to process data or generate responses.
Request the client's LLM to generate text based on provided messages, useful for leveraging AI capabilities within your tools.
```python
@mcp.tool
async def analyze_sentiment(text: str, ctx: Context) -> dict:
"""Analyze the sentiment of a text using the client's LLM."""
# Create a sampling prompt asking for sentiment analysis
prompt = f"Analyze the sentiment of the following text as positive, negative, or neutral. Just output a single word - 'positive', 'negative', or 'neutral'. Text to analyze: {text}"
# Send the sampling request to the client's LLM (provide a hint for the model you want to use)
response = await ctx.sample(prompt, model_preferences="claude-3-sonnet")
# Process the LLM's response
sentiment = response.text.strip().lower()
# Map to standard sentiment values
if "positive" in sentiment:
sentiment = "positive"
elif "negative" in sentiment:
sentiment = "negative"
else:
sentiment = "neutral"
return {"text": text, "sentiment": sentiment}
response = await ctx.sample("Analyze this data", temperature=0.7)
```
See [LLM Sampling](/servers/sampling) for comprehensive usage and advanced techniques.
### Progress Reporting
Update clients on the progress of long-running operations, enabling progress indicators and better user experience.
```python
await ctx.report_progress(progress=50, total=100) # 50% complete
```
See [Progress Reporting](/servers/progress) for detailed patterns and examples.
### Resource Access
Read data from resources registered with your FastMCP server, allowing access to files, configuration, or dynamic content.
```python
content_list = await ctx.read_resource("resource://config")
content = content_list[0].content
```
**Method signature:**
- **`ctx.read_resource(uri: str | AnyUrl) -> list[ReadResourceContents]`**: Returns a list of resource content parts
- **`ctx.sample(messages: str | list[str | SamplingMessage], system_prompt: str | None = None, temperature: float | None = None, max_tokens: int | None = None, model_preferences: ModelPreferences | str | list[str] | None = None) -> TextContent | ImageContent`**
- `messages`: A string or list of strings/message objects to send to the LLM
- `system_prompt`: Optional system prompt to guide the LLM's behavior
- `temperature`: Optional sampling temperature (controls randomness)
- `max_tokens`: Optional maximum number of tokens to generate (defaults to 512)
- `model_preferences`: Optional model selection preferences (e.g., a model hint string, list of hints, or a ModelPreferences object)
- Returns the LLM's response as TextContent or ImageContent
When providing a simple string, it's treated as a user message. For more complex scenarios, you can provide a list of messages with different roles.
```python
@mcp.tool
async def generate_example(concept: str, ctx: Context) -> str:
"""Generate a Python code example for a given concept."""
# Using a system prompt and a user message
response = await ctx.sample(
messages=f"Write a simple Python code example demonstrating '{concept}'.",
system_prompt="You are an expert Python programmer. Provide concise, working code examples without explanations.",
temperature=0.7,
max_tokens=300
)
code_example = response.text
return f"```python\n{code_example}\n```"
```
See [Client Sampling](/clients/client#llm-sampling) for more details on how clients handle these requests.
### User Elicitation
<VersionBadge version="2.10.0" />
Request structured input from users during tool execution. This allows tools to interactively ask for missing parameters, clarification, or additional context as needed.
```python
from dataclasses import dataclass
@dataclass
class UserInfo:
name: str
age: int
@mcp.tool
async def collect_user_info(ctx: Context) -> str:
"""Collect user information through interactive prompts."""
# Request structured user information
result = await ctx.elicit(
message="Please provide your information",
response_type=UserInfo
)
if result.action == "accept":
user = result.data
return f"Hello {user.name}, you are {user.age} years old"
elif result.action == "decline":
return "Information not provided"
else: # cancel
return "Operation cancelled"
```
**Method signature:**
- **`ctx.elicit(message: str, response_type: type = str) -> ElicitationResult`**
- `message`: The prompt message to display to the user
- `response_type`: The Python type defining the expected response structure (dataclass, primitive type, etc.)
- Returns an `ElicitationResult` with `action` ("accept", "decline", "cancel") and `data` (when accepted)
**Supported Response Types:**
- **Primitive types**: `str`, `int`, `float`, `bool`
- **Literal types**: `Literal["option1", "option2"]` for constrained choices
- **Enum types**: Python enums for predefined options
- **Dataclass types**: Custom structured data with multiple fields
```python
from typing import Literal
from enum import Enum
class Priority(Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
@dataclass
class TaskInfo:
title: str
priority: Priority
urgent: bool
@mcp.tool
async def create_task(ctx: Context) -> str:
"""Create a task with user-provided details."""
# Multiple elicitation calls for different information
# Simple string input
title_result = await ctx.elicit("What's the task title?", response_type=str)
if title_result.action != "accept":
return "Task creation cancelled"
# Enum selection
priority_result = await ctx.elicit("What's the priority?", response_type=Priority)
if priority_result.action != "accept":
return "Task creation cancelled"
# Boolean choice
urgent_result = await ctx.elicit("Is this urgent?", response_type=bool)
if urgent_result.action != "accept":
return "Task creation cancelled"
return f"Created task: {title_result.data} (Priority: {priority_result.data.value}, Urgent: {urgent_result.data})"
```
**Pattern Matching Support:**
FastMCP provides typed result classes for pattern matching:
```python
from fastmcp.server.elicitation import (
AcceptedElicitation,
DeclinedElicitation,
CancelledElicitation
)
@mcp.tool
async def pattern_example(ctx: Context) -> str:
result = await ctx.elicit("Enter your name:", response_type=str)
match result:
case AcceptedElicitation(data=name):
return f"Hello {name}!"
case DeclinedElicitation():
return "No name provided"
case CancelledElicitation():
return "Operation cancelled"
```
Elicitation requires the client to provide an elicitation handler. If the client doesn't support elicitation, the request will fail. See [Client Elicitation](/clients/elicitation) for details on implementing client-side handlers.
### Component Changes
### Change Notifications
<VersionBadge version="2.9.1" />
@ -405,7 +205,19 @@ async def custom_tool_management(ctx: Context) -> str:
These methods are primarily used internally by FastMCP's automatic notification system and most users will not need to invoke them directly.
### Request Information
### FastMCP Server
To access the underlying FastMCP server instance, you can use the `ctx.fastmcp` property:
```python
@mcp.tool
async def my_tool(ctx: Context) -> None:
# Access the FastMCP server instance
server_name = ctx.fastmcp.name
...
```
### MCP Request
Access metadata about the current request and client.
@ -425,60 +237,6 @@ async def request_info(ctx: Context) -> dict:
- **`ctx.client_id -> str | None`**: Get the ID of the client making the request, if provided during initialization
- **`ctx.session_id -> str | None`**: Get the MCP session ID for session-based data sharing (HTTP transports only)
### Advanced Access
#### FastMCP Server and Sessions
```python
@mcp.tool
async def advanced_tool(ctx: Context) -> str:
"""Demonstrate advanced context access."""
# Access the FastMCP server instance
server_name = ctx.fastmcp.name
# Low-level session access (rarely needed)
session = ctx.session
request_context = ctx.request_context
return f"Server: {server_name}"
```
#### HTTP Requests
<VersionBadge version="2.2.7" />
<Warning>
The `ctx.get_http_request()` method is deprecated and will be removed in a future version.
Please use the `get_http_request()` dependency function instead.
See the [HTTP Requests pattern](/patterns/http-requests) for more details.
</Warning>
For web applications, you can access the underlying HTTP request:
```python
@mcp.tool
async def handle_web_request(ctx: Context) -> dict:
"""Access HTTP request information from the Starlette request."""
request = ctx.get_http_request()
# Access HTTP headers, query parameters, etc.
user_agent = request.headers.get("user-agent", "Unknown")
client_ip = request.client.host if request.client else "Unknown"
return {
"user_agent": user_agent,
"client_ip": client_ip,
"path": request.url.path,
}
```
#### Advanced Properties Reference
- **`ctx.fastmcp -> FastMCP`**: Access the server instance the context belongs to
- **`ctx.session`**: Access the raw `mcp.server.session.ServerSession` object
- **`ctx.request_context`**: Access the raw `mcp.shared.context.RequestContext` object
<Warning>
Direct use of `session` or `request_context` requires understanding the low-level MCP Python SDK and may be less stable than using the methods provided directly on the `Context` object.
</Warning>
The MCP request is part of the low-level MCP SDK and intended for advanced use cases. Most users will not need to use it directly.
</Warning>

View file

@ -0,0 +1,303 @@
---
title: User Elicitation
sidebarTitle: Elicitation
description: Request structured input from users during tool execution through the MCP context.
icon: user-check
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="2.10.0" />
User elicitation allows MCP servers to request structured input from users during tool execution. Instead of requiring all inputs upfront, tools can interactively ask for missing parameters, clarification, or additional context as needed.
<Tip>
Most of the examples in this document assume you have a FastMCP server instance named `mcp` and show how to use the `ctx.elicit` method to request user input from an `@mcp.tool`-decorated function.
</Tip>
## What is Elicitation?
Elicitation enables tools to pause execution and request specific information from users. This is particularly useful for:
- **Missing parameters**: Ask for required information not provided initially
- **Clarification requests**: Get user confirmation or choices for ambiguous scenarios
- **Progressive disclosure**: Collect complex information step-by-step
- **Dynamic workflows**: Adapt tool behavior based on user responses
For example, a file management tool might ask "Which directory should I create?" or a data analysis tool might request "What date range should I analyze?"
### Basic Usage
Use the `ctx.elicit()` method within any tool function to request user input:
```python {14-17}
from fastmcp import FastMCP, Context
from dataclasses import dataclass
mcp = FastMCP("Elicitation Server")
@dataclass
class UserInfo:
name: str
age: int
@mcp.tool
async def collect_user_info(ctx: Context) -> str:
"""Collect user information through interactive prompts."""
result = await ctx.elicit(
message="Please provide your information",
response_type=UserInfo
)
if result.action == "accept":
user = result.data
return f"Hello {user.name}, you are {user.age} years old"
elif result.action == "decline":
return "Information not provided"
else: # cancel
return "Operation cancelled"
```
## Method Signature
<Card icon="code" title="Context Elicitation Method">
<ResponseField name="ctx.elicit" type="async method">
<Expandable title="Parameters">
<ResponseField name="message" type="str">
The prompt message to display to the user
</ResponseField>
<ResponseField name="response_type" type="type" default="str">
The Python type defining the expected response structure (dataclass, primitive type, etc.) Note that elicitation responses are subject to a restricted subset of JSON Schema types. See [Supported Response Types](#supported-response-types) for more details.
</ResponseField>
</Expandable>
<Expandable title="Response">
<ResponseField name="ElicitationResult" type="object">
Result object containing the user's response
<Expandable title="properties">
<ResponseField name="action" type="Literal['accept', 'decline', 'cancel']">
How the user responded to the request
</ResponseField>
<ResponseField name="data" type="response_type | None">
The user's input data (only present when action is "accept")
</ResponseField>
</Expandable>
</ResponseField>
</Expandable>
</ResponseField>
</Card>
## Elicitation Actions
The elicitation result contains an `action` field indicating how the user responded:
- **`accept`**: User provided valid input - data is available in the `data` field
- **`decline`**: User chose not to provide the requested information and the data field is `None`
- **`cancel`**: User cancelled the entire operation and the data field is `None`
```python {5, 7}
@mcp.tool
async def my_tool(ctx: Context) -> str:
result = await ctx.elicit("Choose an action")
if result.action == "accept":
return "Accepted!"
elif result.action == "decline":
return "Declined!"
else:
return "Cancelled!"
```
FastMCP also provides typed result classes for pattern matching on the `action` field:
```python {1-5, 12, 14, 16}
from fastmcp.server.elicitation import (
AcceptedElicitation,
DeclinedElicitation,
CancelledElicitation,
)
@mcp.tool
async def pattern_example(ctx: Context) -> str:
result = await ctx.elicit("Enter your name:", response_type=str)
match result:
case AcceptedElicitation(data=name):
return f"Hello {name}!"
case DeclinedElicitation():
return "No name provided"
case CancelledElicitation():
return "Operation cancelled"
```
## Response Types
The server must send a schema to the client indicating the type of data it expects in response to the elicitation request. If the request is `accept`-ed, the client must send a response that matches the schema.
The MCP spec only supports a limited subset of JSON Schema types for elicitation responses. Specifically, it only supports JSON **objects** with **primitive** properties including `string`, `number` (or `integer`), `boolean` and `enum` fields.
FastMCP makes it easy to request a broader range of types, including scalars (e.g. `str`), by automatically wrapping them in MCP-compatible object schemas.
### Scalar Types
You can request simple scalar data types for basic input, such as a string, integer, or boolean.
When you request a scalar type, FastMCP automatically wraps it in an object schema for MCP spec compatibility. Clients will see a corresponding schema requesting a single "value" field of the requested type. Once clients respond, the provided object is "unwrapped" and the scalar value is returned to your tool function as the `data` field of the `ElicitationResult` object.
As a developer, this means you do not have to worry about creating or accessing a structured object when you only need a scalar value.
<CodeGroup>
```python {4} title="Request a string"
@mcp.tool
async def get_user_name(ctx: Context) -> str:
"""Get the user's name."""
result = await ctx.elicit("What's your name?", response_type=str)
if result.action == "accept":
return f"Hello, {result.data}!"
return "No name provided"
```
```python {4} title="Request an integer"
@mcp.tool
async def pick_a_number(ctx: Context) -> str:
"""Pick a number."""
result = await ctx.elicit("Pick a number!", response_type=int)
if result.action == "accept":
return f"You picked {result.data}"
return "No number provided"
```
```python {4} title="Request a boolean"
@mcp.tool
async def pick_a_boolean(ctx: Context) -> str:
"""Pick a boolean."""
result = await ctx.elicit("True or false?", response_type=bool)
if result.action == "accept":
return f"You picked {result.data}"
return "No boolean provided"
```
</CodeGroup>
### Constrained Options
Often you'll want to constrain the user's response to a specific set of values. You can do this by using a `Literal` type or a Python enum as the response type, or by passing a list of strings to the `response_type` parameter as a convenient shortcut.
<CodeGroup>
```python {6} title="Using a list of strings"
@mcp.tool
async def set_priority(ctx: Context) -> str:
"""Set task priority level."""
result = await ctx.elicit(
"What priority level?",
response_type=["low", "medium", "high"],
)
if result.action == "accept":
return f"Priority set to: {result.data}"
```
```python {1, 8} title="Using a Literal type"
from typing import Literal
@mcp.tool
async def set_priority(ctx: Context) -> str:
"""Set task priority level."""
result = await ctx.elicit(
"What priority level?",
response_type=Literal["low", "medium", "high"]
)
if result.action == "accept":
return f"Priority set to: {result.data}"
return "No priority set"
```
```python {1, 11} title="Using a Python enum"
from enum import Enum
class Priority(Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
@mcp.tool
async def set_priority(ctx: Context) -> str:
"""Set task priority level."""
result = await ctx.elicit("What priority level?", response_type=Priority)
if result.action == "accept":
return f"Priority set to: {result.data.value}"
return "No priority set"
```
</CodeGroup>
### Structured Responses
You can request structured data with multiple fields by using a dataclass, typed dict, or Pydantic model as the response type. Note that the MCP spec only supports shallow objects with scalar (string, number, boolean) or enum properties.
```python {1, 16, 20}
from dataclasses import dataclass
from typing import Literal
@dataclass
class TaskDetails:
title: str
description: str
priority: Literal["low", "medium", "high"]
due_date: str
@mcp.tool
async def create_task(ctx: Context) -> str:
"""Create a new task with user-provided details."""
result = await ctx.elicit(
"Please provide task details",
response_type=TaskDetails
)
if result.action == "accept":
task = result.data
return f"Created task: {task.title} (Priority: {task.priority})"
return "Task creation cancelled"
```
## Multi-Turn Elicitation
Tools can make multiple elicitation calls to gather information progressively:
```python {6, 11, 16-19}
@mcp.tool
async def plan_meeting(ctx: Context) -> str:
"""Plan a meeting by gathering details step by step."""
# Get meeting title
title_result = await ctx.elicit("What's the meeting title?", response_type=str)
if title_result.action != "accept":
return "Meeting planning cancelled"
# Get duration
duration_result = await ctx.elicit("Duration in minutes?", response_type=int)
if duration_result.action != "accept":
return "Meeting planning cancelled"
# Get priority
priority_result = await ctx.elicit(
"Is this urgent?",
response_type=Literal["yes", "no"]
)
if priority_result.action != "accept":
return "Meeting planning cancelled"
urgent = priority_result.data == "yes"
return f"Meeting '{title_result.data}' planned for {duration_result.data} minutes (Urgent: {urgent})"
```
## Client Requirements
Elicitation requires the client to implement an elicitation handler. See [Client Elicitation](/clients/elicitation) for details on how clients can handle these requests.
If a client doesn't support elicitation, calls to `ctx.elicit()` will raise an error indicating that elicitation is not supported.

194
docs/servers/logging.mdx Normal file
View file

@ -0,0 +1,194 @@
---
title: Server Logging
sidebarTitle: Logging
description: Send log messages back to MCP clients through the context.
icon: receipt
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<Tip>
This documentation covers **MCP client logging** - sending messages from your server to MCP clients. For standard server-side logging (e.g., writing to files, console), use `fastmcp.utilities.logging.get_logger()` or Python's built-in `logging` module.
</Tip>
Server logging allows MCP tools to send debug, info, warning, and error messages back to the client. This provides visibility into function execution and helps with debugging during development and operation.
## Why Use Server Logging?
Server logging is essential for:
- **Debugging**: Send detailed execution information to help diagnose issues
- **Progress visibility**: Keep users informed about what the tool is doing
- **Error reporting**: Communicate problems and their context to clients
- **Audit trails**: Create records of tool execution for compliance or analysis
Unlike standard Python logging, MCP server logging sends messages directly to the client, making them visible in the client's interface or logs.
### Basic Usage
Use the context logging methods within any tool function:
```python {8-9, 13, 17, 21}
from fastmcp import FastMCP, Context
mcp = FastMCP("LoggingDemo")
@mcp.tool
async def analyze_data(data: list[float], ctx: Context) -> dict:
"""Analyze numerical data with comprehensive logging."""
await ctx.debug("Starting analysis of numerical data")
await ctx.info(f"Analyzing {len(data)} data points")
try:
if not data:
await ctx.warning("Empty data list provided")
return {"error": "Empty data list"}
result = sum(data) / len(data)
await ctx.info(f"Analysis complete, average: {result}")
return {"average": result, "count": len(data)}
except Exception as e:
await ctx.error(f"Analysis failed: {str(e)}")
raise
```
## Logging Methods
<Card icon="code" title="Context Logging Methods">
<ResponseField name="ctx.debug" type="async method">
Send debug-level messages for detailed execution information
<Expandable title="parameters">
<ResponseField name="message" type="str">
The debug message to send to the client
</ResponseField>
</Expandable>
</ResponseField>
<ResponseField name="ctx.info" type="async method">
Send informational messages about normal execution
<Expandable title="parameters">
<ResponseField name="message" type="str">
The information message to send to the client
</ResponseField>
</Expandable>
</ResponseField>
<ResponseField name="ctx.warning" type="async method">
Send warning messages for potential issues that didn't prevent execution
<Expandable title="parameters">
<ResponseField name="message" type="str">
The warning message to send to the client
</ResponseField>
</Expandable>
</ResponseField>
<ResponseField name="ctx.error" type="async method">
Send error messages for problems that occurred during execution
<Expandable title="parameters">
<ResponseField name="message" type="str">
The error message to send to the client
</ResponseField>
</Expandable>
</ResponseField>
<ResponseField name="ctx.log" type="async method">
Generic logging method with custom level and logger name
<Expandable title="parameters">
<ResponseField name="level" type="Literal['debug', 'info', 'warning', 'error']">
The log level for the message
</ResponseField>
<ResponseField name="message" type="str">
The message to send to the client
</ResponseField>
<ResponseField name="logger_name" type="str | None" default="None">
Optional custom logger name for categorizing messages
</ResponseField>
</Expandable>
</ResponseField>
</Card>
## Log Levels
### Debug
Use for detailed information that's typically only useful when diagnosing problems:
```python
@mcp.tool
async def process_file(file_path: str, ctx: Context) -> str:
"""Process a file with detailed debug logging."""
await ctx.debug(f"Starting to process file: {file_path}")
await ctx.debug("Checking file permissions")
# File processing logic
await ctx.debug("File processing completed successfully")
return "File processed"
```
### Info
Use for general information about normal program execution:
```python
@mcp.tool
async def backup_database(ctx: Context) -> str:
"""Backup database with progress information."""
await ctx.info("Starting database backup")
await ctx.info("Connecting to database")
await ctx.info("Backup completed successfully")
return "Database backed up"
```
### Warning
Use for potentially harmful situations that don't prevent execution:
```python
@mcp.tool
async def validate_config(config: dict, ctx: Context) -> dict:
"""Validate configuration with warnings for deprecated options."""
if "old_api_key" in config:
await ctx.warning("Using deprecated 'old_api_key' field. Please use 'api_key' instead")
if config.get("timeout", 30) > 300:
await ctx.warning("Timeout value is very high (>5 minutes), this may cause issues")
return {"status": "valid", "warnings": "see logs"}
```
### Error
Use for error events that might still allow the application to continue:
```python
@mcp.tool
async def batch_process(items: list[str], ctx: Context) -> dict:
"""Process multiple items, logging errors for failed items."""
successful = 0
failed = 0
for item in items:
try:
# Process item
successful += 1
except Exception as e:
await ctx.error(f"Failed to process item '{item}': {str(e)}")
failed += 1
return {"successful": successful, "failed": failed}
```
## Client Handling
Log messages are sent to the client through the MCP protocol. How clients handle these messages depends on their implementation:
- **Development clients**: May display logs in real-time for debugging
- **Production clients**: May store logs for later analysis or display to users
- **Integration clients**: May forward logs to external logging systems
See [Client Logging](/clients/logging) for details on how clients can handle server log messages.

189
docs/servers/progress.mdx Normal file
View file

@ -0,0 +1,189 @@
---
title: Progress Reporting
sidebarTitle: Progress
description: Update clients on the progress of long-running operations through the MCP context.
icon: chart-line
---
import { VersionBadge } from '/snippets/version-badge.mdx'
Progress reporting allows MCP tools to notify clients about the progress of long-running operations. This enables clients to display progress indicators and provide better user experience during time-consuming tasks.
## Why Use Progress Reporting?
Progress reporting is valuable for:
- **User experience**: Keep users informed about long-running operations
- **Progress indicators**: Enable clients to show progress bars or percentages
- **Timeout prevention**: Demonstrate that operations are actively progressing
- **Debugging**: Track execution progress for performance analysis
### Basic Usage
Use `ctx.report_progress()` to send progress updates to the client:
```python {14, 21}
from fastmcp import FastMCP, Context
import asyncio
mcp = FastMCP("ProgressDemo")
@mcp.tool
async def process_items(items: list[str], ctx: Context) -> dict:
"""Process a list of items with progress updates."""
total = len(items)
results = []
for i, item in enumerate(items):
# Report progress as we process each item
await ctx.report_progress(progress=i, total=total)
# Simulate processing time
await asyncio.sleep(0.1)
results.append(item.upper())
# Report 100% completion
await ctx.report_progress(progress=total, total=total)
return {"processed": len(results), "results": results}
```
## Method Signature
<Card icon="code" title="Context Progress Method">
<ResponseField name="ctx.report_progress" type="async method">
Report progress to the client for long-running operations
<Expandable title="parameters">
<ResponseField name="progress" type="float">
Current progress value (e.g., 24, 0.75, 1500)
</ResponseField>
<ResponseField name="total" type="float | None" default="None">
Optional total value (e.g., 100, 1.0, 2000). When provided, clients may interpret this as enabling percentage calculation.
</ResponseField>
</Expandable>
</ResponseField>
</Card>
## Progress Patterns
### Percentage-Based Progress
Report progress as a percentage (0-100):
```python {13-14}
@mcp.tool
async def download_file(url: str, ctx: Context) -> str:
"""Download a file with percentage progress."""
total_size = 1000 # KB
downloaded = 0
while downloaded < total_size:
# Download chunk
chunk_size = min(50, total_size - downloaded)
downloaded += chunk_size
# Report percentage progress
percentage = (downloaded / total_size) * 100
await ctx.report_progress(progress=percentage, total=100)
await asyncio.sleep(0.1) # Simulate download time
return f"Downloaded file from {url}"
```
### Absolute Progress
Report progress with absolute values:
```python {10}
@mcp.tool
async def backup_database(ctx: Context) -> str:
"""Backup database tables with absolute progress."""
tables = ["users", "orders", "products", "inventory", "logs"]
for i, table in enumerate(tables):
await ctx.info(f"Backing up table: {table}")
# Report absolute progress
await ctx.report_progress(progress=i + 1, total=len(tables))
# Simulate backup time
await asyncio.sleep(0.5)
return "Database backup completed"
```
### Indeterminate Progress
Report progress without a known total for operations where the endpoint is unknown:
```python {11}
@mcp.tool
async def scan_directory(directory: str, ctx: Context) -> dict:
"""Scan directory with indeterminate progress."""
files_found = 0
# Simulate directory scanning
for i in range(10): # Unknown number of files
files_found += 1
# Report progress without total for indeterminate operations
await ctx.report_progress(progress=files_found)
await asyncio.sleep(0.2)
return {"files_found": files_found, "directory": directory}
```
### Multi-Stage Operations
Break complex operations into stages with progress for each:
```python
@mcp.tool
async def data_migration(source: str, destination: str, ctx: Context) -> str:
"""Migrate data with multi-stage progress reporting."""
# Stage 1: Validation (0-25%)
await ctx.info("Validating source data")
for i in range(5):
await ctx.report_progress(progress=i * 5, total=100)
await asyncio.sleep(0.1)
# Stage 2: Export (25-60%)
await ctx.info("Exporting data from source")
for i in range(7):
progress = 25 + (i * 5)
await ctx.report_progress(progress=progress, total=100)
await asyncio.sleep(0.1)
# Stage 3: Transform (60-80%)
await ctx.info("Transforming data format")
for i in range(4):
progress = 60 + (i * 5)
await ctx.report_progress(progress=progress, total=100)
await asyncio.sleep(0.1)
# Stage 4: Import (80-100%)
await ctx.info("Importing to destination")
for i in range(4):
progress = 80 + (i * 5)
await ctx.report_progress(progress=progress, total=100)
await asyncio.sleep(0.1)
# Final completion
await ctx.report_progress(progress=100, total=100)
return f"Migration from {source} to {destination} completed"
```
## Client Requirements
Progress reporting requires clients to support progress handling:
- Clients must send a `progressToken` in the initial request to receive progress updates
- If no progress token is provided, progress calls will have no effect (they won't error)
- See [Client Progress](/clients/progress) for details on implementing client-side progress handling

190
docs/servers/sampling.mdx Normal file
View file

@ -0,0 +1,190 @@
---
title: LLM Sampling
sidebarTitle: Sampling
description: Request the client's LLM to generate text based on provided messages through the MCP context.
icon: robot
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="2.0.0" />
LLM sampling allows MCP tools to request the client's LLM to generate text based on provided messages. This is useful when tools need to leverage the LLM's capabilities to process data, generate responses, or perform text-based analysis.
## Why Use LLM Sampling?
LLM sampling enables tools to:
- **Leverage AI capabilities**: Use the client's LLM for text generation and analysis
- **Offload complex reasoning**: Let the LLM handle tasks requiring natural language understanding
- **Generate dynamic content**: Create responses, summaries, or transformations based on data
- **Maintain context**: Use the same LLM instance that the user is already interacting with
### Basic Usage
Use `ctx.sample()` to request text generation from the client's LLM:
```python {14}
from fastmcp import FastMCP, Context
mcp = FastMCP("SamplingDemo")
@mcp.tool
async def analyze_sentiment(text: str, ctx: Context) -> dict:
"""Analyze the sentiment of text using the client's LLM."""
prompt = f"""Analyze the sentiment of the following text as positive, negative, or neutral.
Just output a single word - 'positive', 'negative', or 'neutral'.
Text to analyze: {text}"""
# Request LLM analysis
response = await ctx.sample(prompt)
# Process the LLM's response
sentiment = response.text.strip().lower()
# Map to standard sentiment values
if "positive" in sentiment:
sentiment = "positive"
elif "negative" in sentiment:
sentiment = "negative"
else:
sentiment = "neutral"
return {"text": text, "sentiment": sentiment}
```
## Method Signature
<Card icon="code" title="Context Sampling Method">
<ResponseField name="ctx.sample" type="async method">
Request text generation from the client's LLM
<Expandable title="parameters">
<ResponseField name="messages" type="str | list[str | SamplingMessage]">
A string or list of strings/message objects to send to the LLM
</ResponseField>
<ResponseField name="system_prompt" type="str | None" default="None">
Optional system prompt to guide the LLM's behavior
</ResponseField>
<ResponseField name="temperature" type="float | None" default="None">
Optional sampling temperature (controls randomness, typically 0.0-1.0)
</ResponseField>
<ResponseField name="max_tokens" type="int | None" default="512">
Optional maximum number of tokens to generate
</ResponseField>
<ResponseField name="model_preferences" type="ModelPreferences | str | list[str] | None" default="None">
Optional model selection preferences (e.g., model hint string, list of hints, or ModelPreferences object)
</ResponseField>
</Expandable>
<Expandable title="returns">
<ResponseField name="response" type="TextContent | ImageContent">
The LLM's response content (typically TextContent with a .text attribute)
</ResponseField>
</Expandable>
</ResponseField>
</Card>
## Simple Text Generation
### Basic Prompting
Generate text with simple string prompts:
```python {6}
@mcp.tool
async def generate_summary(content: str, ctx: Context) -> str:
"""Generate a summary of the provided content."""
prompt = f"Please provide a concise summary of the following content:\n\n{content}"
response = await ctx.sample(prompt)
return response.text
```
### System Prompt
Use system prompts to guide the LLM's behavior:
```python {4-9}
@mcp.tool
async def generate_code_example(concept: str, ctx: Context) -> str:
"""Generate a Python code example for a given concept."""
response = await ctx.sample(
messages=f"Write a simple Python code example demonstrating '{concept}'.",
system_prompt="You are an expert Python programmer. Provide concise, working code examples without explanations.",
temperature=0.7,
max_tokens=300
)
code_example = response.text
return f"```python\n{code_example}\n```"
```
### Model Preferences
Specify model preferences for different use cases:
```python {4-8, 17-22}
@mcp.tool
async def creative_writing(topic: str, ctx: Context) -> str:
"""Generate creative content using a specific model."""
response = await ctx.sample(
messages=f"Write a creative short story about {topic}",
model_preferences="claude-3-sonnet", # Prefer a specific model
temperature=0.9, # High creativity
max_tokens=1000
)
return response.text
@mcp.tool
async def technical_analysis(data: str, ctx: Context) -> str:
"""Perform technical analysis with a reasoning-focused model."""
response = await ctx.sample(
messages=f"Analyze this technical data and provide insights: {data}",
model_preferences=["claude-3-opus", "gpt-4"], # Prefer reasoning models
temperature=0.2, # Low randomness for consistency
max_tokens=800
)
return response.text
```
### Complex Message Structures
Use structured messages for more complex interactions:
```python {1, 6-10}
from fastmcp.client.sampling import SamplingMessage
@mcp.tool
async def multi_turn_analysis(user_query: str, context_data: str, ctx: Context) -> str:
"""Perform analysis using multi-turn conversation structure."""
messages = [
SamplingMessage(role="user", content=f"I have this data: {context_data}"),
SamplingMessage(role="assistant", content="I can see your data. What would you like me to analyze?"),
SamplingMessage(role="user", content=user_query)
]
response = await ctx.sample(
messages=messages,
system_prompt="You are a data analyst. Provide detailed insights based on the conversation context.",
temperature=0.3
)
return response.text
```
## Client Requirements
LLM sampling requires client support:
- Clients must implement sampling handlers to process requests
- If the client doesn't support sampling, calls to `ctx.sample()` will fail
- See [Client Sampling](/clients/sampling) for details on implementing client-side sampling handlers