Jeremiah Lowin commited on
Commit
0ce024d
·
1 Parent(s): ae3ce72

Update docs and tests

Browse files
docs/servers/tools.mdx CHANGED
@@ -109,16 +109,19 @@ Field provides several validation and documentation features:
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
  | Constrained types | `Literal["A", "B"]`, `Enum` | Parameters with specific allowed values - see [Constrained Types](#constrained-types) |
 
 
122
  | Pydantic models | `UserData` | Complex structured data - see [Pydantic Models](#pydantic-models) |
123
 
124
  For additional type annotations not listed here, see the [Parameter Types](#parameter-types) section below for more detailed information and examples.
@@ -334,11 +337,10 @@ The duplicate behavior options are:
334
 
335
  FastMCP supports a wide variety of parameter types to give you flexibility when designing your tools.
336
 
337
-
338
 
339
  FastMCP supports **type coercion** when possible. This means that if a client sends data that doesn't match the expected type, FastMCP will attempt to convert it to the appropriate type. For example, if a client sends a string for a parameter annotated as `int`, FastMCP will attempt to convert it to an integer. If the conversion is not possible, FastMCP will return a validation error.
340
 
341
-
342
  ### Built-in Types
343
 
344
  The most common parameter types are Python's built-in scalar types:
@@ -357,6 +359,32 @@ def process_values(
357
 
358
  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`.
359
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
360
  ### Collection Types
361
 
362
  FastMCP supports all standard Python collection types:
@@ -502,6 +530,40 @@ def process_image_data(
502
 
503
  This approach is recommended when you expect to receive base64-encoded binary data from clients.
504
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
505
  ### Pydantic Models
506
 
507
  For complex, structured data with nested fields and validation, use Pydantic models:
 
109
 
110
  #### Supported Types
111
 
112
+ FastMCP supports a wide range of type annotations, including all Pydantic types:
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](#binary-data) |
118
+ | Date and Time | `datetime`, `date`, `timedelta` | Date and time objects - see [Date and Time Types](#date-and-time-types) |
119
  | Collection types | `list[str]`, `dict[str, int]`, `set[int]` | Collections of items - see [Collection Types](#collection-types) |
120
  | Optional types | `float \| None`, `Optional[float]`| Parameters that may be null/omitted - see [Union and Optional Types](#union-and-optional-types) |
121
  | Union types | `str \| int`, `Union[str, int]`| Parameters accepting multiple types - see [Union and Optional Types](#union-and-optional-types) |
122
  | Constrained types | `Literal["A", "B"]`, `Enum` | Parameters with specific allowed values - see [Constrained Types](#constrained-types) |
123
+ | Paths | `Path` | File system paths - see [Paths](#paths) |
124
+ | UUIDs | `UUID` | Universally unique identifiers - see [UUIDs](#uuids) |
125
  | Pydantic models | `UserData` | Complex structured data - see [Pydantic Models](#pydantic-models) |
126
 
127
  For additional type annotations not listed here, see the [Parameter Types](#parameter-types) section below for more detailed information and examples.
 
337
 
338
  FastMCP supports a wide variety of parameter types to give you flexibility when designing your tools.
339
 
340
+ FastMCP generally supports all types that Pydantic supports as fields, including all Pydantic custom types. This means you can use any type that can be validated and parsed by Pydantic in your tool parameters.
341
 
342
  FastMCP supports **type coercion** when possible. This means that if a client sends data that doesn't match the expected type, FastMCP will attempt to convert it to the appropriate type. For example, if a client sends a string for a parameter annotated as `int`, FastMCP will attempt to convert it to an integer. If the conversion is not possible, FastMCP will return a validation error.
343
 
 
344
  ### Built-in Types
345
 
346
  The most common parameter types are Python's built-in scalar types:
 
359
 
360
  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`.
361
 
362
+ ### Date and Time Types
363
+
364
+ FastMCP supports various date and time types from the `datetime` module:
365
+
366
+ ```python
367
+ from datetime import datetime, date, timedelta
368
+
369
+ @mcp.tool()
370
+ def process_date_time(
371
+ event_date: date, # ISO format date string or date object
372
+ event_time: datetime, # ISO format datetime string or datetime object
373
+ duration: timedelta = timedelta(hours=1) # Integer seconds or timedelta
374
+ ) -> str:
375
+ """Process date and time information."""
376
+ # Types are automatically converted from strings
377
+ assert isinstance(event_date, date)
378
+ assert isinstance(event_time, datetime)
379
+ assert isinstance(duration, timedelta)
380
+
381
+ return f"Event on {event_date} at {event_time} for {duration}"
382
+ ```
383
+
384
+ - `datetime` - Accepts ISO format strings (e.g., "2023-04-15T14:30:00")
385
+ - `date` - Accepts ISO format date strings (e.g., "2023-04-15")
386
+ - `timedelta` - Accepts integer seconds or timedelta objects
387
+
388
  ### Collection Types
389
 
390
  FastMCP supports all standard Python collection types:
 
530
 
531
  This approach is recommended when you expect to receive base64-encoded binary data from clients.
532
 
533
+ ### Paths
534
+
535
+ The `Path` type from the `pathlib` module can be used for file system paths:
536
+
537
+ ```python
538
+ from pathlib import Path
539
+
540
+ @mcp.tool()
541
+ def process_file(path: Path) -> str:
542
+ """Process a file at the given path."""
543
+ assert isinstance(path, Path) # Path is properly converted
544
+ return f"Processing file at {path}"
545
+ ```
546
+
547
+ When a client sends a string path, FastMCP automatically converts it to a `Path` object.
548
+
549
+ ### UUIDs
550
+
551
+ The `UUID` type from the `uuid` module can be used for unique identifiers:
552
+
553
+ ```python
554
+ import uuid
555
+
556
+ @mcp.tool()
557
+ def process_item(
558
+ item_id: uuid.UUID # String UUID or UUID object
559
+ ) -> str:
560
+ """Process an item with the given UUID."""
561
+ assert isinstance(item_id, uuid.UUID) # Properly converted to UUID
562
+ return f"Processing item {item_id}"
563
+ ```
564
+
565
+ When a client sends a string UUID (e.g., "123e4567-e89b-12d3-a456-426614174000"), FastMCP automatically converts it to a `UUID` object.
566
+
567
  ### Pydantic Models
568
 
569
  For complex, structured data with nested fields and validation, use Pydantic models:
tests/server/test_server_interactions.py CHANGED
@@ -1,5 +1,7 @@
1
  import base64
 
2
  import json
 
3
  from enum import Enum
4
  from pathlib import Path
5
  from typing import Annotated, Literal
@@ -159,6 +161,8 @@ class TestTools:
159
  assert isinstance(content3, TextContent)
160
  assert content3.text == "direct content"
161
 
 
 
162
  async def test_parameter_descriptions_with_field_annotations(self):
163
  mcp = FastMCP("Test Server")
164
 
@@ -461,6 +465,145 @@ class TestTools:
461
  with pytest.raises(ClientError, match="2 validation errors for analyze"):
462
  await client.call_tool("analyze", {"x": "not a number"})
463
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
464
 
465
  class TestResources:
466
  async def test_text_resource(self):
 
1
  import base64
2
+ import datetime
3
  import json
4
+ import uuid
5
  from enum import Enum
6
  from pathlib import Path
7
  from typing import Annotated, Literal
 
161
  assert isinstance(content3, TextContent)
162
  assert content3.text == "direct content"
163
 
164
+
165
+ class TestToolParameters:
166
  async def test_parameter_descriptions_with_field_annotations(self):
167
  mcp = FastMCP("Test Server")
168
 
 
465
  with pytest.raises(ClientError, match="2 validation errors for analyze"):
466
  await client.call_tool("analyze", {"x": "not a number"})
467
 
468
+ async def test_path_type(self):
469
+ mcp = FastMCP()
470
+
471
+ @mcp.tool()
472
+ def send_path(path: Path) -> str:
473
+ assert isinstance(path, Path)
474
+ return str(path)
475
+
476
+ async with Client(mcp) as client:
477
+ result = await client.call_tool("send_path", {"path": "/tmp/test.txt"})
478
+ assert isinstance(result[0], TextContent)
479
+ assert result[0].text == "/tmp/test.txt"
480
+
481
+ async def test_path_type_error(self):
482
+ mcp = FastMCP()
483
+
484
+ @mcp.tool()
485
+ def send_path(path: Path) -> str:
486
+ return str(path)
487
+
488
+ async with Client(mcp) as client:
489
+ with pytest.raises(ClientError, match="Input is not a valid path"):
490
+ await client.call_tool("send_path", {"path": 1})
491
+
492
+ async def test_uuid_type(self):
493
+ mcp = FastMCP()
494
+
495
+ @mcp.tool()
496
+ def send_uuid(x: uuid.UUID) -> str:
497
+ assert isinstance(x, uuid.UUID)
498
+ return str(x)
499
+
500
+ test_uuid = uuid.uuid4()
501
+
502
+ async with Client(mcp) as client:
503
+ result = await client.call_tool("send_uuid", {"x": test_uuid})
504
+ assert isinstance(result[0], TextContent)
505
+ assert result[0].text == str(test_uuid)
506
+
507
+ async def test_uuid_type_error(self):
508
+ mcp = FastMCP()
509
+
510
+ @mcp.tool()
511
+ def send_uuid(x: uuid.UUID) -> str:
512
+ return str(x)
513
+
514
+ async with Client(mcp) as client:
515
+ with pytest.raises(ClientError, match="Input should be a valid UUID"):
516
+ await client.call_tool("send_uuid", {"x": "not a uuid"})
517
+
518
+ async def test_datetime_type(self):
519
+ mcp = FastMCP()
520
+
521
+ @mcp.tool()
522
+ def send_datetime(x: datetime.datetime) -> str:
523
+ return x.isoformat()
524
+
525
+ async with Client(mcp) as client:
526
+ result = await client.call_tool(
527
+ "send_datetime", {"x": datetime.datetime.now()}
528
+ )
529
+ assert isinstance(result[0], TextContent)
530
+ assert result[0].text == datetime.datetime.now().isoformat()
531
+
532
+ async def test_datetime_type_parse_string(self):
533
+ mcp = FastMCP()
534
+
535
+ @mcp.tool()
536
+ def send_datetime(x: datetime.datetime) -> str:
537
+ return x.isoformat()
538
+
539
+ async with Client(mcp) as client:
540
+ result = await client.call_tool(
541
+ "send_datetime", {"x": "2021-01-01T00:00:00"}
542
+ )
543
+ assert isinstance(result[0], TextContent)
544
+ assert result[0].text == "2021-01-01T00:00:00"
545
+
546
+ async def test_datetime_type_error(self):
547
+ mcp = FastMCP()
548
+
549
+ @mcp.tool()
550
+ def send_datetime(x: datetime.datetime) -> str:
551
+ return x.isoformat()
552
+
553
+ async with Client(mcp) as client:
554
+ with pytest.raises(ClientError, match="Input should be a valid datetime"):
555
+ await client.call_tool("send_datetime", {"x": "not a datetime"})
556
+
557
+ async def test_date_type(self):
558
+ mcp = FastMCP()
559
+
560
+ @mcp.tool()
561
+ def send_date(x: datetime.date) -> str:
562
+ return x.isoformat()
563
+
564
+ async with Client(mcp) as client:
565
+ result = await client.call_tool("send_date", {"x": datetime.date.today()})
566
+ assert isinstance(result[0], TextContent)
567
+ assert result[0].text == datetime.date.today().isoformat()
568
+
569
+ async def test_date_type_parse_string(self):
570
+ mcp = FastMCP()
571
+
572
+ @mcp.tool()
573
+ def send_date(x: datetime.date) -> str:
574
+ return x.isoformat()
575
+
576
+ async with Client(mcp) as client:
577
+ result = await client.call_tool("send_date", {"x": "2021-01-01"})
578
+ assert isinstance(result[0], TextContent)
579
+ assert result[0].text == "2021-01-01"
580
+
581
+ async def test_timedelta_type(self):
582
+ mcp = FastMCP()
583
+
584
+ @mcp.tool()
585
+ def send_timedelta(x: datetime.timedelta) -> str:
586
+ return str(x)
587
+
588
+ async with Client(mcp) as client:
589
+ result = await client.call_tool(
590
+ "send_timedelta", {"x": datetime.timedelta(days=1)}
591
+ )
592
+ assert isinstance(result[0], TextContent)
593
+ assert result[0].text == "1 day, 0:00:00"
594
+
595
+ async def test_timedelta_type_parse_int(self):
596
+ mcp = FastMCP()
597
+
598
+ @mcp.tool()
599
+ def send_timedelta(x: datetime.timedelta) -> str:
600
+ return str(x)
601
+
602
+ async with Client(mcp) as client:
603
+ result = await client.call_tool("send_timedelta", {"x": 1000})
604
+ assert isinstance(result[0], TextContent)
605
+ assert result[0].text == "0:16:40"
606
+
607
 
608
  class TestResources:
609
  async def test_text_resource(self):