# Default Parameter Handling in MCP Tool Functions: A Deep Dive into the Wordle Example

> Understand default parameter handling in MCP tool functions. Learn how import-time evaluation can lead to stale defaults, using the Wordle example to illustrate.

- Repository: [CSK/mcp-wordle-python](https://github.com/cr2007/mcp-wordle-python)
- Tags: deep-dive
- Published: 2026-02-28

---

**Default parameters in MCP tool functions are evaluated at import time, not runtime, causing dynamic defaults like `date.today()` to become stale during long-running processes.**

The `cr2007/mcp-wordle-python` repository demonstrates how FastMCP registers Python functions as remote tools, revealing a critical Python behavior that affects default parameter handling in MCP tool functions. Understanding when default arguments are evaluated prevents bugs where "today" defaults to yesterday's date after the server has been running for days.

## How FastMCP Registers Tool Functions

FastMCP converts ordinary Python callables into discoverable remote tools through decorator-based registration. When the module loads, the `@mcp.tool` decorator captures the function object, its type hints, docstrings, and **default values** at that exact moment.

In [`src/mcp_wordle/main.py`](https://github.com/cr2007/mcp-wordle-python/blob/main/src/mcp_wordle/main.py), the tool registration occurs as follows:

```python
from fastmcp import FastMCP
from datetime import date
from typing import Union

mcp = FastMCP("WordleMCP")

@mcp.tool(
    name="get_wordle_solution",
    description=(
        "Fetches the Wordle of a particular date provided "
        "between 2021-05-19 to 23 days future"
    ),
    annotations={"readOnlyHint": True},
)
async def get_wordle_data(
    target_date: str = date.today().isoformat(),
) -> Union[WordleAPIData, WordleError]:
    ...

```

Because Python evaluates default arguments when the function is **defined** (not when it is called), `date.today().isoformat()` executes immediately when the module imports. The resulting string becomes a static default for the entire process lifetime.

## The Static Default Trap in Python

The evaluation timing creates a classic Python pitfall where dynamic defaults become frozen. If the MCP server starts on `2025-06-27`, the `target_date` parameter defaults to `"2025-06-27"` forever, even when the server runs into the next day.

This behavior manifests in several scenarios:

- **Long-running processes**: Servers running for days or weeks will return stale "today" values
- **Worker pools**: Multiple workers inherit the same pre-computed default from their parent process
- **Hot-reloading**: Only module re-imports refresh the default, not individual tool invocations

The current implementation in [`src/mcp_wordle/main.py`](https://github.com/cr2007/mcp-wordle-python/blob/main/src/mcp_wordle/main.py) lines 30-40 exhibits exactly this pattern, making the tool's "today" default potentially inaccurate for persistent MCP servers.

## Implementing Dynamic Default Parameters

To ensure `date.today()` evaluates fresh on every invocation, replace the dynamic default with a **sentinel value** and compute the actual default inside the function body. This pattern moves evaluation from import-time to runtime.

The recommended implementation modifies `get_wordle_data` as follows:

```python
async def get_wordle_data(
    target_date: str | None = None,
) -> Union[WordleAPIData, WordleError]:
    """Return Wordle data for `target_date` (defaults to today)."""
    if target_date is None:
        target_date = date.today().isoformat()
    
    url = f"https://www.nytimes.com/svc/wordle/v2/{target_date}.json"
    return requests.get(url, timeout=300).json()

```

FastMCP treats `None` as a valid argument value and does not replace it with a default at registration time. The runtime logic you implement determines the final argument value, giving you full control over dynamic default behavior.

## Summary

- **Default parameters in MCP tool functions are evaluated at import time**, not when the tool is called, causing dynamic values like `date.today()` to become static.
- The `cr2007/mcp-wordle-python` repository demonstrates this behavior in [`src/mcp_wordle/main.py`](https://github.com/cr2007/mcp-wordle-python/blob/main/src/mcp_wordle/main.py) where `target_date` defaults to the startup date.
- **Use the sentinel pattern** (`None` as default) to defer evaluation to runtime, ensuring fresh dynamic values on every tool invocation.
- FastMCP passes `None` values through without substitution, allowing function-body logic to handle dynamic defaults correctly.

## Frequently Asked Questions

### Why are my MCP tool defaults not updating daily?

Python evaluates default arguments when the function is defined, not when it is called. If your MCP server started three days ago, `date.today()` was evaluated at startup and remains frozen at that date. To get fresh dates, use `None` as the default and compute `date.today()` inside the function body.

### What is the sentinel pattern in Python?

The sentinel pattern uses a placeholder value (typically `None`) as a default argument to indicate "no value provided." Inside the function, you check for the sentinel and compute the actual default value at runtime. This avoids the common Python pitfall where mutable or dynamic defaults are evaluated once at definition time.

### How does FastMCP handle None values in tool parameters?

FastMCP treats `None` as a valid argument value and does not substitute it with a registration-time defaults. When a client omits a parameter or passes `null`, FastMCP passes `None` to your function, allowing your runtime logic to handle dynamic defaults correctly.

### Where is the tool registration logic in the Wordle MCP repository?

The tool registration occurs in [`src/mcp_wordle/main.py`](https://github.com/cr2007/mcp-wordle-python/blob/main/src/mcp_wordle/main.py) between lines 30-40. The `@mcp.tool` decorator registers `get_wordle_data` with FastMCP, capturing the function signature including the `target_date` parameter with its default value of `date.today().isoformat()`.