Implementing Read-Only Tools in FastMCP Using Annotations: A Complete Guide
You mark a FastMCP tool as read-only by passing annotations={"readOnlyHint": True} to the @mcp.tool() decorator, which signals to client UIs that the function performs only data retrieval without side-effects.
The FastMCP framework streamlines building Model Context Protocol (MCP) servers in Python, but distinguishing between mutating and non-mutating operations requires explicit metadata. In the Wordle MCP (Python) repository (cr2007/mcp-wordle-python), developers demonstrate implementing read-only tools using annotation parameters to flag HTTP fetch operations as safe, side-effect-free queries.
Understanding FastMCP Annotations for Read-Only Tools
FastMCP utilizes an annotations dictionary within the @mcp.tool() decorator to communicate tool behavior metadata to downstream clients. While the Python runtime does not enforce these constraints, the protocol serializes this metadata for UI interpretation.
The readOnlyHint key specifically indicates that a tool executes only read operations—fetching external data or querying state without creating, updating, or deleting resources. This distinction prevents conversational agents from accidentally invoking data-retrieval functions in write-oriented workflows.
The Architecture of Read-Only Tool Registration
| Component | Function | Source Location |
|---|---|---|
| FastMCP runtime | Parses fastmcp.json to locate entry-points and manage dependencies |
fastmcp.json |
| Entry-point script | Instantiates FastMCP, defines tools, launches server |
src/mcp_wordle/main.py |
| Tool definition | Registers callable with metadata via @mcp.tool() |
Lines 30-38 of main.py |
| Read-only signal | annotations={"readOnlyHint": True} marks tool as non-mutating |
Decorator block in main.py |
| HTTP implementation | requests.get() fetches Wordle data from NYTimes API |
Lines 64-67 of main.py |
Implementing readOnlyHint in src/mcp_wordle/main.py
According to the source code in cr2007/mcp-wordle-python, the get_wordle_solution tool implements the read-only pattern by combining the annotation with an HTTP GET request. The decorator registers the function while the annotations parameter advertises its non-mutating nature to MCP clients like Claude Desktop.
from fastmcp import FastMCP
import requests
mcp = FastMCP("WordleMCP")
@mcp.tool(
name="get_wordle_solution",
description="Fetch the Wordle solution for a given date (2021-05-19 … +23 days).",
annotations={"readOnlyHint": True},
)
async def get_wordle_data(target_date: str = "2024-01-01"):
"""Return the JSON payload from the official Wordle endpoint."""
url = f"https://www.nytimes.com/svc/wordle/v2/{target_date}.json"
return requests.get(url, timeout=10).json()
if __name__ == "__main__":
mcp.run()
When mcp.run() executes, FastMCP serializes the readOnlyHint: true flag into the tool's capability description. Clients receiving this metadata understand that invoking get_wordle_solution with a target_date argument performs only external data retrieval and never modifies server state.
How the readOnlyHint Annotation Propagates
The annotation mechanism operates through three distinct phases:
-
Definition Phase – The
@mcp.tool()decorator receives theannotationsdictionary containingreadOnlyHint: True. FastMCP stores this metadata alongside the function reference. -
Protocol Serialization – Upon server startup, FastMCP advertises available tools through the MCP protocol, embedding the annotations into the tool's JSON description.
-
UI Interpretation – Front-end applications like Claude Desktop parse the
readOnlyHintflag and render the tool in read-only palettes or restrict its usage in write-heavy prompt contexts, preventing accidental side-effects.
This separation of concerns allows developers to flag pure-fetch functionality without modifying the underlying Python logic or adding runtime checks.
Client Integration and Deployment Patterns
The Wordle MCP server supports multiple deployment methods, each preserving the read-only annotations through the protocol handshake.
Docker-Based Deployment
Add this configuration to your MCP client settings to run the containerized server:
{
"mcpServers": {
"Wordle MCP (Python)": {
"command": "docker",
"args": [
"run", "--rm", "-i", "--init",
"-e", "DOCKER_CONTAINER=true",
"ghcr.io/cr2007/mcp-wordle-python:latest"
]
}
}
}
Direct uvx Execution
For Python environments with uv installed, invoke the tool directly from the GitHub repository:
{
"mcpServers": {
"Wordle MCP (Python)": {
"command": "uvx",
"args": [
"--from",
"git+https://github.com/cr2007/mcp-wordle-python",
"mcp-wordle"
]
}
}
}
Testing the Read-Only Tool
You can verify the implementation locally without the full server infrastructure:
from src.mcp_wordle.main import get_wordle_data
result = get_wordle_data("2023-09-20")
print(result["solution"]) # => e.g., "crane"
The function returns the Wordle solution JSON without any state mutation, consistent with the readOnlyHint contract.
Summary
- FastMCP annotations use the
annotationsparameter in@mcp.tool()to communicate tool behavior metadata. readOnlyHint: Trueexplicitly marks tools as non-mutating data retrieval operations.- Implementation requires only adding the dictionary to the decorator, without changing function logic.
- Client UIs consume these hints to prevent read-only tools from appearing in write-oriented workflows.
- Deployment via Docker or
uvxpreserves annotation metadata through the MCP protocol handshake.
Frequently Asked Questions
What is the readOnlyHint annotation in FastMCP?
The readOnlyHint is a boolean flag within the annotations dictionary of the @mcp.tool() decorator. It signals to MCP clients that the associated function performs only data retrieval operations and never modifies server state or external resources.
How do I mark a FastMCP tool as read-only?
Pass annotations={"readOnlyHint": True} as an argument to the @mcp.tool() decorator when defining your function. FastMCP will include this metadata in the tool's protocol description, allowing clients to categorize the tool appropriately.
Does FastMCP enforce read-only behavior server-side?
No, FastMCP does not enforce the readOnlyHint constraint at runtime. The annotation serves as metadata for client-side UI interpretation only. Developers must ensure their tool implementation actually avoids side-effects, as the flag operates on an honor system.
Can I combine readOnlyHint with other annotations in FastMCP?
Yes, the annotations dictionary accepts multiple key-value pairs. You can combine readOnlyHint with other metadata flags like experimental or custom hints to provide additional context to MCP clients about tool maturity or specific behavioral characteristics.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →