FastMCP Tutorial: Build Python MCP Servers in Minutes

06 October 2026 · updated 18 October 2026 · 2,871 words

Professional header image for educational tutorial: FastMCP Tutorial: Build and Ship MCP Servers in Python

Model Context Protocol servers are easy to describe and surprisingly fiddly to ship. You need a schema, a transport, client compatibility, authentication, and a lifecycle you can trust. FastMCP exists to collapse that surface area. It bills itself as the fast, Pythonic way to build MCP servers and clients, with one coherent API for servers and clients. Under the hood, it turns ordinary Python functions, classes, and data sources into discoverable tools that hosts like Claude Desktop and Cursor can call safely.

This tutorial walks through fastmcp from first principles to production choices. You will see what FastMCP is in one screen, install it with two commands, and build a working server in about five minutes. From there, we connect clients, explore the v3 CLI with discover, generate-cli, and Agent Skills, and clear up the PrefectHQ versus fastmcp-me naming confusion. We will also cover the decision point between self-hosting and pasting an endpoint, plus a production checklist and the limits worth knowing. By the end, you will have enough context to ship one MCP tool today.

What FastMCP Is, in One Screen

What FastMCP Is, in One Screen
What FastMCP Is, in One Screen

FastMCP is a Python framework from PrefectHQ that wraps the Model Context Protocol into decorators. Mark a function with @mcp.tool and it becomes a tool any compliant host can discover and call. If the protocol layer is still new to you, The Backbone: Model Context Protocol (MCP) Servers covers the transport and primitive model this sits on top of.

Its positioning is narrow on purpose: "the fast, Pythonic way to build MCP servers and clients." That means one coherent API for servers and clients rather than two libraries that disagree with each other.

The framework absorbs the boring parts. When a client connects to a URL, FastMCP handles protocol negotiation, authentication, and connection lifecycle. You write functions; it writes JSON-RPC, the wire format the MCP specification defines and the reason hand-rolled handlers are error-prone.

What it is not: not a hosted service, not a registry, not a replacement for your own server process. It is the library you use to build one, and it stops there.

Three things it removes from your codebase:

  • Hand-rolled JSON-RPC handlers.

  • Manually written JSON schemas. These get generated from your type hints at runtime instead.

  • Per-transport wiring. stdio versus Streamable HTTP stops being a branching decision in your source.

Honest limitation: version churn is real, the release line moved from 3.2.4 to 4.0.10 within months, so pin your version in requirements.txt and read release notes before upgrading anything in production.

Installing it takes two commands.

Prerequisites and a Two-Command Install

Before you write a single tool, settle two variables: the interpreter and the package. Everything downstream depends on both.

Check Python first.

python --version

Check the minimum Python version at gofastmcp.com/getting-started/installation before installing. A version-mismatch bug report wastes your afternoon and the maintainer's. Confirm the version before you file anything against the framework.

Install with one command.

pip install fastmcp

(or uv add fastmcp, confirm in the docs at gofastmcp.com)

That is the whole setup. No scaffolding, no config file, no build step. It is one direct dependency, not one package total; the resolver pulls in dozens of transitive packages, so commit a lockfile.

Verify what you actually installed. The name is contested, so check the metadata:

pip show fastmcp

You want the PrefectHQ project: homepage gofastmcp.com, source at github.com/PrefectHQ/fastmcp. If the metadata points elsewhere, you are on a different distribution with the same name.

No account, no key. Building and running a server on localhost requires no signup and no API key. Authentication becomes a real design concern only when you expose the endpoint to another machine or another person. Keep local development token-free.

One file is enough. A first server fits in a single .py file. No project layout, no package, no build step. Prove the concept works before you organize it.

One virtualenv per project. Version churn is real: the release line moved from 3.2.4 to 4.0.10 within months. Two repos pinning different versions into one global environment is the most common avoidable failure in this stack. Isolate, then pin.

