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

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, the tool registration occurs as follows:

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 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:

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 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 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().

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →