Skip to content

Build an MCP Server with FastMCP 3.2.4: A Practical Guide

Deploy a working MCP server with FastMCP 3.2.4 in minutes. Real install commands, version traps, and gotchas competitors don't mention.

7 min readIntermediate

End goal: a running MCP server that Claude Desktop, Cursor, or any MCP-compatible host can talk to – built on FastMCP, verified with the MCP Inspector, deployed in under ten minutes. No calculator demo. We’ll wire up a tiny file-watcher tool that returns the most recently modified files in a folder, because that’s the kind of thing you’d actually want an LLM to call.

Before any of that works, you have to land on the right FastMCP. That sentence is doing more work than it looks.

Three projects named fastmcp – pick carefully

Search “build MCP server” and you’ll hit three unrelated projects that all call themselves fastmcp. Picking the wrong one is the single most common time-sink for new users.

Package Language Latest Use when
PrefectHQ/fastmcp (Python) Python 3.2.4 This guide. Default choice.
punkpeye/fastmcp TypeScript v3.31.0 Node/Cloudflare Workers stack
yjacquin/fast-mcp Ruby Rails apps

The Python package fastmcp on PyPI shipped 3.2.4 on April 14, 2026, and that’s what we’re installing. FastMCP 3.0 went GA on February 18, 2026 after two betas and an RC, so anything written before then describes a different API surface. Pin your dependency.

One more landmine: FastMCP 1.0 was folded into the official MCP Python SDK back in 2024. The standalone package – the actively maintained fork – now pulls around a million downloads a day and reportedly powers roughly 70% of MCP servers in production (per the PrefectHQ/fastmcp README, as of April 2026). That creates an invisible trap: from mcp.server.fastmcp import FastMCP (FastMCP 1.0, frozen inside the SDK) and from fastmcp import FastMCP (3.x, current standalone) look identical in code samples. They are not the same library. Tutorials mix them silently.

System requirements

  • Python: >=3.10, with wheels for 3.10, 3.11, 3.12, and 3.13. 3.12 is the sweet spot.
  • OS: macOS, Linux, Windows. Claude Desktop integration is macOS/Windows only (as of April 2026).
  • RAM/disk: Estimates only – a hello-world server is lightweight; heavier tools that load models or large files will need more.
  • Package manager:uv recommended, pip works.

Install FastMCP 3.2.4

The two-line path:

# with uv (recommended)
uv init mcp-watcher && cd mcp-watcher
uv add "fastmcp==3.2.4"

# or with pip
python -m venv .venv && source .venv/bin/activate
pip install "fastmcp==3.2.4"

Pin the exact version. A future minor release with a constructor change can break working code without warning – which brings us to the next thing.

Compliance note: If your organization runs a license scanner, install with pip install "fastmcp==3.2.4" "cyclopts>=5.0.0a1". Cyclopts v4 carries docutils as a transitive dependency; gofastmcp.com documents that its licensing can trigger compliance reviews. Cyclopts v5 alpha removes it. The alpha is fine for production CLI use – you’re not touching experimental features.

Write the server

Create watcher.py:

from fastmcp import FastMCP
from pathlib import Path

mcp = FastMCP("file-watcher")

@mcp.tool
def recent_files(folder: str, limit: int = 5) -> list[dict]:
 """Return the most recently modified files in a folder."""
 p = Path(folder).expanduser().resolve()
 if not p.is_dir():
 return [{"error": f"{p} is not a directory"}]
 files = sorted(
 (f for f in p.iterdir() if f.is_file()),
 key=lambda f: f.stat().st_mtime,
 reverse=True,
 )[:limit]
 return [
 {"name": f.name, "size": f.stat().st_size, "mtime": f.stat().st_mtime}
 for f in files
 ]

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

Complete server, right there. Type hints and the docstring feed directly into the MCP tool schema – no separate schema definition needed. No dependencies= kwarg on the constructor either. According to the FastMCP 3.0 changelog, 16 constructor kwargs were dropped after months of deprecation warnings, and ui= became app= with a new AppConfig class. Old tutorials still pass those kwargs and now get a TypeError for it.

Worth pausing here: the decorator-plus-docstring pattern is genuinely elegant, but it also means your docstring is your API documentation. A vague docstring becomes a vague tool description inside Claude’s context window. That’s a product decision as much as a coding one.

Verify it works

Two ways to confirm the server is alive.

Version sanity check:

fastmcp version

