Skip to main content
Glama
fastmcp-parameter-annotations.md8.93 kB
# FastMCP Parameter Type Annotations: The Complete Guide ## Overview This guide documents the correct patterns for defining parameter type annotations in FastMCP tools to ensure proper type introspection and avoid "unknown" parameter types in MCP client interfaces. ## The Problem When using FastMCP, parameters can show up as "unknown" type in MCP clients, making it difficult for users and LLMs to understand what types of values are expected. This issue is caused by incorrect type annotation patterns that confuse FastMCP's Pydantic-based type introspection system. ## The Solution: Working Patterns ### ✅ Required Parameters For required parameters, use this pattern: ```python from typing import Annotated from pydantic import Field @mcp.tool def example_tool( # Required string with validation host_id: Annotated[str, Field(description="Host identifier", min_length=1)], # Required integer with constraints port: Annotated[int, Field(description="Port number", ge=1, le=65535)], # Required literal (enum-like) action: Annotated[Literal["start", "stop", "restart"], Field(description="Action to perform")], # Required boolean enabled: Annotated[bool, Field(description="Whether feature is enabled")] ) -> dict: pass ``` ### ✅ Optional Parameters For optional parameters, **always include `default=` in the Field AND provide a default value**: ```python @mcp.tool def example_tool( # Optional string ssh_key_path: Annotated[str, Field(default="", description="Path to SSH private key file")] = "", # Optional integer with constraints timeout: Annotated[int, Field(default=30, ge=1, le=300, description="Timeout in seconds")] = 30, # Optional boolean test_connection: Annotated[bool, Field(default=True, description="Test connection")] = True, # Optional list with default_factory tags: Annotated[list[str], Field(default_factory=list, description="List of tags")] = [], # Optional dict with default_factory metadata: Annotated[dict[str, str], Field(default_factory=dict, description="Metadata")] = {}, # Optional enum-like parameter (use str, not Literal for optional) log_level: Annotated[str, Field(default="info", description="Log level")] = "info" ) -> dict: pass ``` ### Key Rules for Optional Parameters 1. **Use `default=` in Field** - Always specify the default value in the Field definition 2. **Provide default value after `=`** - Also provide the default value after the parameter annotation 3. **No `| None` unions** - Avoid union types entirely for FastMCP compatibility 4. **Use `default_factory=` for collections** - For lists/dicts, use `default_factory=list` or `default_factory=dict` 5. **Use `str` instead of `Literal` for optional enums** - Literal types in optional parameters cause issues ## ❌ Patterns That Don't Work ### Union Types Inside Annotated ```python # ❌ WRONG - Union inside Annotated ssh_key_path: Annotated[str | None, Field(description="Path to SSH private key")] # ❌ WRONG - Optional with union inside Annotated tags: Annotated[list[str] | None, Field(description="Host tags")] ``` ### Union Types Outside Annotated ```python # ❌ WRONG - Union outside Annotated ssh_key_path: Annotated[str, Field(description="Path to SSH private key")] | None # ❌ WRONG - This also doesn't work compose_path: Annotated[str, Field(description="Docker Compose file path")] | None = None ``` ### Missing Default in Field ```python # ❌ WRONG - Missing default= in Field ssh_key_path: Annotated[str, Field(description="Path to SSH private key")] = "" # ❌ WRONG - FastMCP needs explicit default in Field timeout: Annotated[int, Field(ge=1, le=300, description="Timeout")] = 30 ``` ### Simple Union Types Without Field ```python # ❌ WRONG - Simple union types show as "unknown" ssh_key_path: str | None = None tags: list[str] | None = None ``` ## Complete Example Here's a complete example showing the correct patterns: ```python from typing import Annotated, Literal from pydantic import Field from fastmcp import FastMCP mcp = FastMCP(name="ExampleServer") @mcp.tool async def manage_host( # Required parameters action: Annotated[Literal["add", "remove", "update"], Field(description="Action to perform")], host_id: Annotated[str, Field(description="Host identifier", min_length=1)], # Optional string parameters ssh_key_path: Annotated[str, Field(default="", description="Path to SSH private key file")] = "", description: Annotated[str, Field(default="", description="Host description")] = "", # Optional integer parameters ssh_port: Annotated[int, Field(default=22, ge=1, le=65535, description="SSH port number")] = 22, timeout: Annotated[int, Field(default=30, ge=1, le=300, description="Connection timeout")] = 30, # Optional boolean parameters test_connection: Annotated[bool, Field(default=True, description="Test connection when adding host")] = True, enabled: Annotated[bool, Field(default=True, description="Whether host is enabled")] = True, # Optional collection parameters tags: Annotated[list[str], Field(default_factory=list, description="Host tags")] = [], metadata: Annotated[dict[str, str], Field(default_factory=dict, description="Host metadata")] = {}, # Optional enum-like parameters (use str for optional) log_level: Annotated[str, Field(default="info", description="Log level (debug, info, warn, error)")] = "info", cleanup_type: Annotated[str, Field(default="", description="Type of cleanup to perform")] = "", # Advanced validation examples email: Annotated[str, Field(default="", pattern=r"^[\w\.-]+@[\w\.-]+\.\w+$", description="Email address")] = "", schedule_time: Annotated[str, Field(default="", pattern=r"^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$", description="Time in HH:MM format")] = "" ) -> dict[str, Any]: """Manage Docker hosts with comprehensive parameter validation.""" return {"success": True, "action": action, "host_id": host_id} ``` ## Validation Constraints FastMCP supports all Pydantic validation constraints: ```python # Numeric constraints count: Annotated[int, Field(default=0, ge=0, le=100, description="Count (0-100)")] = 0, ratio: Annotated[float, Field(default=1.0, gt=0, lt=2.0, description="Ratio (0-2)")] = 1.0, multiple: Annotated[int, Field(default=10, multiple_of=5, description="Must be multiple of 5")] = 10, # String constraints name: Annotated[str, Field(default="", min_length=1, max_length=50, description="Name (1-50 chars)")] = "", pattern_field: Annotated[str, Field(default="", pattern=r"^[A-Z]{2}\d{4}$", description="Pattern: XX0000")] = "", # Collection constraints items: Annotated[list[str], Field(default_factory=list, min_length=0, max_length=10, description="0-10 items")] = [], ``` ## Troubleshooting "Unknown" Parameter Types ### Step 1: Check the Pattern Ensure your parameters follow the correct pattern: ```python # ✅ CORRECT parameter: Annotated[Type, Field(default=default_value, description="...")] = default_value ``` ### Step 2: Avoid Union Types If a parameter shows as "unknown", check if it uses union types: ```python # ❌ Remove this parameter: str | None = None # ✅ Replace with this parameter: Annotated[str, Field(default="", description="...")] = "" ``` ### Step 3: Add Explicit Defaults Ensure both `default=` in Field and `= value` are present: ```python # ❌ Missing default in Field parameter: Annotated[str, Field(description="...")] = "" # ✅ Add explicit default parameter: Annotated[str, Field(default="", description="...")] = "" ``` ### Step 4: Use default_factory for Collections For lists and dicts, use `default_factory`: ```python # ✅ Lists tags: Annotated[list[str], Field(default_factory=list, description="Tags")] = [] # ✅ Dicts metadata: Annotated[dict[str, str], Field(default_factory=dict, description="Metadata")] = {} ``` ### Step 5: Test MCP Introspection After making changes: 1. Restart your FastMCP server 2. Reconnect your MCP client 3. Check that parameters show proper types (string, integer, boolean, array, object) 4. If any still show "unknown", verify they follow the patterns above ## Summary The key to proper FastMCP parameter type annotations: 1. **Required**: `Annotated[Type, Field(description="...")]` 2. **Optional**: `Annotated[Type, Field(default=value, description="...")] = value` 3. **Collections**: Use `default_factory=list` or `default_factory=dict` 4. **No Unions**: Avoid `| None` entirely 5. **Explicit Defaults**: Always specify `default=` in Field for optional parameters 6. **Use `str` for optional enums**: Instead of `Literal` for optional enum-like parameters Following these patterns ensures all parameters appear with correct types in MCP client interfaces, providing better usability for both human users and LLMs.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jmagar/docker-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server