Those prerequisites cover the client side too. Once they are met, the first server is a five-minute job.

Your First Working Server in About Five Minutes

With fastmcp installed, one file is a compliant server:

from fastmcp import FastMCP

mcp = FastMCP("demo")

@mcp.tool()
def add(a: int, b: int) -> int:
 """Use for exact integer arithmetic only. Returns the sum. Rejects floats."""
 return a + b

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

Pass transport="http" for Streamable HTTP or transport="stdio" when an editor or another process spawns the server. The MCP specification defines stdio as newline-delimited JSON-RPC over the standard streams of a client-launched subprocess, so the choice is about who starts the process, not about what the tools do.

Your type hints become the JSON schema, and your docstring becomes the tool description the model actually reads. Write the docstring as prompt engineering, not documentation. State when to call the tool, what it returns, and what it refuses. "Add two integers" is weaker than "Use for exact integer arithmetic only. Returns an int. Rejects floats."

Return values must be serializable. For a large payload, return a summary plus a handle, then add a second tool that fetches the rest by that handle. The model pays tokens for every character, so truncation is a feature.

Run it, then verify both halves:

python server.py

Point a client at the local endpoint printed in the server's startup output and confirm the handshake. A server that starts is not a server that responds; those are two separate checks.

Wrap every tool body in try/except and return an actionable error string:

except ValueError:
 return f"lat must be between -90 and 90; got {lat}"

A stack trace in the error field is unreadable to a model and useless to the next developer debugging the same call. When you are ready to hand the file to a team instead of a terminal, see packaging it as a Claude Code plugin.

Connecting a Client: Claude, Cursor, and Everything Else

Your server runs. Now a client has to find it.

Same block, different transport key

Claude Desktop, Claude Code, and Cursor all read the same mcpServers shape. For a stdio server:

{"mcpServers": {"demo": {"command": "python", "args": ["server.py"]}}}

For an HTTP one:

{"mcpServers": {"demo": {"url": "http://localhost:8000/mcp"}}}

Same block, different transport key. The full Claude config walkthrough is in Connecting an MCP Server to Claude in 3 Steps.

FastMCP handles transport framing for you, no session management or reconnection code needed.

Test without an LLM

Use the framework's own client class instead of driving tools through a model. Assert the tool list, then call each tool, in CI. Schema regressions surface before an agent ever sees them. An agent's failure mode is a plausible-sounding wrong call, not a raised exception, so a test suite is the only cheap signal you get.

Transport choice

Streamable HTTP is the current standard transport. SSE is an older binding; check your client's capabilities against the transport specification if connections fail.

Where auth enters

Bearer tokens or OAuth on the public endpoint, gated at the transport layer, per the MCP authorization model. Local development stays token-free: a stdio server is a subprocess with no network surface to protect. Credentials belong on the exposed endpoint, not inside tool bodies.

Debug in this order

If a tool does not appear in the client, read the server's stderr first, then the client's logs. Under stdio, stdout carries JSON-RPC, a stray print() corrupts it. Schema mismatches fail silently for the same reason. The client reports nothing wrong because nothing reached it.

The v3 CLI gives you a second way to confirm all of this from a terminal.

The v3 CLI: discover, generate-cli, and Agent Skills

Your server answers to a client now. FastMCP v3 adds a second route to the same tools: the terminal. Two commands carry most of it.

The following describes behavior documented in the FastMCP repository at github.com/PrefectHQ/fastmcp, verify against the current README before publishing scripts that depend on it.

discover

fastmcp discover scans your editor configs for MCP server definitions. The following describes behavior documented in the FastMCP repository at github.com/PrefectHQ/fastmcp, verify against the current README before publishing scripts that depend on it. Every server you configured becomes one command away, regardless of transport. You stop re-declaring a local stdio server just to query it by hand.

The v3 CLI: discover, generate-cli, and Agent Skills
The v3 CLI: discover, generate-cli, and Agent Skills