You should see FastMCP version, MCP version, Python version, and platform. If fastmcp isn’t found, your venv isn’t active.

Inspector check:

fastmcp dev watcher.py

This launches the MCP Inspector – a debugging interface that opens at http://127.0.0.1:6274. Click Connect, open the Tools tab, click recent_files, pass ~/Downloads as the folder, hit Run. If you see your last five downloads, you’re done.

Common errors and what they actually mean

TypeError: FastMCP.__init__() got an unexpected keyword argument ‘dependencies’

You (or a package you installed) is on FastMCP 3.x but the code targets 2.x. The awslabs core-mcp-server hit exactly this in issue #1982 – the dependencies parameter was removed in 3.0. Two fixes: drop the kwarg, or pin fastmcp<3 if you can’t touch upstream code. The 2→3 upgrade guide on gofastmcp.com lists every removed kwarg.

ImportError on fastmcp[tasks]

Installed the optional tasks extra and got an import error at startup? That’s a transitive-dep regression. fakeredis 2.35.0 shipped an undocumented rename – FakeConnection became FakeAsyncRedisConnection – and broke the internal async backend that fastmcp[tasks] depends on. Current releases pin fakeredis<2.35.0 as a stopgap (per the PrefectHQ release notes). If you’re on a cached environment, pip install --upgrade --force-reinstall fastmcp usually clears it.

ModuleNotFoundError: No module named ‘mcp’

You’re running from outside the venv, or installed FastMCP globally and the script picked up the system Python. which python and which fastmcp should resolve to the same prefix. If they don’t, open a fresh shell.

Claude Desktop says “server disconnected”

Almost always a Python interpreter mismatch. Claude launches your server with whatever python is on its PATH at app-start time – not your venv’s. Edit claude_desktop_config.json to point command at the venv’s interpreter explicitly, or use uv run --python 3.12 fastmcp run watcher.py as the command.

Upgrading from FastMCP 2.x

How bad is the 2→3 jump? Less bad than the changelog implies. The surface API is largely intact – @mcp.tool() still works exactly as before. The pain is concentrated in those 16 removed constructor kwargs: if you used any of them, the traceback names them immediately.

  1. Bump in your lockfile: uv add "fastmcp==3.2.4" or pip install -U "fastmcp==3.2.4".
  2. Run your server with fastmcp run server.py. A TypeError in the traceback names the offending kwarg.
  3. Replace ui= with app= wrapped in AppConfig.
  4. If you imported from mcp.server.fastmcp (FastMCP 1.0 inside the SDK), change one line: from fastmcp import FastMCP. Most servers need nothing else.

The trickier question is whether to migrate at all if 2.x is working. The standalone package gets active fixes; the SDK-bundled 1.0 is frozen. For anything production-facing, staying on a frozen dependency is the bigger risk.

Uninstall

uv remove fastmcp # or: pip uninstall fastmcp
rm -rf .venv # nuke the project venv
# clean Claude Desktop config:
# edit ~/Library/Application Support/Claude/claude_desktop_config.json
# and remove your server entry from "mcpServers"

FastMCP doesn’t write anywhere outside your venv and Claude’s config file – that’s the whole cleanup.

FAQ

Should I use the standalone fastmcp package or the FastMCP class inside the official mcp SDK?

Standalone. The SDK ships a frozen 1.0 copy; the standalone package is where bug fixes actually land.

Can I run a FastMCP server over HTTP instead of stdio for local testing?

Yes – replace mcp.run() with mcp.run(transport="http", port=8000), then point the Inspector at http://localhost:8000/mcp. Stdio is the default because Claude Desktop spawns servers as subprocesses. For browser-based clients or remote deployments, HTTP transport is the right call – it supports multiple simultaneous connections, whereas stdio is single-client. Check the gofastmcp.com docs for the current default transport name; HTTP transport naming conventions may have changed since this was written.

Do I need to learn JSON-RPC or the MCP wire protocol to use FastMCP?

No. FastMCP generates schemas from your type hints and handles the JSON-RPC layer. You’ll only see the protocol if you’re writing custom transports – which is genuinely rare.

Now wire your watcher into Claude Desktop: open ~/Library/Application Support/Claude/claude_desktop_config.json, add an entry under mcpServers pointing command at your venv’s fastmcp binary and args at ["run", "/absolute/path/to/watcher.py"], restart Claude, and ask it what was in your Downloads folder this morning.