Spaces:
Running
Running
Jeremiah Lowin commited on
Commit ·
1a68f2c
1
Parent(s): fb12cad
Update documentation
Browse files- docs/docs.json +12 -2
- docs/servers/context.mdx +59 -301
- docs/servers/elicitation.mdx +303 -0
- docs/servers/logging.mdx +194 -0
- docs/servers/progress.mdx +189 -0
- docs/servers/sampling.mdx +190 -0
docs/docs.json
CHANGED
|
@@ -69,8 +69,18 @@
|
|
| 69 |
"pages": [
|
| 70 |
"servers/tools",
|
| 71 |
"servers/resources",
|
| 72 |
-
"servers/prompts"
|
| 73 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
]
|
| 75 |
},
|
| 76 |
{
|
|
|
|
| 69 |
"pages": [
|
| 70 |
"servers/tools",
|
| 71 |
"servers/resources",
|
| 72 |
+
"servers/prompts"
|
| 73 |
+
]
|
| 74 |
+
},
|
| 75 |
+
{
|
| 76 |
+
"group": "Advanced Features",
|
| 77 |
+
"icon": "stars",
|
| 78 |
+
"pages": [
|
| 79 |
+
"servers/context",
|
| 80 |
+
"servers/elicitation",
|
| 81 |
+
"servers/logging",
|
| 82 |
+
"servers/progress",
|
| 83 |
+
"servers/sampling"
|
| 84 |
]
|
| 85 |
},
|
| 86 |
{
|
docs/servers/context.mdx
CHANGED
|
@@ -6,7 +6,7 @@ icon: rectangle-code
|
|
| 6 |
---
|
| 7 |
import { VersionBadge } from '/snippets/version-badge.mdx'
|
| 8 |
|
| 9 |
-
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.
|
| 10 |
|
| 11 |
## What Is Context?
|
| 12 |
|
|
@@ -37,10 +37,10 @@ To use the context object within any of your functions, simply add a parameter t
|
|
| 37 |
|
| 38 |
#### Tools
|
| 39 |
|
| 40 |
-
```python
|
| 41 |
from fastmcp import FastMCP, Context
|
| 42 |
|
| 43 |
-
mcp = FastMCP(name="
|
| 44 |
|
| 45 |
@mcp.tool
|
| 46 |
async def process_file(file_uri: str, ctx: Context) -> str:
|
|
@@ -53,7 +53,11 @@ async def process_file(file_uri: str, ctx: Context) -> str:
|
|
| 53 |
|
| 54 |
<VersionBadge version="2.2.5" />
|
| 55 |
|
| 56 |
-
```python
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
@mcp.resource("resource://user-data")
|
| 58 |
async def get_user_data(ctx: Context) -> dict:
|
| 59 |
"""Fetch personalized user data based on the request context."""
|
|
@@ -71,7 +75,11 @@ async def get_user_profile(user_id: str, ctx: Context) -> dict:
|
|
| 71 |
|
| 72 |
<VersionBadge version="2.2.5" />
|
| 73 |
|
| 74 |
-
```python
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
@mcp.prompt
|
| 76 |
async def data_analysis_request(dataset: str, ctx: Context) -> str:
|
| 77 |
"""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
|
|
| 89 |
FastMCP provides dependency functions that allow you to retrieve the active context from anywhere within a server request's execution flow:
|
| 90 |
|
| 91 |
```python {2,9}
|
| 92 |
-
from fastmcp import FastMCP
|
| 93 |
from fastmcp.server.dependencies import get_context
|
| 94 |
|
| 95 |
-
mcp = FastMCP(name="
|
| 96 |
|
| 97 |
# Utility function that needs context but doesn't receive it as a parameter
|
| 98 |
async def process_data(data: list[float]) -> dict:
|
|
@@ -114,279 +122,71 @@ async def analyze_dataset(dataset_name: str) -> dict:
|
|
| 114 |
|
| 115 |
## Context Capabilities
|
| 116 |
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
Send log messages back to the MCP client. This is useful for debugging and providing visibility into function execution during a request.
|
| 120 |
-
|
| 121 |
-
```python
|
| 122 |
-
@mcp.tool
|
| 123 |
-
async def analyze_data(data: list[float], ctx: Context) -> dict:
|
| 124 |
-
"""Analyze numerical data with logging."""
|
| 125 |
-
await ctx.debug("Starting analysis of numerical data")
|
| 126 |
-
await ctx.info(f"Analyzing {len(data)} data points")
|
| 127 |
-
|
| 128 |
-
try:
|
| 129 |
-
result = sum(data) / len(data)
|
| 130 |
-
await ctx.info(f"Analysis complete, average: {result}")
|
| 131 |
-
return {"average": result, "count": len(data)}
|
| 132 |
-
except ZeroDivisionError:
|
| 133 |
-
await ctx.warning("Empty data list provided")
|
| 134 |
-
return {"error": "Empty data list"}
|
| 135 |
-
except Exception as e:
|
| 136 |
-
await ctx.error(f"Analysis failed: {str(e)}")
|
| 137 |
-
raise
|
| 138 |
-
```
|
| 139 |
-
|
| 140 |
-
**Available Logging Methods:**
|
| 141 |
|
| 142 |
-
|
| 143 |
-
- **`ctx.info(message: str)`**: General information about execution
|
| 144 |
-
- **`ctx.warning(message: str)`**: Potential issues that didn't prevent execution
|
| 145 |
-
- **`ctx.error(message: str)`**: Errors that occurred during execution
|
| 146 |
-
- **`ctx.log(level: Literal["debug", "info", "warning", "error"], message: str, logger_name: str | None = None)`**: Generic log method supporting custom logger names
|
| 147 |
-
|
| 148 |
-
### Progress Reporting
|
| 149 |
|
| 150 |
-
|
| 151 |
|
| 152 |
```python
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
results = []
|
| 158 |
-
|
| 159 |
-
for i, item in enumerate(items):
|
| 160 |
-
# Report progress as percentage
|
| 161 |
-
await ctx.report_progress(progress=i, total=total)
|
| 162 |
-
|
| 163 |
-
# Process the item (simulated with a sleep)
|
| 164 |
-
await asyncio.sleep(0.1)
|
| 165 |
-
results.append(item.upper())
|
| 166 |
-
|
| 167 |
-
# Report 100% completion
|
| 168 |
-
await ctx.report_progress(progress=total, total=total)
|
| 169 |
-
|
| 170 |
-
return {"processed": len(results), "results": results}
|
| 171 |
```
|
| 172 |
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
- **`ctx.report_progress(progress: float, total: float | None = None)`**
|
| 176 |
-
- `progress`: Current progress value (e.g., 24)
|
| 177 |
-
- `total`: Optional total value (e.g., 100). If provided, clients may interpret this as a percentage.
|
| 178 |
-
|
| 179 |
-
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.
|
| 180 |
|
| 181 |
-
|
| 182 |
|
| 183 |
-
|
| 184 |
|
| 185 |
```python
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
# Read the document content
|
| 190 |
-
content_list = await ctx.read_resource(document_uri)
|
| 191 |
-
|
| 192 |
-
if not content_list:
|
| 193 |
-
return "Document is empty"
|
| 194 |
-
|
| 195 |
-
document_text = content_list[0].content
|
| 196 |
-
|
| 197 |
-
# Example: Generate a simple summary (length-based)
|
| 198 |
-
words = document_text.split()
|
| 199 |
-
total_words = len(words)
|
| 200 |
-
|
| 201 |
-
await ctx.info(f"Document has {total_words} words")
|
| 202 |
-
|
| 203 |
-
# Return a simple summary
|
| 204 |
-
if total_words > 100:
|
| 205 |
-
summary = " ".join(words[:100]) + "..."
|
| 206 |
-
return f"Summary ({total_words} words total): {summary}"
|
| 207 |
-
else:
|
| 208 |
-
return f"Full document ({total_words} words): {document_text}"
|
| 209 |
```
|
| 210 |
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
- **`ctx.read_resource(uri: str | AnyUrl) -> list[ReadResourceContents]`**
|
| 214 |
-
- `uri`: The resource URI to read
|
| 215 |
-
- Returns a list of resource content parts (usually containing just one item)
|
| 216 |
-
|
| 217 |
-
The returned content is typically accessed via `content_list[0].content` and can be text or binary data depending on the resource.
|
| 218 |
|
| 219 |
### LLM Sampling
|
| 220 |
|
| 221 |
<VersionBadge version="2.0.0" />
|
| 222 |
|
| 223 |
-
Request the client's LLM to generate text based on provided messages
|
| 224 |
-
|
| 225 |
-
```python
|
| 226 |
-
@mcp.tool
|
| 227 |
-
async def analyze_sentiment(text: str, ctx: Context) -> dict:
|
| 228 |
-
"""Analyze the sentiment of a text using the client's LLM."""
|
| 229 |
-
# Create a sampling prompt asking for sentiment analysis
|
| 230 |
-
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}"
|
| 231 |
-
|
| 232 |
-
# Send the sampling request to the client's LLM (provide a hint for the model you want to use)
|
| 233 |
-
response = await ctx.sample(prompt, model_preferences="claude-3-sonnet")
|
| 234 |
-
|
| 235 |
-
# Process the LLM's response
|
| 236 |
-
sentiment = response.text.strip().lower()
|
| 237 |
-
|
| 238 |
-
# Map to standard sentiment values
|
| 239 |
-
if "positive" in sentiment:
|
| 240 |
-
sentiment = "positive"
|
| 241 |
-
elif "negative" in sentiment:
|
| 242 |
-
sentiment = "negative"
|
| 243 |
-
else:
|
| 244 |
-
sentiment = "neutral"
|
| 245 |
-
|
| 246 |
-
return {"text": text, "sentiment": sentiment}
|
| 247 |
-
```
|
| 248 |
-
|
| 249 |
-
**Method signature:**
|
| 250 |
-
|
| 251 |
-
- **`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`**
|
| 252 |
-
- `messages`: A string or list of strings/message objects to send to the LLM
|
| 253 |
-
- `system_prompt`: Optional system prompt to guide the LLM's behavior
|
| 254 |
-
- `temperature`: Optional sampling temperature (controls randomness)
|
| 255 |
-
- `max_tokens`: Optional maximum number of tokens to generate (defaults to 512)
|
| 256 |
-
- `model_preferences`: Optional model selection preferences (e.g., a model hint string, list of hints, or a ModelPreferences object)
|
| 257 |
-
- Returns the LLM's response as TextContent or ImageContent
|
| 258 |
-
|
| 259 |
-
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.
|
| 260 |
|
| 261 |
```python
|
| 262 |
-
|
| 263 |
-
async def generate_example(concept: str, ctx: Context) -> str:
|
| 264 |
-
"""Generate a Python code example for a given concept."""
|
| 265 |
-
# Using a system prompt and a user message
|
| 266 |
-
response = await ctx.sample(
|
| 267 |
-
messages=f"Write a simple Python code example demonstrating '{concept}'.",
|
| 268 |
-
system_prompt="You are an expert Python programmer. Provide concise, working code examples without explanations.",
|
| 269 |
-
temperature=0.7,
|
| 270 |
-
max_tokens=300
|
| 271 |
-
)
|
| 272 |
-
|
| 273 |
-
code_example = response.text
|
| 274 |
-
return f"```python\n{code_example}\n```"
|
| 275 |
```
|
| 276 |
|
| 277 |
-
See [
|
| 278 |
|
| 279 |
-
### User Elicitation
|
| 280 |
|
| 281 |
-
|
| 282 |
|
| 283 |
-
|
| 284 |
|
| 285 |
```python
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
@dataclass
|
| 289 |
-
class UserInfo:
|
| 290 |
-
name: str
|
| 291 |
-
age: int
|
| 292 |
-
|
| 293 |
-
@mcp.tool
|
| 294 |
-
async def collect_user_info(ctx: Context) -> str:
|
| 295 |
-
"""Collect user information through interactive prompts."""
|
| 296 |
-
# Request structured user information
|
| 297 |
-
result = await ctx.elicit(
|
| 298 |
-
message="Please provide your information",
|
| 299 |
-
response_type=UserInfo
|
| 300 |
-
)
|
| 301 |
-
|
| 302 |
-
if result.action == "accept":
|
| 303 |
-
user = result.data
|
| 304 |
-
return f"Hello {user.name}, you are {user.age} years old"
|
| 305 |
-
elif result.action == "decline":
|
| 306 |
-
return "Information not provided"
|
| 307 |
-
else: # cancel
|
| 308 |
-
return "Operation cancelled"
|
| 309 |
```
|
| 310 |
|
| 311 |
-
|
| 312 |
|
| 313 |
-
|
| 314 |
-
- `message`: The prompt message to display to the user
|
| 315 |
-
- `response_type`: The Python type defining the expected response structure (dataclass, primitive type, etc.)
|
| 316 |
-
- Returns an `ElicitationResult` with `action` ("accept", "decline", "cancel") and `data` (when accepted)
|
| 317 |
-
|
| 318 |
-
**Supported Response Types:**
|
| 319 |
|
| 320 |
-
|
| 321 |
-
- **Literal types**: `Literal["option1", "option2"]` for constrained choices
|
| 322 |
-
- **Enum types**: Python enums for predefined options
|
| 323 |
-
- **Dataclass types**: Custom structured data with multiple fields
|
| 324 |
|
| 325 |
```python
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
class Priority(Enum):
|
| 330 |
-
LOW = "low"
|
| 331 |
-
MEDIUM = "medium"
|
| 332 |
-
HIGH = "high"
|
| 333 |
-
|
| 334 |
-
@dataclass
|
| 335 |
-
class TaskInfo:
|
| 336 |
-
title: str
|
| 337 |
-
priority: Priority
|
| 338 |
-
urgent: bool
|
| 339 |
-
|
| 340 |
-
@mcp.tool
|
| 341 |
-
async def create_task(ctx: Context) -> str:
|
| 342 |
-
"""Create a task with user-provided details."""
|
| 343 |
-
# Multiple elicitation calls for different information
|
| 344 |
-
|
| 345 |
-
# Simple string input
|
| 346 |
-
title_result = await ctx.elicit("What's the task title?", response_type=str)
|
| 347 |
-
if title_result.action != "accept":
|
| 348 |
-
return "Task creation cancelled"
|
| 349 |
-
|
| 350 |
-
# Enum selection
|
| 351 |
-
priority_result = await ctx.elicit("What's the priority?", response_type=Priority)
|
| 352 |
-
if priority_result.action != "accept":
|
| 353 |
-
return "Task creation cancelled"
|
| 354 |
-
|
| 355 |
-
# Boolean choice
|
| 356 |
-
urgent_result = await ctx.elicit("Is this urgent?", response_type=bool)
|
| 357 |
-
if urgent_result.action != "accept":
|
| 358 |
-
return "Task creation cancelled"
|
| 359 |
-
|
| 360 |
-
return f"Created task: {title_result.data} (Priority: {priority_result.data.value}, Urgent: {urgent_result.data})"
|
| 361 |
```
|
| 362 |
|
| 363 |
-
**
|
| 364 |
-
|
| 365 |
-
FastMCP provides typed result classes for pattern matching:
|
| 366 |
-
|
| 367 |
-
```python
|
| 368 |
-
from fastmcp.server.elicitation import (
|
| 369 |
-
AcceptedElicitation,
|
| 370 |
-
DeclinedElicitation,
|
| 371 |
-
CancelledElicitation
|
| 372 |
-
)
|
| 373 |
-
|
| 374 |
-
@mcp.tool
|
| 375 |
-
async def pattern_example(ctx: Context) -> str:
|
| 376 |
-
result = await ctx.elicit("Enter your name:", response_type=str)
|
| 377 |
-
|
| 378 |
-
match result:
|
| 379 |
-
case AcceptedElicitation(data=name):
|
| 380 |
-
return f"Hello {name}!"
|
| 381 |
-
case DeclinedElicitation():
|
| 382 |
-
return "No name provided"
|
| 383 |
-
case CancelledElicitation():
|
| 384 |
-
return "Operation cancelled"
|
| 385 |
-
```
|
| 386 |
|
| 387 |
-
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.
|
| 388 |
|
| 389 |
-
###
|
| 390 |
|
| 391 |
<VersionBadge version="2.9.1" />
|
| 392 |
|
|
@@ -405,7 +205,19 @@ async def custom_tool_management(ctx: Context) -> str:
|
|
| 405 |
|
| 406 |
These methods are primarily used internally by FastMCP's automatic notification system and most users will not need to invoke them directly.
|
| 407 |
|
| 408 |
-
###
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 409 |
|
| 410 |
Access metadata about the current request and client.
|
| 411 |
|
|
@@ -425,60 +237,6 @@ async def request_info(ctx: Context) -> dict:
|
|
| 425 |
- **`ctx.client_id -> str | None`**: Get the ID of the client making the request, if provided during initialization
|
| 426 |
- **`ctx.session_id -> str | None`**: Get the MCP session ID for session-based data sharing (HTTP transports only)
|
| 427 |
|
| 428 |
-
### Advanced Access
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
#### FastMCP Server and Sessions
|
| 432 |
-
|
| 433 |
-
```python
|
| 434 |
-
@mcp.tool
|
| 435 |
-
async def advanced_tool(ctx: Context) -> str:
|
| 436 |
-
"""Demonstrate advanced context access."""
|
| 437 |
-
# Access the FastMCP server instance
|
| 438 |
-
server_name = ctx.fastmcp.name
|
| 439 |
-
|
| 440 |
-
# Low-level session access (rarely needed)
|
| 441 |
-
session = ctx.session
|
| 442 |
-
request_context = ctx.request_context
|
| 443 |
-
|
| 444 |
-
return f"Server: {server_name}"
|
| 445 |
-
```
|
| 446 |
-
|
| 447 |
-
#### HTTP Requests
|
| 448 |
-
|
| 449 |
-
<VersionBadge version="2.2.7" />
|
| 450 |
-
|
| 451 |
-
<Warning>
|
| 452 |
-
The `ctx.get_http_request()` method is deprecated and will be removed in a future version.
|
| 453 |
-
Please use the `get_http_request()` dependency function instead.
|
| 454 |
-
See the [HTTP Requests pattern](/patterns/http-requests) for more details.
|
| 455 |
-
</Warning>
|
| 456 |
-
|
| 457 |
-
For web applications, you can access the underlying HTTP request:
|
| 458 |
-
|
| 459 |
-
```python
|
| 460 |
-
@mcp.tool
|
| 461 |
-
async def handle_web_request(ctx: Context) -> dict:
|
| 462 |
-
"""Access HTTP request information from the Starlette request."""
|
| 463 |
-
request = ctx.get_http_request()
|
| 464 |
-
|
| 465 |
-
# Access HTTP headers, query parameters, etc.
|
| 466 |
-
user_agent = request.headers.get("user-agent", "Unknown")
|
| 467 |
-
client_ip = request.client.host if request.client else "Unknown"
|
| 468 |
-
|
| 469 |
-
return {
|
| 470 |
-
"user_agent": user_agent,
|
| 471 |
-
"client_ip": client_ip,
|
| 472 |
-
"path": request.url.path,
|
| 473 |
-
}
|
| 474 |
-
```
|
| 475 |
-
|
| 476 |
-
#### Advanced Properties Reference
|
| 477 |
-
|
| 478 |
-
- **`ctx.fastmcp -> FastMCP`**: Access the server instance the context belongs to
|
| 479 |
-
- **`ctx.session`**: Access the raw `mcp.server.session.ServerSession` object
|
| 480 |
-
- **`ctx.request_context`**: Access the raw `mcp.shared.context.RequestContext` object
|
| 481 |
-
|
| 482 |
<Warning>
|
| 483 |
-
|
| 484 |
-
</Warning>
|
|
|
|
| 6 |
---
|
| 7 |
import { VersionBadge } from '/snippets/version-badge.mdx'
|
| 8 |
|
| 9 |
+
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.
|
| 10 |
|
| 11 |
## What Is Context?
|
| 12 |
|
|
|
|
| 37 |
|
| 38 |
#### Tools
|
| 39 |
|
| 40 |
+
```python {1, 6}
|
| 41 |
from fastmcp import FastMCP, Context
|
| 42 |
|
| 43 |
+
mcp = FastMCP(name="Context Demo")
|
| 44 |
|
| 45 |
@mcp.tool
|
| 46 |
async def process_file(file_uri: str, ctx: Context) -> str:
|
|
|
|
| 53 |
|
| 54 |
<VersionBadge version="2.2.5" />
|
| 55 |
|
| 56 |
+
```python {1, 6, 12}
|
| 57 |
+
from fastmcp import FastMCP, Context
|
| 58 |
+
|
| 59 |
+
mcp = FastMCP(name="Context Demo")
|
| 60 |
+
|
| 61 |
@mcp.resource("resource://user-data")
|
| 62 |
async def get_user_data(ctx: Context) -> dict:
|
| 63 |
"""Fetch personalized user data based on the request context."""
|
|
|
|
| 75 |
|
| 76 |
<VersionBadge version="2.2.5" />
|
| 77 |
|
| 78 |
+
```python {1, 6}
|
| 79 |
+
from fastmcp import FastMCP, Context
|
| 80 |
+
|
| 81 |
+
mcp = FastMCP(name="Context Demo")
|
| 82 |
+
|
| 83 |
@mcp.prompt
|
| 84 |
async def data_analysis_request(dataset: str, ctx: Context) -> str:
|
| 85 |
"""Generate a request to analyze data with contextual information."""
|
|
|
|
| 97 |
FastMCP provides dependency functions that allow you to retrieve the active context from anywhere within a server request's execution flow:
|
| 98 |
|
| 99 |
```python {2,9}
|
| 100 |
+
from fastmcp import FastMCP
|
| 101 |
from fastmcp.server.dependencies import get_context
|
| 102 |
|
| 103 |
+
mcp = FastMCP(name="Dependency Demo")
|
| 104 |
|
| 105 |
# Utility function that needs context but doesn't receive it as a parameter
|
| 106 |
async def process_data(data: list[float]) -> dict:
|
|
|
|
| 122 |
|
| 123 |
## Context Capabilities
|
| 124 |
|
| 125 |
+
FastMCP provides several advanced capabilities through the context object. Each capability has dedicated documentation with comprehensive examples and best practices:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
|
| 127 |
+
### Logging
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
|
| 129 |
+
Send debug, info, warning, and error messages back to the MCP client for visibility into function execution.
|
| 130 |
|
| 131 |
```python
|
| 132 |
+
await ctx.debug("Starting analysis")
|
| 133 |
+
await ctx.info(f"Processing {len(data)} items")
|
| 134 |
+
await ctx.warning("Deprecated parameter used")
|
| 135 |
+
await ctx.error("Processing failed")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
```
|
| 137 |
|
| 138 |
+
See [Server Logging](/servers/logging) for complete documentation and examples.
|
| 139 |
+
### Client Elicitation
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
|
| 141 |
+
<VersionBadge version="2.10.0" />
|
| 142 |
|
| 143 |
+
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.
|
| 144 |
|
| 145 |
```python
|
| 146 |
+
result = await ctx.elicit("Enter your name:", response_type=str)
|
| 147 |
+
if result.action == "accept":
|
| 148 |
+
name = result.data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
```
|
| 150 |
|
| 151 |
+
See [User Elicitation](/servers/elicitation) for detailed examples and supported response types.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
|
| 153 |
### LLM Sampling
|
| 154 |
|
| 155 |
<VersionBadge version="2.0.0" />
|
| 156 |
|
| 157 |
+
Request the client's LLM to generate text based on provided messages, useful for leveraging AI capabilities within your tools.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
|
| 159 |
```python
|
| 160 |
+
response = await ctx.sample("Analyze this data", temperature=0.7)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
```
|
| 162 |
|
| 163 |
+
See [LLM Sampling](/servers/sampling) for comprehensive usage and advanced techniques.
|
| 164 |
|
|
|
|
| 165 |
|
| 166 |
+
### Progress Reporting
|
| 167 |
|
| 168 |
+
Update clients on the progress of long-running operations, enabling progress indicators and better user experience.
|
| 169 |
|
| 170 |
```python
|
| 171 |
+
await ctx.report_progress(progress=50, total=100) # 50% complete
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
```
|
| 173 |
|
| 174 |
+
See [Progress Reporting](/servers/progress) for detailed patterns and examples.
|
| 175 |
|
| 176 |
+
### Resource Access
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
|
| 178 |
+
Read data from resources registered with your FastMCP server, allowing access to files, configuration, or dynamic content.
|
|
|
|
|
|
|
|
|
|
| 179 |
|
| 180 |
```python
|
| 181 |
+
content_list = await ctx.read_resource("resource://config")
|
| 182 |
+
content = content_list[0].content
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
```
|
| 184 |
|
| 185 |
+
**Method signature:**
|
| 186 |
+
- **`ctx.read_resource(uri: str | AnyUrl) -> list[ReadResourceContents]`**: Returns a list of resource content parts
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
|
|
|
|
| 188 |
|
| 189 |
+
### Change Notifications
|
| 190 |
|
| 191 |
<VersionBadge version="2.9.1" />
|
| 192 |
|
|
|
|
| 205 |
|
| 206 |
These methods are primarily used internally by FastMCP's automatic notification system and most users will not need to invoke them directly.
|
| 207 |
|
| 208 |
+
### FastMCP Server
|
| 209 |
+
|
| 210 |
+
To access the underlying FastMCP server instance, you can use the `ctx.fastmcp` property:
|
| 211 |
+
|
| 212 |
+
```python
|
| 213 |
+
@mcp.tool
|
| 214 |
+
async def my_tool(ctx: Context) -> None:
|
| 215 |
+
# Access the FastMCP server instance
|
| 216 |
+
server_name = ctx.fastmcp.name
|
| 217 |
+
...
|
| 218 |
+
```
|
| 219 |
+
|
| 220 |
+
### MCP Request
|
| 221 |
|
| 222 |
Access metadata about the current request and client.
|
| 223 |
|
|
|
|
| 237 |
- **`ctx.client_id -> str | None`**: Get the ID of the client making the request, if provided during initialization
|
| 238 |
- **`ctx.session_id -> str | None`**: Get the MCP session ID for session-based data sharing (HTTP transports only)
|
| 239 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
<Warning>
|
| 241 |
+
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.
|
| 242 |
+
</Warning>
|
docs/servers/elicitation.mdx
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: User Elicitation
|
| 3 |
+
sidebarTitle: Elicitation
|
| 4 |
+
description: Request structured input from users during tool execution through the MCP context.
|
| 5 |
+
icon: user-check
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
import { VersionBadge } from '/snippets/version-badge.mdx'
|
| 9 |
+
|
| 10 |
+
<VersionBadge version="2.10.0" />
|
| 11 |
+
|
| 12 |
+
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.
|
| 13 |
+
|
| 14 |
+
<Tip>
|
| 15 |
+
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.
|
| 16 |
+
</Tip>
|
| 17 |
+
|
| 18 |
+
## What is Elicitation?
|
| 19 |
+
|
| 20 |
+
Elicitation enables tools to pause execution and request specific information from users. This is particularly useful for:
|
| 21 |
+
|
| 22 |
+
- **Missing parameters**: Ask for required information not provided initially
|
| 23 |
+
- **Clarification requests**: Get user confirmation or choices for ambiguous scenarios
|
| 24 |
+
- **Progressive disclosure**: Collect complex information step-by-step
|
| 25 |
+
- **Dynamic workflows**: Adapt tool behavior based on user responses
|
| 26 |
+
|
| 27 |
+
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?"
|
| 28 |
+
|
| 29 |
+
### Basic Usage
|
| 30 |
+
|
| 31 |
+
Use the `ctx.elicit()` method within any tool function to request user input:
|
| 32 |
+
|
| 33 |
+
```python {14-17}
|
| 34 |
+
from fastmcp import FastMCP, Context
|
| 35 |
+
from dataclasses import dataclass
|
| 36 |
+
|
| 37 |
+
mcp = FastMCP("Elicitation Server")
|
| 38 |
+
|
| 39 |
+
@dataclass
|
| 40 |
+
class UserInfo:
|
| 41 |
+
name: str
|
| 42 |
+
age: int
|
| 43 |
+
|
| 44 |
+
@mcp.tool
|
| 45 |
+
async def collect_user_info(ctx: Context) -> str:
|
| 46 |
+
"""Collect user information through interactive prompts."""
|
| 47 |
+
result = await ctx.elicit(
|
| 48 |
+
message="Please provide your information",
|
| 49 |
+
response_type=UserInfo
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
if result.action == "accept":
|
| 53 |
+
user = result.data
|
| 54 |
+
return f"Hello {user.name}, you are {user.age} years old"
|
| 55 |
+
elif result.action == "decline":
|
| 56 |
+
return "Information not provided"
|
| 57 |
+
else: # cancel
|
| 58 |
+
return "Operation cancelled"
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
## Method Signature
|
| 62 |
+
|
| 63 |
+
<Card icon="code" title="Context Elicitation Method">
|
| 64 |
+
<ResponseField name="ctx.elicit" type="async method">
|
| 65 |
+
<Expandable title="Parameters">
|
| 66 |
+
<ResponseField name="message" type="str">
|
| 67 |
+
The prompt message to display to the user
|
| 68 |
+
</ResponseField>
|
| 69 |
+
|
| 70 |
+
<ResponseField name="response_type" type="type" default="str">
|
| 71 |
+
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.
|
| 72 |
+
</ResponseField>
|
| 73 |
+
</Expandable>
|
| 74 |
+
|
| 75 |
+
<Expandable title="Response">
|
| 76 |
+
<ResponseField name="ElicitationResult" type="object">
|
| 77 |
+
Result object containing the user's response
|
| 78 |
+
|
| 79 |
+
<Expandable title="properties">
|
| 80 |
+
<ResponseField name="action" type="Literal['accept', 'decline', 'cancel']">
|
| 81 |
+
How the user responded to the request
|
| 82 |
+
</ResponseField>
|
| 83 |
+
|
| 84 |
+
<ResponseField name="data" type="response_type | None">
|
| 85 |
+
The user's input data (only present when action is "accept")
|
| 86 |
+
</ResponseField>
|
| 87 |
+
</Expandable>
|
| 88 |
+
</ResponseField>
|
| 89 |
+
</Expandable>
|
| 90 |
+
</ResponseField>
|
| 91 |
+
</Card>
|
| 92 |
+
|
| 93 |
+
## Elicitation Actions
|
| 94 |
+
|
| 95 |
+
The elicitation result contains an `action` field indicating how the user responded:
|
| 96 |
+
|
| 97 |
+
- **`accept`**: User provided valid input - data is available in the `data` field
|
| 98 |
+
- **`decline`**: User chose not to provide the requested information and the data field is `None`
|
| 99 |
+
- **`cancel`**: User cancelled the entire operation and the data field is `None`
|
| 100 |
+
|
| 101 |
+
```python {5, 7}
|
| 102 |
+
@mcp.tool
|
| 103 |
+
async def my_tool(ctx: Context) -> str:
|
| 104 |
+
result = await ctx.elicit("Choose an action")
|
| 105 |
+
|
| 106 |
+
if result.action == "accept":
|
| 107 |
+
return "Accepted!"
|
| 108 |
+
elif result.action == "decline":
|
| 109 |
+
return "Declined!"
|
| 110 |
+
else:
|
| 111 |
+
return "Cancelled!"
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
FastMCP also provides typed result classes for pattern matching on the `action` field:
|
| 115 |
+
|
| 116 |
+
```python {1-5, 12, 14, 16}
|
| 117 |
+
from fastmcp.server.elicitation import (
|
| 118 |
+
AcceptedElicitation,
|
| 119 |
+
DeclinedElicitation,
|
| 120 |
+
CancelledElicitation,
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
@mcp.tool
|
| 124 |
+
async def pattern_example(ctx: Context) -> str:
|
| 125 |
+
result = await ctx.elicit("Enter your name:", response_type=str)
|
| 126 |
+
|
| 127 |
+
match result:
|
| 128 |
+
case AcceptedElicitation(data=name):
|
| 129 |
+
return f"Hello {name}!"
|
| 130 |
+
case DeclinedElicitation():
|
| 131 |
+
return "No name provided"
|
| 132 |
+
case CancelledElicitation():
|
| 133 |
+
return "Operation cancelled"
|
| 134 |
+
```
|
| 135 |
+
|
| 136 |
+
## Response Types
|
| 137 |
+
|
| 138 |
+
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.
|
| 139 |
+
|
| 140 |
+
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.
|
| 141 |
+
|
| 142 |
+
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.
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
### Scalar Types
|
| 146 |
+
|
| 147 |
+
You can request simple scalar data types for basic input, such as a string, integer, or boolean.
|
| 148 |
+
|
| 149 |
+
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.
|
| 150 |
+
|
| 151 |
+
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.
|
| 152 |
+
|
| 153 |
+
<CodeGroup>
|
| 154 |
+
```python {4} title="Request a string"
|
| 155 |
+
@mcp.tool
|
| 156 |
+
async def get_user_name(ctx: Context) -> str:
|
| 157 |
+
"""Get the user's name."""
|
| 158 |
+
result = await ctx.elicit("What's your name?", response_type=str)
|
| 159 |
+
|
| 160 |
+
if result.action == "accept":
|
| 161 |
+
return f"Hello, {result.data}!"
|
| 162 |
+
return "No name provided"
|
| 163 |
+
```
|
| 164 |
+
```python {4} title="Request an integer"
|
| 165 |
+
@mcp.tool
|
| 166 |
+
async def pick_a_number(ctx: Context) -> str:
|
| 167 |
+
"""Pick a number."""
|
| 168 |
+
result = await ctx.elicit("Pick a number!", response_type=int)
|
| 169 |
+
|
| 170 |
+
if result.action == "accept":
|
| 171 |
+
return f"You picked {result.data}"
|
| 172 |
+
return "No number provided"
|
| 173 |
+
```
|
| 174 |
+
```python {4} title="Request a boolean"
|
| 175 |
+
@mcp.tool
|
| 176 |
+
async def pick_a_boolean(ctx: Context) -> str:
|
| 177 |
+
"""Pick a boolean."""
|
| 178 |
+
result = await ctx.elicit("True or false?", response_type=bool)
|
| 179 |
+
|
| 180 |
+
if result.action == "accept":
|
| 181 |
+
return f"You picked {result.data}"
|
| 182 |
+
return "No boolean provided"
|
| 183 |
+
```
|
| 184 |
+
</CodeGroup>
|
| 185 |
+
|
| 186 |
+
### Constrained Options
|
| 187 |
+
|
| 188 |
+
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.
|
| 189 |
+
|
| 190 |
+
<CodeGroup>
|
| 191 |
+
```python {6} title="Using a list of strings"
|
| 192 |
+
@mcp.tool
|
| 193 |
+
async def set_priority(ctx: Context) -> str:
|
| 194 |
+
"""Set task priority level."""
|
| 195 |
+
result = await ctx.elicit(
|
| 196 |
+
"What priority level?",
|
| 197 |
+
response_type=["low", "medium", "high"],
|
| 198 |
+
)
|
| 199 |
+
|
| 200 |
+
if result.action == "accept":
|
| 201 |
+
return f"Priority set to: {result.data}"
|
| 202 |
+
```
|
| 203 |
+
```python {1, 8} title="Using a Literal type"
|
| 204 |
+
from typing import Literal
|
| 205 |
+
|
| 206 |
+
@mcp.tool
|
| 207 |
+
async def set_priority(ctx: Context) -> str:
|
| 208 |
+
"""Set task priority level."""
|
| 209 |
+
result = await ctx.elicit(
|
| 210 |
+
"What priority level?",
|
| 211 |
+
response_type=Literal["low", "medium", "high"]
|
| 212 |
+
)
|
| 213 |
+
|
| 214 |
+
if result.action == "accept":
|
| 215 |
+
return f"Priority set to: {result.data}"
|
| 216 |
+
return "No priority set"
|
| 217 |
+
```
|
| 218 |
+
```python {1, 11} title="Using a Python enum"
|
| 219 |
+
from enum import Enum
|
| 220 |
+
|
| 221 |
+
class Priority(Enum):
|
| 222 |
+
LOW = "low"
|
| 223 |
+
MEDIUM = "medium"
|
| 224 |
+
HIGH = "high"
|
| 225 |
+
|
| 226 |
+
@mcp.tool
|
| 227 |
+
async def set_priority(ctx: Context) -> str:
|
| 228 |
+
"""Set task priority level."""
|
| 229 |
+
result = await ctx.elicit("What priority level?", response_type=Priority)
|
| 230 |
+
|
| 231 |
+
if result.action == "accept":
|
| 232 |
+
return f"Priority set to: {result.data.value}"
|
| 233 |
+
return "No priority set"
|
| 234 |
+
```
|
| 235 |
+
</CodeGroup>
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
### Structured Responses
|
| 239 |
+
|
| 240 |
+
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.
|
| 241 |
+
|
| 242 |
+
```python {1, 16, 20}
|
| 243 |
+
from dataclasses import dataclass
|
| 244 |
+
from typing import Literal
|
| 245 |
+
|
| 246 |
+
@dataclass
|
| 247 |
+
class TaskDetails:
|
| 248 |
+
title: str
|
| 249 |
+
description: str
|
| 250 |
+
priority: Literal["low", "medium", "high"]
|
| 251 |
+
due_date: str
|
| 252 |
+
|
| 253 |
+
@mcp.tool
|
| 254 |
+
async def create_task(ctx: Context) -> str:
|
| 255 |
+
"""Create a new task with user-provided details."""
|
| 256 |
+
result = await ctx.elicit(
|
| 257 |
+
"Please provide task details",
|
| 258 |
+
response_type=TaskDetails
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
if result.action == "accept":
|
| 262 |
+
task = result.data
|
| 263 |
+
return f"Created task: {task.title} (Priority: {task.priority})"
|
| 264 |
+
return "Task creation cancelled"
|
| 265 |
+
```
|
| 266 |
+
|
| 267 |
+
## Multi-Turn Elicitation
|
| 268 |
+
|
| 269 |
+
Tools can make multiple elicitation calls to gather information progressively:
|
| 270 |
+
|
| 271 |
+
```python {6, 11, 16-19}
|
| 272 |
+
@mcp.tool
|
| 273 |
+
async def plan_meeting(ctx: Context) -> str:
|
| 274 |
+
"""Plan a meeting by gathering details step by step."""
|
| 275 |
+
|
| 276 |
+
# Get meeting title
|
| 277 |
+
title_result = await ctx.elicit("What's the meeting title?", response_type=str)
|
| 278 |
+
if title_result.action != "accept":
|
| 279 |
+
return "Meeting planning cancelled"
|
| 280 |
+
|
| 281 |
+
# Get duration
|
| 282 |
+
duration_result = await ctx.elicit("Duration in minutes?", response_type=int)
|
| 283 |
+
if duration_result.action != "accept":
|
| 284 |
+
return "Meeting planning cancelled"
|
| 285 |
+
|
| 286 |
+
# Get priority
|
| 287 |
+
priority_result = await ctx.elicit(
|
| 288 |
+
"Is this urgent?",
|
| 289 |
+
response_type=Literal["yes", "no"]
|
| 290 |
+
)
|
| 291 |
+
if priority_result.action != "accept":
|
| 292 |
+
return "Meeting planning cancelled"
|
| 293 |
+
|
| 294 |
+
urgent = priority_result.data == "yes"
|
| 295 |
+
return f"Meeting '{title_result.data}' planned for {duration_result.data} minutes (Urgent: {urgent})"
|
| 296 |
+
```
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
## Client Requirements
|
| 300 |
+
|
| 301 |
+
Elicitation requires the client to implement an elicitation handler. See [Client Elicitation](/clients/elicitation) for details on how clients can handle these requests.
|
| 302 |
+
|
| 303 |
+
If a client doesn't support elicitation, calls to `ctx.elicit()` will raise an error indicating that elicitation is not supported.
|
docs/servers/logging.mdx
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Server Logging
|
| 3 |
+
sidebarTitle: Logging
|
| 4 |
+
description: Send log messages back to MCP clients through the context.
|
| 5 |
+
icon: receipt
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
import { VersionBadge } from '/snippets/version-badge.mdx'
|
| 9 |
+
|
| 10 |
+
<Tip>
|
| 11 |
+
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.
|
| 12 |
+
</Tip>
|
| 13 |
+
|
| 14 |
+
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.
|
| 15 |
+
|
| 16 |
+
## Why Use Server Logging?
|
| 17 |
+
|
| 18 |
+
Server logging is essential for:
|
| 19 |
+
|
| 20 |
+
- **Debugging**: Send detailed execution information to help diagnose issues
|
| 21 |
+
- **Progress visibility**: Keep users informed about what the tool is doing
|
| 22 |
+
- **Error reporting**: Communicate problems and their context to clients
|
| 23 |
+
- **Audit trails**: Create records of tool execution for compliance or analysis
|
| 24 |
+
|
| 25 |
+
Unlike standard Python logging, MCP server logging sends messages directly to the client, making them visible in the client's interface or logs.
|
| 26 |
+
|
| 27 |
+
### Basic Usage
|
| 28 |
+
|
| 29 |
+
Use the context logging methods within any tool function:
|
| 30 |
+
|
| 31 |
+
```python {8-9, 13, 17, 21}
|
| 32 |
+
from fastmcp import FastMCP, Context
|
| 33 |
+
|
| 34 |
+
mcp = FastMCP("LoggingDemo")
|
| 35 |
+
|
| 36 |
+
@mcp.tool
|
| 37 |
+
async def analyze_data(data: list[float], ctx: Context) -> dict:
|
| 38 |
+
"""Analyze numerical data with comprehensive logging."""
|
| 39 |
+
await ctx.debug("Starting analysis of numerical data")
|
| 40 |
+
await ctx.info(f"Analyzing {len(data)} data points")
|
| 41 |
+
|
| 42 |
+
try:
|
| 43 |
+
if not data:
|
| 44 |
+
await ctx.warning("Empty data list provided")
|
| 45 |
+
return {"error": "Empty data list"}
|
| 46 |
+
|
| 47 |
+
result = sum(data) / len(data)
|
| 48 |
+
await ctx.info(f"Analysis complete, average: {result}")
|
| 49 |
+
return {"average": result, "count": len(data)}
|
| 50 |
+
|
| 51 |
+
except Exception as e:
|
| 52 |
+
await ctx.error(f"Analysis failed: {str(e)}")
|
| 53 |
+
raise
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
## Logging Methods
|
| 57 |
+
|
| 58 |
+
<Card icon="code" title="Context Logging Methods">
|
| 59 |
+
<ResponseField name="ctx.debug" type="async method">
|
| 60 |
+
Send debug-level messages for detailed execution information
|
| 61 |
+
|
| 62 |
+
<Expandable title="parameters">
|
| 63 |
+
<ResponseField name="message" type="str">
|
| 64 |
+
The debug message to send to the client
|
| 65 |
+
</ResponseField>
|
| 66 |
+
</Expandable>
|
| 67 |
+
</ResponseField>
|
| 68 |
+
|
| 69 |
+
<ResponseField name="ctx.info" type="async method">
|
| 70 |
+
Send informational messages about normal execution
|
| 71 |
+
|
| 72 |
+
<Expandable title="parameters">
|
| 73 |
+
<ResponseField name="message" type="str">
|
| 74 |
+
The information message to send to the client
|
| 75 |
+
</ResponseField>
|
| 76 |
+
</Expandable>
|
| 77 |
+
</ResponseField>
|
| 78 |
+
|
| 79 |
+
<ResponseField name="ctx.warning" type="async method">
|
| 80 |
+
Send warning messages for potential issues that didn't prevent execution
|
| 81 |
+
|
| 82 |
+
<Expandable title="parameters">
|
| 83 |
+
<ResponseField name="message" type="str">
|
| 84 |
+
The warning message to send to the client
|
| 85 |
+
</ResponseField>
|
| 86 |
+
</Expandable>
|
| 87 |
+
</ResponseField>
|
| 88 |
+
|
| 89 |
+
<ResponseField name="ctx.error" type="async method">
|
| 90 |
+
Send error messages for problems that occurred during execution
|
| 91 |
+
|
| 92 |
+
<Expandable title="parameters">
|
| 93 |
+
<ResponseField name="message" type="str">
|
| 94 |
+
The error message to send to the client
|
| 95 |
+
</ResponseField>
|
| 96 |
+
</Expandable>
|
| 97 |
+
</ResponseField>
|
| 98 |
+
|
| 99 |
+
<ResponseField name="ctx.log" type="async method">
|
| 100 |
+
Generic logging method with custom level and logger name
|
| 101 |
+
|
| 102 |
+
<Expandable title="parameters">
|
| 103 |
+
<ResponseField name="level" type="Literal['debug', 'info', 'warning', 'error']">
|
| 104 |
+
The log level for the message
|
| 105 |
+
</ResponseField>
|
| 106 |
+
|
| 107 |
+
<ResponseField name="message" type="str">
|
| 108 |
+
The message to send to the client
|
| 109 |
+
</ResponseField>
|
| 110 |
+
|
| 111 |
+
<ResponseField name="logger_name" type="str | None" default="None">
|
| 112 |
+
Optional custom logger name for categorizing messages
|
| 113 |
+
</ResponseField>
|
| 114 |
+
</Expandable>
|
| 115 |
+
</ResponseField>
|
| 116 |
+
</Card>
|
| 117 |
+
|
| 118 |
+
## Log Levels
|
| 119 |
+
|
| 120 |
+
### Debug
|
| 121 |
+
Use for detailed information that's typically only useful when diagnosing problems:
|
| 122 |
+
|
| 123 |
+
```python
|
| 124 |
+
@mcp.tool
|
| 125 |
+
async def process_file(file_path: str, ctx: Context) -> str:
|
| 126 |
+
"""Process a file with detailed debug logging."""
|
| 127 |
+
await ctx.debug(f"Starting to process file: {file_path}")
|
| 128 |
+
await ctx.debug("Checking file permissions")
|
| 129 |
+
|
| 130 |
+
# File processing logic
|
| 131 |
+
await ctx.debug("File processing completed successfully")
|
| 132 |
+
return "File processed"
|
| 133 |
+
```
|
| 134 |
+
|
| 135 |
+
### Info
|
| 136 |
+
Use for general information about normal program execution:
|
| 137 |
+
|
| 138 |
+
```python
|
| 139 |
+
@mcp.tool
|
| 140 |
+
async def backup_database(ctx: Context) -> str:
|
| 141 |
+
"""Backup database with progress information."""
|
| 142 |
+
await ctx.info("Starting database backup")
|
| 143 |
+
await ctx.info("Connecting to database")
|
| 144 |
+
await ctx.info("Backup completed successfully")
|
| 145 |
+
return "Database backed up"
|
| 146 |
+
```
|
| 147 |
+
|
| 148 |
+
### Warning
|
| 149 |
+
Use for potentially harmful situations that don't prevent execution:
|
| 150 |
+
|
| 151 |
+
```python
|
| 152 |
+
@mcp.tool
|
| 153 |
+
async def validate_config(config: dict, ctx: Context) -> dict:
|
| 154 |
+
"""Validate configuration with warnings for deprecated options."""
|
| 155 |
+
if "old_api_key" in config:
|
| 156 |
+
await ctx.warning("Using deprecated 'old_api_key' field. Please use 'api_key' instead")
|
| 157 |
+
|
| 158 |
+
if config.get("timeout", 30) > 300:
|
| 159 |
+
await ctx.warning("Timeout value is very high (>5 minutes), this may cause issues")
|
| 160 |
+
|
| 161 |
+
return {"status": "valid", "warnings": "see logs"}
|
| 162 |
+
```
|
| 163 |
+
|
| 164 |
+
### Error
|
| 165 |
+
Use for error events that might still allow the application to continue:
|
| 166 |
+
|
| 167 |
+
```python
|
| 168 |
+
@mcp.tool
|
| 169 |
+
async def batch_process(items: list[str], ctx: Context) -> dict:
|
| 170 |
+
"""Process multiple items, logging errors for failed items."""
|
| 171 |
+
successful = 0
|
| 172 |
+
failed = 0
|
| 173 |
+
|
| 174 |
+
for item in items:
|
| 175 |
+
try:
|
| 176 |
+
# Process item
|
| 177 |
+
successful += 1
|
| 178 |
+
except Exception as e:
|
| 179 |
+
await ctx.error(f"Failed to process item '{item}': {str(e)}")
|
| 180 |
+
failed += 1
|
| 181 |
+
|
| 182 |
+
return {"successful": successful, "failed": failed}
|
| 183 |
+
```
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
## Client Handling
|
| 187 |
+
|
| 188 |
+
Log messages are sent to the client through the MCP protocol. How clients handle these messages depends on their implementation:
|
| 189 |
+
|
| 190 |
+
- **Development clients**: May display logs in real-time for debugging
|
| 191 |
+
- **Production clients**: May store logs for later analysis or display to users
|
| 192 |
+
- **Integration clients**: May forward logs to external logging systems
|
| 193 |
+
|
| 194 |
+
See [Client Logging](/clients/logging) for details on how clients can handle server log messages.
|
docs/servers/progress.mdx
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Progress Reporting
|
| 3 |
+
sidebarTitle: Progress
|
| 4 |
+
description: Update clients on the progress of long-running operations through the MCP context.
|
| 5 |
+
icon: chart-line
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
import { VersionBadge } from '/snippets/version-badge.mdx'
|
| 9 |
+
|
| 10 |
+
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.
|
| 11 |
+
|
| 12 |
+
## Why Use Progress Reporting?
|
| 13 |
+
|
| 14 |
+
Progress reporting is valuable for:
|
| 15 |
+
|
| 16 |
+
- **User experience**: Keep users informed about long-running operations
|
| 17 |
+
- **Progress indicators**: Enable clients to show progress bars or percentages
|
| 18 |
+
- **Timeout prevention**: Demonstrate that operations are actively progressing
|
| 19 |
+
- **Debugging**: Track execution progress for performance analysis
|
| 20 |
+
|
| 21 |
+
### Basic Usage
|
| 22 |
+
|
| 23 |
+
Use `ctx.report_progress()` to send progress updates to the client:
|
| 24 |
+
|
| 25 |
+
```python {14, 21}
|
| 26 |
+
from fastmcp import FastMCP, Context
|
| 27 |
+
import asyncio
|
| 28 |
+
|
| 29 |
+
mcp = FastMCP("ProgressDemo")
|
| 30 |
+
|
| 31 |
+
@mcp.tool
|
| 32 |
+
async def process_items(items: list[str], ctx: Context) -> dict:
|
| 33 |
+
"""Process a list of items with progress updates."""
|
| 34 |
+
total = len(items)
|
| 35 |
+
results = []
|
| 36 |
+
|
| 37 |
+
for i, item in enumerate(items):
|
| 38 |
+
# Report progress as we process each item
|
| 39 |
+
await ctx.report_progress(progress=i, total=total)
|
| 40 |
+
|
| 41 |
+
# Simulate processing time
|
| 42 |
+
await asyncio.sleep(0.1)
|
| 43 |
+
results.append(item.upper())
|
| 44 |
+
|
| 45 |
+
# Report 100% completion
|
| 46 |
+
await ctx.report_progress(progress=total, total=total)
|
| 47 |
+
|
| 48 |
+
return {"processed": len(results), "results": results}
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
## Method Signature
|
| 52 |
+
|
| 53 |
+
<Card icon="code" title="Context Progress Method">
|
| 54 |
+
<ResponseField name="ctx.report_progress" type="async method">
|
| 55 |
+
Report progress to the client for long-running operations
|
| 56 |
+
|
| 57 |
+
<Expandable title="parameters">
|
| 58 |
+
<ResponseField name="progress" type="float">
|
| 59 |
+
Current progress value (e.g., 24, 0.75, 1500)
|
| 60 |
+
</ResponseField>
|
| 61 |
+
|
| 62 |
+
<ResponseField name="total" type="float | None" default="None">
|
| 63 |
+
Optional total value (e.g., 100, 1.0, 2000). When provided, clients may interpret this as enabling percentage calculation.
|
| 64 |
+
</ResponseField>
|
| 65 |
+
</Expandable>
|
| 66 |
+
</ResponseField>
|
| 67 |
+
</Card>
|
| 68 |
+
|
| 69 |
+
## Progress Patterns
|
| 70 |
+
|
| 71 |
+
### Percentage-Based Progress
|
| 72 |
+
|
| 73 |
+
Report progress as a percentage (0-100):
|
| 74 |
+
|
| 75 |
+
```python {13-14}
|
| 76 |
+
@mcp.tool
|
| 77 |
+
async def download_file(url: str, ctx: Context) -> str:
|
| 78 |
+
"""Download a file with percentage progress."""
|
| 79 |
+
total_size = 1000 # KB
|
| 80 |
+
downloaded = 0
|
| 81 |
+
|
| 82 |
+
while downloaded < total_size:
|
| 83 |
+
# Download chunk
|
| 84 |
+
chunk_size = min(50, total_size - downloaded)
|
| 85 |
+
downloaded += chunk_size
|
| 86 |
+
|
| 87 |
+
# Report percentage progress
|
| 88 |
+
percentage = (downloaded / total_size) * 100
|
| 89 |
+
await ctx.report_progress(progress=percentage, total=100)
|
| 90 |
+
|
| 91 |
+
await asyncio.sleep(0.1) # Simulate download time
|
| 92 |
+
|
| 93 |
+
return f"Downloaded file from {url}"
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
### Absolute Progress
|
| 97 |
+
|
| 98 |
+
Report progress with absolute values:
|
| 99 |
+
|
| 100 |
+
```python {10}
|
| 101 |
+
@mcp.tool
|
| 102 |
+
async def backup_database(ctx: Context) -> str:
|
| 103 |
+
"""Backup database tables with absolute progress."""
|
| 104 |
+
tables = ["users", "orders", "products", "inventory", "logs"]
|
| 105 |
+
|
| 106 |
+
for i, table in enumerate(tables):
|
| 107 |
+
await ctx.info(f"Backing up table: {table}")
|
| 108 |
+
|
| 109 |
+
# Report absolute progress
|
| 110 |
+
await ctx.report_progress(progress=i + 1, total=len(tables))
|
| 111 |
+
|
| 112 |
+
# Simulate backup time
|
| 113 |
+
await asyncio.sleep(0.5)
|
| 114 |
+
|
| 115 |
+
return "Database backup completed"
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
### Indeterminate Progress
|
| 119 |
+
|
| 120 |
+
Report progress without a known total for operations where the endpoint is unknown:
|
| 121 |
+
|
| 122 |
+
```python {11}
|
| 123 |
+
@mcp.tool
|
| 124 |
+
async def scan_directory(directory: str, ctx: Context) -> dict:
|
| 125 |
+
"""Scan directory with indeterminate progress."""
|
| 126 |
+
files_found = 0
|
| 127 |
+
|
| 128 |
+
# Simulate directory scanning
|
| 129 |
+
for i in range(10): # Unknown number of files
|
| 130 |
+
files_found += 1
|
| 131 |
+
|
| 132 |
+
# Report progress without total for indeterminate operations
|
| 133 |
+
await ctx.report_progress(progress=files_found)
|
| 134 |
+
|
| 135 |
+
await asyncio.sleep(0.2)
|
| 136 |
+
|
| 137 |
+
return {"files_found": files_found, "directory": directory}
|
| 138 |
+
```
|
| 139 |
+
|
| 140 |
+
### Multi-Stage Operations
|
| 141 |
+
|
| 142 |
+
Break complex operations into stages with progress for each:
|
| 143 |
+
|
| 144 |
+
```python
|
| 145 |
+
@mcp.tool
|
| 146 |
+
async def data_migration(source: str, destination: str, ctx: Context) -> str:
|
| 147 |
+
"""Migrate data with multi-stage progress reporting."""
|
| 148 |
+
|
| 149 |
+
# Stage 1: Validation (0-25%)
|
| 150 |
+
await ctx.info("Validating source data")
|
| 151 |
+
for i in range(5):
|
| 152 |
+
await ctx.report_progress(progress=i * 5, total=100)
|
| 153 |
+
await asyncio.sleep(0.1)
|
| 154 |
+
|
| 155 |
+
# Stage 2: Export (25-60%)
|
| 156 |
+
await ctx.info("Exporting data from source")
|
| 157 |
+
for i in range(7):
|
| 158 |
+
progress = 25 + (i * 5)
|
| 159 |
+
await ctx.report_progress(progress=progress, total=100)
|
| 160 |
+
await asyncio.sleep(0.1)
|
| 161 |
+
|
| 162 |
+
# Stage 3: Transform (60-80%)
|
| 163 |
+
await ctx.info("Transforming data format")
|
| 164 |
+
for i in range(4):
|
| 165 |
+
progress = 60 + (i * 5)
|
| 166 |
+
await ctx.report_progress(progress=progress, total=100)
|
| 167 |
+
await asyncio.sleep(0.1)
|
| 168 |
+
|
| 169 |
+
# Stage 4: Import (80-100%)
|
| 170 |
+
await ctx.info("Importing to destination")
|
| 171 |
+
for i in range(4):
|
| 172 |
+
progress = 80 + (i * 5)
|
| 173 |
+
await ctx.report_progress(progress=progress, total=100)
|
| 174 |
+
await asyncio.sleep(0.1)
|
| 175 |
+
|
| 176 |
+
# Final completion
|
| 177 |
+
await ctx.report_progress(progress=100, total=100)
|
| 178 |
+
|
| 179 |
+
return f"Migration from {source} to {destination} completed"
|
| 180 |
+
```
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
## Client Requirements
|
| 184 |
+
|
| 185 |
+
Progress reporting requires clients to support progress handling:
|
| 186 |
+
|
| 187 |
+
- Clients must send a `progressToken` in the initial request to receive progress updates
|
| 188 |
+
- If no progress token is provided, progress calls will have no effect (they won't error)
|
| 189 |
+
- See [Client Progress](/clients/progress) for details on implementing client-side progress handling
|
docs/servers/sampling.mdx
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: LLM Sampling
|
| 3 |
+
sidebarTitle: Sampling
|
| 4 |
+
description: Request the client's LLM to generate text based on provided messages through the MCP context.
|
| 5 |
+
icon: robot
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
import { VersionBadge } from '/snippets/version-badge.mdx'
|
| 9 |
+
|
| 10 |
+
<VersionBadge version="2.0.0" />
|
| 11 |
+
|
| 12 |
+
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.
|
| 13 |
+
|
| 14 |
+
## Why Use LLM Sampling?
|
| 15 |
+
|
| 16 |
+
LLM sampling enables tools to:
|
| 17 |
+
|
| 18 |
+
- **Leverage AI capabilities**: Use the client's LLM for text generation and analysis
|
| 19 |
+
- **Offload complex reasoning**: Let the LLM handle tasks requiring natural language understanding
|
| 20 |
+
- **Generate dynamic content**: Create responses, summaries, or transformations based on data
|
| 21 |
+
- **Maintain context**: Use the same LLM instance that the user is already interacting with
|
| 22 |
+
|
| 23 |
+
### Basic Usage
|
| 24 |
+
|
| 25 |
+
Use `ctx.sample()` to request text generation from the client's LLM:
|
| 26 |
+
|
| 27 |
+
```python {14}
|
| 28 |
+
from fastmcp import FastMCP, Context
|
| 29 |
+
|
| 30 |
+
mcp = FastMCP("SamplingDemo")
|
| 31 |
+
|
| 32 |
+
@mcp.tool
|
| 33 |
+
async def analyze_sentiment(text: str, ctx: Context) -> dict:
|
| 34 |
+
"""Analyze the sentiment of text using the client's LLM."""
|
| 35 |
+
prompt = f"""Analyze the sentiment of the following text as positive, negative, or neutral.
|
| 36 |
+
Just output a single word - 'positive', 'negative', or 'neutral'.
|
| 37 |
+
|
| 38 |
+
Text to analyze: {text}"""
|
| 39 |
+
|
| 40 |
+
# Request LLM analysis
|
| 41 |
+
response = await ctx.sample(prompt)
|
| 42 |
+
|
| 43 |
+
# Process the LLM's response
|
| 44 |
+
sentiment = response.text.strip().lower()
|
| 45 |
+
|
| 46 |
+
# Map to standard sentiment values
|
| 47 |
+
if "positive" in sentiment:
|
| 48 |
+
sentiment = "positive"
|
| 49 |
+
elif "negative" in sentiment:
|
| 50 |
+
sentiment = "negative"
|
| 51 |
+
else:
|
| 52 |
+
sentiment = "neutral"
|
| 53 |
+
|
| 54 |
+
return {"text": text, "sentiment": sentiment}
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
## Method Signature
|
| 58 |
+
|
| 59 |
+
<Card icon="code" title="Context Sampling Method">
|
| 60 |
+
<ResponseField name="ctx.sample" type="async method">
|
| 61 |
+
Request text generation from the client's LLM
|
| 62 |
+
|
| 63 |
+
<Expandable title="parameters">
|
| 64 |
+
<ResponseField name="messages" type="str | list[str | SamplingMessage]">
|
| 65 |
+
A string or list of strings/message objects to send to the LLM
|
| 66 |
+
</ResponseField>
|
| 67 |
+
|
| 68 |
+
<ResponseField name="system_prompt" type="str | None" default="None">
|
| 69 |
+
Optional system prompt to guide the LLM's behavior
|
| 70 |
+
</ResponseField>
|
| 71 |
+
|
| 72 |
+
<ResponseField name="temperature" type="float | None" default="None">
|
| 73 |
+
Optional sampling temperature (controls randomness, typically 0.0-1.0)
|
| 74 |
+
</ResponseField>
|
| 75 |
+
|
| 76 |
+
<ResponseField name="max_tokens" type="int | None" default="512">
|
| 77 |
+
Optional maximum number of tokens to generate
|
| 78 |
+
</ResponseField>
|
| 79 |
+
|
| 80 |
+
<ResponseField name="model_preferences" type="ModelPreferences | str | list[str] | None" default="None">
|
| 81 |
+
Optional model selection preferences (e.g., model hint string, list of hints, or ModelPreferences object)
|
| 82 |
+
</ResponseField>
|
| 83 |
+
</Expandable>
|
| 84 |
+
|
| 85 |
+
<Expandable title="returns">
|
| 86 |
+
<ResponseField name="response" type="TextContent | ImageContent">
|
| 87 |
+
The LLM's response content (typically TextContent with a .text attribute)
|
| 88 |
+
</ResponseField>
|
| 89 |
+
</Expandable>
|
| 90 |
+
</ResponseField>
|
| 91 |
+
</Card>
|
| 92 |
+
|
| 93 |
+
## Simple Text Generation
|
| 94 |
+
|
| 95 |
+
### Basic Prompting
|
| 96 |
+
|
| 97 |
+
Generate text with simple string prompts:
|
| 98 |
+
|
| 99 |
+
```python {6}
|
| 100 |
+
@mcp.tool
|
| 101 |
+
async def generate_summary(content: str, ctx: Context) -> str:
|
| 102 |
+
"""Generate a summary of the provided content."""
|
| 103 |
+
prompt = f"Please provide a concise summary of the following content:\n\n{content}"
|
| 104 |
+
|
| 105 |
+
response = await ctx.sample(prompt)
|
| 106 |
+
return response.text
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
### System Prompt
|
| 110 |
+
|
| 111 |
+
Use system prompts to guide the LLM's behavior:
|
| 112 |
+
|
| 113 |
+
```python {4-9}
|
| 114 |
+
@mcp.tool
|
| 115 |
+
async def generate_code_example(concept: str, ctx: Context) -> str:
|
| 116 |
+
"""Generate a Python code example for a given concept."""
|
| 117 |
+
response = await ctx.sample(
|
| 118 |
+
messages=f"Write a simple Python code example demonstrating '{concept}'.",
|
| 119 |
+
system_prompt="You are an expert Python programmer. Provide concise, working code examples without explanations.",
|
| 120 |
+
temperature=0.7,
|
| 121 |
+
max_tokens=300
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
code_example = response.text
|
| 125 |
+
return f"```python\n{code_example}\n```"
|
| 126 |
+
```
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
### Model Preferences
|
| 130 |
+
|
| 131 |
+
Specify model preferences for different use cases:
|
| 132 |
+
|
| 133 |
+
```python {4-8, 17-22}
|
| 134 |
+
@mcp.tool
|
| 135 |
+
async def creative_writing(topic: str, ctx: Context) -> str:
|
| 136 |
+
"""Generate creative content using a specific model."""
|
| 137 |
+
response = await ctx.sample(
|
| 138 |
+
messages=f"Write a creative short story about {topic}",
|
| 139 |
+
model_preferences="claude-3-sonnet", # Prefer a specific model
|
| 140 |
+
temperature=0.9, # High creativity
|
| 141 |
+
max_tokens=1000
|
| 142 |
+
)
|
| 143 |
+
|
| 144 |
+
return response.text
|
| 145 |
+
|
| 146 |
+
@mcp.tool
|
| 147 |
+
async def technical_analysis(data: str, ctx: Context) -> str:
|
| 148 |
+
"""Perform technical analysis with a reasoning-focused model."""
|
| 149 |
+
response = await ctx.sample(
|
| 150 |
+
messages=f"Analyze this technical data and provide insights: {data}",
|
| 151 |
+
model_preferences=["claude-3-opus", "gpt-4"], # Prefer reasoning models
|
| 152 |
+
temperature=0.2, # Low randomness for consistency
|
| 153 |
+
max_tokens=800
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
return response.text
|
| 157 |
+
```
|
| 158 |
+
|
| 159 |
+
### Complex Message Structures
|
| 160 |
+
|
| 161 |
+
Use structured messages for more complex interactions:
|
| 162 |
+
|
| 163 |
+
```python {1, 6-10}
|
| 164 |
+
from fastmcp.client.sampling import SamplingMessage
|
| 165 |
+
|
| 166 |
+
@mcp.tool
|
| 167 |
+
async def multi_turn_analysis(user_query: str, context_data: str, ctx: Context) -> str:
|
| 168 |
+
"""Perform analysis using multi-turn conversation structure."""
|
| 169 |
+
messages = [
|
| 170 |
+
SamplingMessage(role="user", content=f"I have this data: {context_data}"),
|
| 171 |
+
SamplingMessage(role="assistant", content="I can see your data. What would you like me to analyze?"),
|
| 172 |
+
SamplingMessage(role="user", content=user_query)
|
| 173 |
+
]
|
| 174 |
+
|
| 175 |
+
response = await ctx.sample(
|
| 176 |
+
messages=messages,
|
| 177 |
+
system_prompt="You are a data analyst. Provide detailed insights based on the conversation context.",
|
| 178 |
+
temperature=0.3
|
| 179 |
+
)
|
| 180 |
+
|
| 181 |
+
return response.text
|
| 182 |
+
```
|
| 183 |
+
|
| 184 |
+
## Client Requirements
|
| 185 |
+
|
| 186 |
+
LLM sampling requires client support:
|
| 187 |
+
|
| 188 |
+
- Clients must implement sampling handlers to process requests
|
| 189 |
+
- If the client doesn't support sampling, calls to `ctx.sample()` will fail
|
| 190 |
+
- See [Client Sampling](/clients/sampling) for details on implementing client-side sampling handlers
|