generate-cli

fastmcp generate-cli <server> connects to any MCP server, reads its tool schemas, and writes a standalone Python CLI script. The following describes behavior documented in the FastMCP repository at github.com/PrefectHQ/fastmcp, verify against the current README before publishing scripts that depend on it. Every tool becomes a typed subcommand with flags, help text, and tab completion. The stated rationale is blunt: MCP tool schemas already contain everything a CLI framework needs. You are not hand-writing an argument parser.

The payoff is an LLM-free path to the same tools. Run the generated CLI in cron jobs, CI smoke tests, and incident debugging, no model call required. At 2am you want a command with a --help flag, not a chat session.

The shipped Agent Skill

The repository ships a CLI skill at skills/fastmcp-client-cli/SKILL.md. Drop it into your agent's skills directory and the agent can discover, list, and call MCP tools on your behalf without you writing tool-calling code. Mechanism worth knowing: a SKILL.md file carries name and description in required frontmatter, which the agent preloads. The body loads only when the agent judges the skill relevant, so a vague description is the failure mode.

This framework-plus-SKILL.md pattern is spreading across the ecosystem. Shipping documentation for agents as well as humans is becoming table stakes for anything an agent is expected to adopt. If you are wiring this into Claude Code, next steps for Claude Code users covers the config side.

Pin the generated CLI to source control, upstream CLI changes surface as a diff, not a mystery failure. Check the changelog at github.com/PrefectHQ/fastmcp for current stability status.

PrefectHQ vs fastmcp-me: Disambiguating the Name

Search for the package name and you will find more than one thing calling itself FastMCP. The project this tutorial uses lives at github.com/PrefectHQ/fastmcp. A repository at github.com/fastmcp-me/fastmcp-python also uses the FastMCP name (verify independently, this tutorial has not audited that codebase). Examples do not transfer between different projects sharing the name.

Three checks before you pin a dependency

Before an install line reaches requirements.txt, verify:

  • The GitHub org. PrefectHQ, not a lookalike.

  • The repository URL. Match it against the homepage pip prints. The org path for the canonical project has moved over time, so an old badge or cached link may point somewhere stale.

  • The release cadence. FastMCP ships often, so a repository with a single commit months ago is a different project than the one in this guide.

Those three answer most of the question. If they do not match what you expected, stop and read the actual README.

The signature test

The PrefectHQ line has a recognizable shape: from fastmcp import FastMCP, then mcp = FastMCP("name"), decorators on functions, and a run() call at the bottom. That quickstart is what you will find in the published package documentation. If a tutorial's mental model differs, its docs, examples, and bugs differ too.

Judge on evidence, not popularity

PyPI download counts for PrefectHQ/fastmcp are available at pypistats.org; no equivalent public figures exist for the third-party repository, so the numbers aren't comparable. Look at documentation quality, open issues, and whether the API holds still across releases instead.

When you document the dependency internally, link the repository you actually depend on. Future readers searching the package name will land on whatever ranks first, which may not be what is installed. For adjacent MCP server options and how they compare, see Where to Find More.

The Decision Point: Host It Yourself or Paste an Endpoint

Once the dependency is pinned to the right repo, the next question isn't how to build a server. It's whether you should run one at all.

The self-hosted side

Self-hosting means you own the process, the port, the TLS terminator, the auth layer, and the pager that goes off at 3am. For a tool only your team calls, that is often the correct and cheapest answer, because you already run CI and one more container isn't a new category of work. You also keep the data path inside your network, which matters more than the compute cost.

The hosted side

Pasting a hosted Streamable HTTP endpoint means someone else owns all of it. You trade control over versions and data path for zero operations work. That trade is good for generic capabilities and bad for anything proprietary. If a server touches credentials, customer records, or an internal database, the data path argument decides it on its own: a hosted endpoint receives whatever you put in the tool arguments.

