Spaces:
Running
Running
Jeremiah Lowin commited on
Commit ·
120f8eb
1
Parent(s): 800a5b4
update tool docs
Browse files- docs/servers/tools.mdx +173 -57
- tests/server/test_server_interactions.py +14 -0
docs/servers/tools.mdx
CHANGED
|
@@ -66,23 +66,6 @@ def analyze_text(
|
|
| 66 |
# Implementation...
|
| 67 |
```
|
| 68 |
|
| 69 |
-
#### Supported Types
|
| 70 |
-
|
| 71 |
-
FastMCP supports a wide range of type annotations:
|
| 72 |
-
|
| 73 |
-
| Type Annotation | Example | Description |
|
| 74 |
-
| :---------------------- | :---------------------------- | :---------------------------------- |
|
| 75 |
-
| Basic types | `int`, `float`, `str`, `bool` | Simple scalar values |
|
| 76 |
-
| Container types | `list[str]`, `dict[str, int]` | Collections of items |
|
| 77 |
-
| Optional types | `Optional[float]`, `float\|None`| Parameters that may be null/omitted |
|
| 78 |
-
| Union types | `str \| int`, `Union[str, int]`| Parameters accepting multiple types |
|
| 79 |
-
| Literal types | `Literal["A", "B"]` | Parameters with specific allowed values |
|
| 80 |
-
| Pydantic models | `UserData` | Complex structured data (see Structured Inputs) |
|
| 81 |
-
|
| 82 |
-
<Tip>
|
| 83 |
-
**Automatic JSON Parsing:** FastMCP intelligently handles arguments. If a client sends a string that looks like valid JSON (e.g., `"['a', 'b']"`) for a parameter hinted as a structured type (like `list[str]` or a Pydantic model), FastMCP will automatically attempt to parse the JSON string into the expected Python object before validation. This improves robustness when interacting with various clients.
|
| 84 |
-
</Tip>
|
| 85 |
-
|
| 86 |
#### Parameter Metadata
|
| 87 |
|
| 88 |
You can provide additional metadata about parameters using Pydantic's `Field` class with `Annotated`. This approach is preferred as it's more modern and keeps type hints separate from validation rules:
|
|
@@ -124,6 +107,20 @@ Field provides several validation and documentation features:
|
|
| 124 |
- `pattern`: Regex pattern for string validation
|
| 125 |
- `default`: Default value if parameter is omitted
|
| 126 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
#### Optional Arguments
|
| 128 |
|
| 129 |
FastMCP follows Python's standard function parameter conventions. Parameters without default values are required, while those with default values are optional.
|
|
@@ -142,44 +139,6 @@ def search_products(
|
|
| 142 |
|
| 143 |
In this example, the LLM must provide a `query` parameter, while `max_results`, `sort_by`, and `category` will use their default values if not explicitly provided.
|
| 144 |
|
| 145 |
-
### Structured Inputs
|
| 146 |
-
|
| 147 |
-
For tools requiring complex, nested, or well-validated inputs, use Pydantic models. Define a `BaseModel` and use it as a type hint for a parameter.
|
| 148 |
-
|
| 149 |
-
```python
|
| 150 |
-
from pydantic import BaseModel, Field
|
| 151 |
-
from typing import Optional
|
| 152 |
-
from datetime import date
|
| 153 |
-
|
| 154 |
-
class ReservationRequest(BaseModel):
|
| 155 |
-
guest_name: str = Field(description="Full name of the guest making the reservation.")
|
| 156 |
-
check_in: date
|
| 157 |
-
check_out: date
|
| 158 |
-
room_type: Literal["standard", "deluxe", "suite"] = Field(default="standard", description="Type of room requested.")
|
| 159 |
-
guests: int = Field(gt=0, description="Number of guests (must be positive).")
|
| 160 |
-
special_requests: Optional[str] = Field(default=None, description="Any special requests for the stay.")
|
| 161 |
-
|
| 162 |
-
@mcp.tool()
|
| 163 |
-
def make_reservation(request: ReservationRequest) -> dict:
|
| 164 |
-
"""Creates a new hotel reservation based on the provided details."""
|
| 165 |
-
# Pydantic automatically validates the incoming 'request' data
|
| 166 |
-
# against the ReservationRequest model before this function runs.
|
| 167 |
-
print(f"Making reservation for {request.guest_name}...")
|
| 168 |
-
# Implementation...
|
| 169 |
-
return {
|
| 170 |
-
"reservation_id": "R12345",
|
| 171 |
-
"status": "confirmed",
|
| 172 |
-
"guest": request.guest_name,
|
| 173 |
-
"dates": f"{request.check_in} to {request.check_out}"
|
| 174 |
-
}
|
| 175 |
-
```
|
| 176 |
-
|
| 177 |
-
Using Pydantic models provides:
|
| 178 |
-
- Clear, self-documenting structure for complex inputs.
|
| 179 |
-
- Built-in data validation (e.g., `gt=0`, date parsing).
|
| 180 |
-
- Automatic generation of detailed JSON schemas for the LLM.
|
| 181 |
-
- Easy handling of optional fields and default values.
|
| 182 |
-
|
| 183 |
### Metadata
|
| 184 |
|
| 185 |
While FastMCP infers the name and description from your function, you can override these and add tags using arguments to the `@mcp.tool` decorator:
|
|
@@ -293,7 +252,7 @@ FastMCP automatically catches exceptions raised within your tool function:
|
|
| 293 |
|
| 294 |
Using informative exceptions helps the LLM understand failures and react appropriately.
|
| 295 |
|
| 296 |
-
###
|
| 297 |
|
| 298 |
Tools can access MCP features like logging, reading resources, or reporting progress through the `Context` object. To use it, add a parameter to your tool function with the type hint `Context`.
|
| 299 |
|
|
@@ -365,4 +324,161 @@ The duplicate behavior options are:
|
|
| 365 |
- `"warn"` (default): Logs a warning and the new tool replaces the old one.
|
| 366 |
- `"error"`: Raises a `ValueError`, preventing the duplicate registration.
|
| 367 |
- `"replace"`: Silently replaces the existing tool with the new one.
|
| 368 |
-
- `"ignore"`: Keeps the original tool and ignores the new registration attempt.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
# Implementation...
|
| 67 |
```
|
| 68 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
#### Parameter Metadata
|
| 70 |
|
| 71 |
You can provide additional metadata about parameters using Pydantic's `Field` class with `Annotated`. This approach is preferred as it's more modern and keeps type hints separate from validation rules:
|
|
|
|
| 107 |
- `pattern`: Regex pattern for string validation
|
| 108 |
- `default`: Default value if parameter is omitted
|
| 109 |
|
| 110 |
+
#### Supported Types
|
| 111 |
+
|
| 112 |
+
FastMCP supports a wide range of type annotations:
|
| 113 |
+
|
| 114 |
+
| Type Annotation | Example | Description |
|
| 115 |
+
| :---------------------- | :---------------------------- | :---------------------------------- |
|
| 116 |
+
| Basic types | `int`, `float`, `str`, `bool` | Simple scalar values - see [Built-in Types](#built-in-types) |
|
| 117 |
+
| Binary data | `bytes` | Binary content - see [Binary Data Handling](#binary-data-handling) |
|
| 118 |
+
| Collection types | `list[str]`, `dict[str, int]`, `set[int]` | Collections of items - see [Collection Types](#collection-types) |
|
| 119 |
+
| Optional types | `float \| None`, `Optional[float]`| Parameters that may be null/omitted - see [Union and Optional Types](#union-and-optional-types) |
|
| 120 |
+
| Union types | `str \| int`, `Union[str, int]`| Parameters accepting multiple types - see [Union and Optional Types](#union-and-optional-types) |
|
| 121 |
+
| Literal types | `Literal["A", "B"]` | Parameters with specific allowed values - see [Literal Types](#literal-types) |
|
| 122 |
+
| Pydantic models | `UserData` | Complex structured data - see [Pydantic Models](#pydantic-models) |
|
| 123 |
+
|
| 124 |
#### Optional Arguments
|
| 125 |
|
| 126 |
FastMCP follows Python's standard function parameter conventions. Parameters without default values are required, while those with default values are optional.
|
|
|
|
| 139 |
|
| 140 |
In this example, the LLM must provide a `query` parameter, while `max_results`, `sort_by`, and `category` will use their default values if not explicitly provided.
|
| 141 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
### Metadata
|
| 143 |
|
| 144 |
While FastMCP infers the name and description from your function, you can override these and add tags using arguments to the `@mcp.tool` decorator:
|
|
|
|
| 252 |
|
| 253 |
Using informative exceptions helps the LLM understand failures and react appropriately.
|
| 254 |
|
| 255 |
+
### Accessing MCP Context
|
| 256 |
|
| 257 |
Tools can access MCP features like logging, reading resources, or reporting progress through the `Context` object. To use it, add a parameter to your tool function with the type hint `Context`.
|
| 258 |
|
|
|
|
| 324 |
- `"warn"` (default): Logs a warning and the new tool replaces the old one.
|
| 325 |
- `"error"`: Raises a `ValueError`, preventing the duplicate registration.
|
| 326 |
- `"replace"`: Silently replaces the existing tool with the new one.
|
| 327 |
+
- `"ignore"`: Keeps the original tool and ignores the new registration attempt.
|
| 328 |
+
|
| 329 |
+
## Parameter Types
|
| 330 |
+
|
| 331 |
+
FastMCP supports a wide variety of parameter types to give you flexibility when designing your tools. When clients send parameters, FastMCP will attempt to coerce values into the appropriate type when possible (for example, parsing JSON strings into structured types).
|
| 332 |
+
|
| 333 |
+
### Built-in Types
|
| 334 |
+
|
| 335 |
+
The most common parameter types are Python's built-in scalar types:
|
| 336 |
+
|
| 337 |
+
```python
|
| 338 |
+
@mcp.tool()
|
| 339 |
+
def process_values(
|
| 340 |
+
name: str, # Text data
|
| 341 |
+
count: int, # Integer numbers
|
| 342 |
+
amount: float, # Floating point numbers
|
| 343 |
+
enabled: bool # Boolean values (True/False)
|
| 344 |
+
):
|
| 345 |
+
"""Process various value types."""
|
| 346 |
+
# Implementation...
|
| 347 |
+
```
|
| 348 |
+
|
| 349 |
+
These types provide clear expectations to the LLM about what values are acceptable and allow FastMCP to validate inputs properly. Even if a client provides a string like "42", it will be coerced to an integer for parameters annotated as `int`.
|
| 350 |
+
|
| 351 |
+
### Collection Types
|
| 352 |
+
|
| 353 |
+
For structured data collections, FastMCP supports standard Python collection types:
|
| 354 |
+
|
| 355 |
+
```python
|
| 356 |
+
@mcp.tool()
|
| 357 |
+
def analyze_data(
|
| 358 |
+
values: list[float], # List of numbers
|
| 359 |
+
labels: list[str], # List of strings
|
| 360 |
+
properties: dict[str, str], # Dictionary with string keys and values
|
| 361 |
+
mixed_data: dict[str, list[int]] # Nested collections
|
| 362 |
+
):
|
| 363 |
+
"""Analyze collections of data."""
|
| 364 |
+
# Implementation...
|
| 365 |
+
```
|
| 366 |
+
|
| 367 |
+
Collection types can be nested and combined to represent complex data structures. If a client sends a JSON string like `"[1.5, 2.5, 3.5]"` for a `list[float]` parameter, FastMCP will automatically parse and convert it.
|
| 368 |
+
|
| 369 |
+
### Union and Optional Types
|
| 370 |
+
|
| 371 |
+
For parameters that can accept multiple types or may be omitted:
|
| 372 |
+
|
| 373 |
+
```python
|
| 374 |
+
@mcp.tool()
|
| 375 |
+
def flexible_search(
|
| 376 |
+
query: str | int, # Can be either string or integer
|
| 377 |
+
filters: dict[str, str] | None = None, # Optional dictionary
|
| 378 |
+
sort_field: str | None = None # Optional string
|
| 379 |
+
):
|
| 380 |
+
"""Search with flexible parameter types."""
|
| 381 |
+
# Implementation...
|
| 382 |
+
```
|
| 383 |
+
|
| 384 |
+
Modern Python syntax (`str | int`) is preferred over older `Union[str, int]` forms. Similarly, `str | None` is preferred over `Optional[str]`.
|
| 385 |
+
|
| 386 |
+
### Literal Types
|
| 387 |
+
|
| 388 |
+
When a parameter must be one of a predefined set of values:
|
| 389 |
+
|
| 390 |
+
```python
|
| 391 |
+
from typing import Literal
|
| 392 |
+
|
| 393 |
+
@mcp.tool()
|
| 394 |
+
def sort_data(
|
| 395 |
+
data: list[float],
|
| 396 |
+
order: Literal["ascending", "descending"] = "ascending",
|
| 397 |
+
algorithm: Literal["quicksort", "mergesort", "heapsort"] = "quicksort"
|
| 398 |
+
):
|
| 399 |
+
"""Sort data using specified order and algorithm."""
|
| 400 |
+
# Implementation...
|
| 401 |
+
```
|
| 402 |
+
|
| 403 |
+
Literal types help LLMs understand exactly which values are acceptable and provide validation for incoming parameters.
|
| 404 |
+
|
| 405 |
+
### Binary Data Handling
|
| 406 |
+
|
| 407 |
+
There are two approaches to handling binary data in tool parameters:
|
| 408 |
+
|
| 409 |
+
#### Using bytes type
|
| 410 |
+
|
| 411 |
+
```python
|
| 412 |
+
@mcp.tool()
|
| 413 |
+
def process_binary(data: bytes):
|
| 414 |
+
"""Process binary data directly.
|
| 415 |
+
|
| 416 |
+
The client can send a binary string, which will be
|
| 417 |
+
converted directly to bytes.
|
| 418 |
+
"""
|
| 419 |
+
# Implementation using binary data
|
| 420 |
+
data_length = len(data)
|
| 421 |
+
# ...
|
| 422 |
+
```
|
| 423 |
+
|
| 424 |
+
When you annotate a parameter as `bytes`, FastMCP will:
|
| 425 |
+
- Convert raw strings directly to bytes
|
| 426 |
+
- Validate that the input can be properly represented as bytes
|
| 427 |
+
|
| 428 |
+
FastMCP does not automatically decode base64-encoded strings for bytes parameters. If you need to accept base64-encoded data, you should handle the decoding manually as shown below.
|
| 429 |
+
|
| 430 |
+
#### Using base64-encoded strings
|
| 431 |
+
|
| 432 |
+
```python
|
| 433 |
+
from typing import Annotated
|
| 434 |
+
from pydantic import Field
|
| 435 |
+
|
| 436 |
+
@mcp.tool()
|
| 437 |
+
def process_image_data(
|
| 438 |
+
image_data: Annotated[str, Field(description="Base64-encoded image data")]
|
| 439 |
+
):
|
| 440 |
+
"""Process an image from base64-encoded string.
|
| 441 |
+
|
| 442 |
+
The client is expected to provide base64-encoded data as a string.
|
| 443 |
+
You'll need to decode it manually.
|
| 444 |
+
"""
|
| 445 |
+
# Manual base64 decoding
|
| 446 |
+
import base64
|
| 447 |
+
binary_data = base64.b64decode(image_data)
|
| 448 |
+
# Process binary_data...
|
| 449 |
+
```
|
| 450 |
+
|
| 451 |
+
This approach is recommended when you expect to receive base64-encoded binary data from clients.
|
| 452 |
+
|
| 453 |
+
### Pydantic Models
|
| 454 |
+
|
| 455 |
+
For complex, structured data with nested fields and validation, use Pydantic models:
|
| 456 |
+
|
| 457 |
+
```python
|
| 458 |
+
from pydantic import BaseModel, Field
|
| 459 |
+
from typing import Optional
|
| 460 |
+
|
| 461 |
+
class User(BaseModel):
|
| 462 |
+
username: str
|
| 463 |
+
email: str = Field(description="User's email address")
|
| 464 |
+
age: int | None = None
|
| 465 |
+
is_active: bool = True
|
| 466 |
+
|
| 467 |
+
@mcp.tool()
|
| 468 |
+
def create_user(user: User):
|
| 469 |
+
"""Create a new user in the system."""
|
| 470 |
+
# The input is automatically validated against the User model
|
| 471 |
+
# Even if provided as a JSON string or dict
|
| 472 |
+
# Implementation...
|
| 473 |
+
```
|
| 474 |
+
|
| 475 |
+
Using Pydantic models provides:
|
| 476 |
+
- Clear, self-documenting structure for complex inputs
|
| 477 |
+
- Built-in data validation
|
| 478 |
+
- Automatic generation of detailed JSON schemas for the LLM
|
| 479 |
+
- Automatic conversion from dict/JSON input
|
| 480 |
+
|
| 481 |
+
Clients can provide data for Pydantic model parameters as either:
|
| 482 |
+
- A JSON object (string)
|
| 483 |
+
- A dictionary with the appropriate structure
|
| 484 |
+
- Nested parameters in the appropriate format
|
tests/server/test_server_interactions.py
CHANGED
|
@@ -195,6 +195,20 @@ class TestTools:
|
|
| 195 |
assert result[0].mimeType == "image/png"
|
| 196 |
assert result[0].data == base64.b64encode(b"fake png data").decode()
|
| 197 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
|
| 199 |
class TestResources:
|
| 200 |
async def test_text_resource(self):
|
|
|
|
| 195 |
assert result[0].mimeType == "image/png"
|
| 196 |
assert result[0].data == base64.b64encode(b"fake png data").decode()
|
| 197 |
|
| 198 |
+
async def test_tool_with_invalid_input(self):
|
| 199 |
+
mcp = FastMCP()
|
| 200 |
+
|
| 201 |
+
@mcp.tool()
|
| 202 |
+
def my_tool(x: int) -> int:
|
| 203 |
+
return x + 1
|
| 204 |
+
|
| 205 |
+
async with Client(mcp) as client:
|
| 206 |
+
with pytest.raises(
|
| 207 |
+
ClientError,
|
| 208 |
+
match="Input should be a valid integer, unable to parse string as an integer",
|
| 209 |
+
):
|
| 210 |
+
await client.call_tool("my_tool", {"x": "not an int"})
|
| 211 |
+
|
| 212 |
|
| 213 |
class TestResources:
|
| 214 |
async def test_text_resource(self):
|