# How to Configure a FastMCP Server with Custom Tools and Annotations

> Configure a FastMCP server with custom tools and annotations. Learn to decorate Python functions with @mcp.tool and set up annotations for enhanced functionality. Run your custom server easily.

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

---

**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`](https://github.com/cr2007/mcp-wordle-python/blob/main/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`](https://github.com/cr2007/mcp-wordle-python/blob/main/src/mcp_wordle/main.py), the server is instantiated with a descriptive name that identifies the service to MCP clients.

```python
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

```python
@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 `True` when 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:

```python
@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`](https://github.com/cr2007/mcp-wordle-python/blob/main/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`](https://github.com/cr2007/mcp-wordle-python/blob/main/src/mcp_wordle/main.py), this appears at lines 70-71:

```python
if __name__ == "__main__":
    mcp.run()

```

The project defines a console script in [`pyproject.toml`](https://github.com/cr2007/mcp-wordle-python/blob/main/pyproject.toml) that exposes this entry point as the command `mcp-wordle`:

```toml
[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:

```json
{
  "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 `readOnlyHint` or `experimental` as 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.