Moltline Studio's hosted endpoints at mcp.moltlinestudio.com cover 110 free-forever tools, paste a URL and skip the deploy step for generic capabilities. A $19/month All-Access license unlocks the full catalog. No install step, no dependency to pin, no server process to keep alive. If you want the client-side config, Adding a Hosted HTTP Endpoint to Claude Code in 60 Seconds walks through it.

The honest caveat: this is a one-person studio publishing endpoints, not a platform with SLAs. Read the terms, keep secrets out of tool arguments, and maintain your own fallback for anything critical to revenue.

The choosing rule

If the server wraps your internal systems or holds regulated data, self-host it. If it wraps a public API or a generic capability, a hosted endpoint is almost always the cheaper path. That single test resolves most cases before you write any code.

Agent-native access

For agents that must obtain access without a human in the loop, the /api endpoint serves a live HTTP 402 x402 challenge, so an agent can discover the price and settle on chain. A server states terms in a PAYMENT-REQUIRED header, the client returns a signed PAYMENT-SIGNATURE, and settlement comes back in PAYMENT-RESPONSE. That pattern is unusual and worth copying when you design agent-native purchase flows.

One caution when you do: routing headers are inspectable, not self-authenticating. Validate that what the agent claims matches what it actually requested.

Production Checklist and Limits You Should Know

Whichever side of that trade you chose, the same six checks decide whether your server survives contact with a real agent.

  • Pin both versions. Pin the FastMCP version in requirements.txt, and version your own server alongside it. Upgrade in a branch, run your tool-list assertions, then merge. Version churn is real, and any script you generated with fastmcp generate-cli moves with upstream changes.

  • Validate at the boundary. FastMCP generates JSON schemas from type hints; that schema is advisory to the model, not enforced at the Python boundary. Add explicit range checks, reject unknown arguments, and return an error message naming the offending field. limit must be between 1 and 100, got -5 is actionable; a raw ValueError is not.

  • Log to stderr under stdio. The stdio spec reserves stdout for JSON-RPC, treat that as the reason to route all logging to stderr or a file. One stray print() on stdout corrupts the protocol stream, route all application logs to stderr.

  • Keep payloads small. Truncate or paginate. Return a summary plus a handle, and expose a follow-up tool for the next page. The model's context window, not your bandwidth, is the binding constraint.

  • Name tools verb_noun, write docstrings for a new teammate. search_invoices, not invoices. The description field is the only signal a model has when deciding whether to call your tool or another one, so state when to use it, what it returns, and what it refuses.

  • Accept the three limits. FastMCP is a library, not a platform. Hosting, secrets management, and observability are outside its scope and yours to add.

For the parts the framework deliberately leaves to you, process supervision, secret handling, and traces, see MCP Developer Tools Worth Running in Production.

Conclusion: Ship One Tool Today

Everything above is theory until a tool answers a call. Install FastMCP, write one function, wrap it with @mcp.tool(), give the docstring one sentence about when to use the tool and one about what it returns, then run it. Five minutes of that teaches you more than any comparison article, this one included.

Next, connect a real client and call the tool from your editor. If the model picks the wrong tool or passes the wrong argument, fix the docstring before you touch the code. The description field is what the model reads; the function signature is what it fills.

Then decide where the tool runs.

  • Generic wrapper (a public API, a conversion, a lookup): paste a hosted Streamable HTTP endpoint instead of running a process. Moltline Studio's hosted endpoints at mcp.moltlinestudio.com cover 110 free-forever tools, paste a URL and skip the deploy step for generic capabilities.

  • Proprietary wrapper: keep it self-hosted, keep the endpoint private, and treat the auth layer as your problem from day one.

Ship the smallest version of whichever applies today.

Try it rather than read about it

22 hosted MCP servers, 160 tools, 110 of them free. No account, no API key, no signup — paste a URL into your client and the tools are there.

Browse the servers
← All posts