mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-20 04:24:17 +02:00
Add SEP-1034 default values support for elicitation (#2545)
- Add comprehensive tests for default values in elicitation schemas - Document default values support in elicitation docs - Confirms FastMCP automatically supports defaults via Pydantic Closes #2544
This commit is contained in:
parent
7f8a010798
commit
acf4db0bf5
2 changed files with 167 additions and 1 deletions
|
|
@ -280,6 +280,41 @@ async def create_task(ctx: Context) -> str:
|
|||
return "Task creation cancelled"
|
||||
```
|
||||
|
||||
### Default Values
|
||||
|
||||
You can provide default values for elicitation fields using Pydantic's `Field(default=...)`. Clients will pre-populate form fields with these defaults, making it easier for users to provide input.
|
||||
|
||||
Default values are supported for all primitive types:
|
||||
- Strings: `Field(default="[email protected]")`
|
||||
- Integers: `Field(default=50)`
|
||||
- Numbers: `Field(default=3.14)`
|
||||
- Booleans: `Field(default=False)`
|
||||
- Enums: `Field(default=EnumValue.A)`
|
||||
|
||||
Fields with default values are automatically marked as optional (not included in the `required` list), so users can accept the default or provide their own value.
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, Field
|
||||
from enum import Enum
|
||||
|
||||
class Priority(Enum):
|
||||
LOW = "low"
|
||||
MEDIUM = "medium"
|
||||
HIGH = "high"
|
||||
|
||||
class TaskDetails(BaseModel):
|
||||
title: str = Field(description="Task title")
|
||||
description: str = Field(default="", description="Task description")
|
||||
priority: Priority = Field(default=Priority.MEDIUM, description="Task priority")
|
||||
|
||||
@mcp.tool
|
||||
async def create_task(ctx: Context) -> str:
|
||||
result = await ctx.elicit("Please provide task details", response_type=TaskDetails)
|
||||
if result.action == "accept":
|
||||
return f"Created: {result.data.title}"
|
||||
return "Task creation cancelled"
|
||||
```
|
||||
|
||||
## Multi-Turn Elicitation
|
||||
|
||||
Tools can make multiple elicitation calls to gather information progressively:
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from typing import Literal
|
|||
|
||||
import pytest
|
||||
from mcp.types import ElicitRequestParams
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, Field
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from fastmcp import Context, FastMCP
|
||||
|
|
@ -729,3 +729,134 @@ def test_enum_elicitation_schema_with_enum_names():
|
|||
"Completed",
|
||||
"On Hold",
|
||||
]
|
||||
|
||||
|
||||
class TestElicitationDefaults:
|
||||
"""Test suite for default values in elicitation schemas."""
|
||||
|
||||
def test_string_default_preserved(self):
|
||||
"""Test that string defaults are preserved in the schema."""
|
||||
|
||||
class Model(BaseModel):
|
||||
email: str = Field(default="[email protected]")
|
||||
|
||||
schema = get_elicitation_schema(Model)
|
||||
props = schema.get("properties", {})
|
||||
|
||||
assert "email" in props
|
||||
assert "default" in props["email"]
|
||||
assert props["email"]["default"] == "[email protected]"
|
||||
assert props["email"]["type"] == "string"
|
||||
|
||||
def test_integer_default_preserved(self):
|
||||
"""Test that integer defaults are preserved in the schema."""
|
||||
|
||||
class Model(BaseModel):
|
||||
count: int = Field(default=50)
|
||||
|
||||
schema = get_elicitation_schema(Model)
|
||||
props = schema.get("properties", {})
|
||||
|
||||
assert "count" in props
|
||||
assert "default" in props["count"]
|
||||
assert props["count"]["default"] == 50
|
||||
assert props["count"]["type"] == "integer"
|
||||
|
||||
def test_number_default_preserved(self):
|
||||
"""Test that number defaults are preserved in the schema."""
|
||||
|
||||
class Model(BaseModel):
|
||||
price: float = Field(default=3.14)
|
||||
|
||||
schema = get_elicitation_schema(Model)
|
||||
props = schema.get("properties", {})
|
||||
|
||||
assert "price" in props
|
||||
assert "default" in props["price"]
|
||||
assert props["price"]["default"] == 3.14
|
||||
assert props["price"]["type"] == "number"
|
||||
|
||||
def test_boolean_default_preserved(self):
|
||||
"""Test that boolean defaults are preserved in the schema."""
|
||||
|
||||
class Model(BaseModel):
|
||||
enabled: bool = Field(default=False)
|
||||
|
||||
schema = get_elicitation_schema(Model)
|
||||
props = schema.get("properties", {})
|
||||
|
||||
assert "enabled" in props
|
||||
assert "default" in props["enabled"]
|
||||
assert props["enabled"]["default"] is False
|
||||
assert props["enabled"]["type"] == "boolean"
|
||||
|
||||
def test_enum_default_preserved(self):
|
||||
"""Test that enum defaults are preserved in the schema."""
|
||||
|
||||
class Priority(Enum):
|
||||
LOW = "low"
|
||||
MEDIUM = "medium"
|
||||
HIGH = "high"
|
||||
|
||||
class Model(BaseModel):
|
||||
choice: Priority = Field(default=Priority.MEDIUM)
|
||||
|
||||
schema = get_elicitation_schema(Model)
|
||||
props = schema.get("properties", {})
|
||||
|
||||
assert "choice" in props
|
||||
assert "default" in props["choice"]
|
||||
assert props["choice"]["default"] == "medium"
|
||||
assert "enum" in props["choice"]
|
||||
assert props["choice"]["type"] == "string"
|
||||
|
||||
def test_all_defaults_preserved_together(self):
|
||||
"""Test that all default types are preserved when used together."""
|
||||
|
||||
class Priority(Enum):
|
||||
A = "A"
|
||||
B = "B"
|
||||
|
||||
class Model(BaseModel):
|
||||
string_field: str = Field(default="[email protected]")
|
||||
integer_field: int = Field(default=50)
|
||||
number_field: float = Field(default=3.14)
|
||||
boolean_field: bool = Field(default=False)
|
||||
enum_field: Priority = Field(default=Priority.A)
|
||||
|
||||
schema = get_elicitation_schema(Model)
|
||||
props = schema.get("properties", {})
|
||||
|
||||
assert props["string_field"]["default"] == "[email protected]"
|
||||
assert props["integer_field"]["default"] == 50
|
||||
assert props["number_field"]["default"] == 3.14
|
||||
assert props["boolean_field"]["default"] is False
|
||||
assert props["enum_field"]["default"] == "A"
|
||||
|
||||
def test_mixed_defaults_and_required(self):
|
||||
"""Test that fields with defaults are not in required list."""
|
||||
|
||||
class Model(BaseModel):
|
||||
required_field: str = Field(description="Required field")
|
||||
optional_with_default: int = Field(default=42)
|
||||
|
||||
schema = get_elicitation_schema(Model)
|
||||
props = schema.get("properties", {})
|
||||
required = schema.get("required", [])
|
||||
|
||||
assert "required_field" in required
|
||||
assert "optional_with_default" not in required
|
||||
assert props["optional_with_default"]["default"] == 42
|
||||
|
||||
def test_compress_schema_preserves_defaults(self):
|
||||
"""Test that compress_schema() doesn't strip default values."""
|
||||
|
||||
class Model(BaseModel):
|
||||
string_field: str = Field(default="test")
|
||||
integer_field: int = Field(default=42)
|
||||
|
||||
schema = get_elicitation_schema(Model)
|
||||
props = schema.get("properties", {})
|
||||
|
||||
assert "default" in props["string_field"]
|
||||
assert "default" in props["integer_field"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue