Jeremiah Lowin commited on
Commit
120f8eb
·
1 Parent(s): 800a5b4

update tool docs

Browse files
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
- ### Using Context in Tools
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):