How to Configure a FastMCP Server with Custom Tools and Annotations
You configure a FastMCP server with custom tools and annotations by creating a FastMCP instance, decorating Python functions with @mcp.tool() while providing name, description, and an optional annotations dictionary, then starting the server with mcp.run().
The cr2007/mcp-wordle-python repository provides a complete reference implementation for building Model Context Protocol (MCP) compatible servers in Python. This guide explains how to configure a FastMCP server with custom tools and annotations using the production patterns found in src/mcp_wordle/main.py.
Initializing the FastMCP Server
Every FastMCP application begins with a server instance that holds the registered tools and configuration. In src/mcp_wordle/main.py, the server is instantiated with a descriptive name that identifies the service to MCP clients.
from fastmcp import FastMCP
# Create the MCP server instance
mcp = FastMCP("WordleMCP")
This FastMCP instance acts as the central registry. All subsequent tools attach to this object, and the server name ("WordleMCP") appears in client interfaces when they query available capabilities.
Registering Custom Tools with Decorators
Tools are Python functions exposed to MCP clients through the @mcp.tool() decorator. This decorator transforms regular functions into callable endpoints that clients like Claude Desktop can invoke via JSON-RPC.
The decorator accepts three key parameters:
- name: A unique identifier for the tool (used in API calls)
- description: Human-readable explanation of functionality
- annotations: Optional dictionary of metadata hints
@mcp.tool(
name="echo_message",
description="Returns the supplied message unchanged.",
annotations={"readOnlyHint": False, "experimental": True},
)
def echo_message(message: str) -> dict:
"""Simple echo tool used for demonstration."""
return {"echo": message}
Functions can be synchronous or async. Return values use standard Python types (dict, list, str), which FastMCP automatically serializes to JSON for the client.
Configuring Tool Annotations
Annotations provide metadata that MCP clients use to adjust UI behavior and visibility. The FastMCP library passes these values directly to the client without modification, following the MCP specification.
Common annotation keys include:
- readOnlyHint: Set to
Truewhen a tool does not modify state (e.g., queries) - experimental: Flags the tool as unstable or in development
- hidden: Prevents the tool from appearing in autocomplete lists
- Custom keys: Domain-specific flags like
requires_auth
In the Wordle implementation, the built-in tool uses readOnlyHint to indicate safe, non-destructive operations:
@mcp.tool(
name="get_wordle_solution",
description="Returns a random Wordle solution from a curated list.",
annotations={"readOnlyHint": True},
)
def get_wordle_solution() -> dict:
"""Fetch a random 5-letter wordle answer."""
# Implementation details...
return {"solution": word}
The client decides how to render these hints. The server merely forwards the dictionary defined in src/mcp_wordle/main.py.
Running the Server and Client Configuration
To start accepting connections, invoke mcp.run() within the entry point guard. In src/mcp_wordle/main.py, this appears at lines 70-71:
if __name__ == "__main__":
mcp.run()
The project defines a console script in pyproject.toml that exposes this entry point as the command mcp-wordle:
[project.scripts]
mcp-wordle = "mcp_wordle.main:mcp.run"
To connect this server to Claude Desktop, add a configuration block to your MCP servers JSON file:
{
"mcpServers": {
"Wordle MCP (Python)": {
"command": "uvx",
"args": [
"--from",
"git+https://github.com/cr2007/mcp-wordle-python",
"mcp-wordle"
]
}
}
}
When Claude Desktop loads, it queries the server's /tools endpoint, discovers all registered tools including their annotations, and surfaces them in the interface according to the metadata provided.
Summary
- FastMCP instantiation: Create a server with
FastMCP("ServerName")to establish the tool registry. - Tool registration: Use
@mcp.tool(name=..., description=..., annotations=...)to expose Python functions. - Annotations: Pass metadata like
readOnlyHintorexperimentalas a dictionary to guide client UI behavior. - Execution: Launch via
mcp.run()or the configured console script (mcp-wordle). - Integration: Configure Claude Desktop or other MCP clients to execute the server command and discover tools automatically.
Frequently Asked Questions
What parameters does the @mcp.tool decorator accept?
The decorator accepts name (string identifier), description (string explanation), and annotations (dictionary of metadata). All parameters are optional but recommended for proper client integration. The function signature defines the tool's input schema.
How do annotations affect tool behavior?
Annotations do not change server-side logic. They provide hints to MCP clients like Claude Desktop, which may use readOnlyHint to style tools differently, hidden to exclude them from lists, or experimental to display warning labels. The server passes annotations through unchanged.
Can I use async functions as FastMCP tools?
Yes. FastMCP supports both synchronous and asynchronous Python functions. Define your tool with async def when performing non-blocking I/O operations, and the server handles the event loop integration automatically.
How do I add the server to Claude Desktop permanently?
Add a JSON configuration object to your Claude Desktop settings under mcpServers, specifying the launch command (e.g., uvx or docker) and arguments pointing to your package. The server persists across Claude sessions and reloads when the application starts.
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 →