MCPVault
Blog
GuideSeptember 2, 20267 min read

FastMCP Tutorial: Build an MCP Server in Python [2026]

FastMCP tutorial: build a working MCP server in Python in 10 minutes with tools, resources and prompts, test it, connect it to Claude Code and publish it.

FastMCP is the Python framework most MCP servers are built with. You write a function, add a decorator, and FastMCP turns the type hints and docstring into a tool the model can call, with input validation and the protocol handshake handled for you. This tutorial builds a small notes server with tools, a resource and a prompt, tests it without leaving Python, connects it to Claude Code and Claude Desktop, and ends with publishing it so other people can run it with one command.

Everything below was run on 2 September 2026 with FastMCP 4.0.1, uv 0.12.9 and Python 3.12. The output shown is the real output.

1. Install FastMCP

FastMCP needs Python 3.10 or newer. With uv, which is the fastest route and the one the MCP ecosystem has settled on:

uv init notes-mcp && cd notes-mcp
uv add fastmcp

Or with pip inside a virtual environment:

pip install fastmcp

Check the version with uv run fastmcp version (or fastmcp version). You want 4.x.

2. Write the server

Create server.py:

from fastmcp import FastMCP

mcp = FastMCP("notes")

NOTES: dict[str, str] = {}

@mcp.tool def add_note(title: str, body: str) -> str: """Save a note under a title. Overwrites an existing note with the same title.""" NOTES[title] = body return f"saved {title!r} ({len(body)} chars)"

@mcp.tool def search_notes(query: str) -> list[str]: """Return the titles of notes whose title or body contains the query (case-insensitive).""" q = query.lower() return [t for t, b in NOTES.items() if q in t.lower() or q in b.lower()]

@mcp.resource("notes://{title}") def read_note(title: str) -> str: """The full text of one note.""" return NOTES.get(title, "")

@mcp.prompt def summarize(title: str) -> str: """Ask the model to summarize a note in three bullet points.""" return f"Summarize the note titled {title!r} in three bullet points."

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

Three things are worth noticing. The docstring becomes the tool description the model reads, so write it for the model, not for you. The type hints become the JSON schema, so title: str means the model cannot send a number. And there is no protocol code at all: mcp.run() starts a stdio server that speaks MCP.

The three decorators map to the three things an MCP server can offer. Tools are actions the model calls. Resources are data the client can read by URI, here notes://mcp. Prompts are reusable templates the user can pick from the client. Most servers in the vault only ship tools; resources and prompts are cheap to add and make the server nicer to use from Claude Desktop.

3. Test it without a client

FastMCP ships a client that can connect to a server object in the same process. That makes tests fast and needs no editor. Create test_client.py:

import asyncio
from fastmcp import Client
from server import mcp

async def main(): async with Client(mcp) as client: tools = await client.list_tools() print("tools:", [t.name for t in tools]) r = await client.call_tool("add_note", {"title": "mcp", "body": "Model Context Protocol lets agents call tools."}) print("add_note ->", r.data) r = await client.call_tool("search_notes", {"query": "agents"}) print("search_notes ->", r.data) res = await client.read_resource("notes://mcp") print("resource ->", res[0].text) p = await client.get_prompt("summarize", {"title": "mcp"}) print("prompt ->", p.messages[0].content.text)

asyncio.run(main())

Run it:

uv run python test_client.py

Output from the test run:

tools: ['add_note', 'search_notes']
add_note -> saved 'mcp' (46 chars)
search_notes -> ['mcp']
resource -> Model Context Protocol lets agents call tools.
prompt -> Summarize the note titled 'mcp' in three bullet points.

r.data is the deserialized return value, so a tool that returns a list gives you a list back. Put these calls in pytest and you have a test suite for the server before any model has touched it.

Two more ways to poke at it. uv run fastmcp inspect server.py prints the tools, resources and prompts with their schemas. The official MCP Inspector opens a web UI where you can call tools by hand:

npx @modelcontextprotocol/inspector uv run server.py

4. Connect it to Claude Code

Claude Code runs the server as a subprocess over stdio. Give it the absolute path:

claude mcp add notes -- uv run --directory /absolute/path/to/notes-mcp server.py

Then in a session: "Add a note titled standup with today's three priorities, then search my notes for priorities." Claude Code asks to run add_note and search_notes and shows the results. The --directory flag makes uv use the project's own environment, so the server works no matter which folder Claude Code was started in.

The same command works for any client that launches stdio servers. For Claude Desktop, claude_desktop_config.json:

{
  "mcpServers": {
    "notes": {
      "command": "uv",
      "args": ["run", "--directory", "/absolute/path/to/notes-mcp", "server.py"]
    }
  }
}

Claude Desktop shows the summarize prompt in its prompt picker and the notes:// resource in its attachment menu, which is the reason to add those two decorators.

If a client cannot find uv, use the full path from which uv; GUI apps often have a shorter PATH than your terminal.

5. Run it over HTTP

Stdio is right for local tools. When the server should live on a machine other than the client, run it as an HTTP server:

uv run fastmcp run server.py:mcp --transport http --port 8000

Clients connect to http://localhost:8000/mcp. Claude Code:

claude mcp add --transport http notes http://localhost:8000/mcp

Anything you deploy publicly needs authentication. FastMCP has built-in auth providers, and the fastmcp auth CLI group configures them; put the server behind OAuth or a bearer token before you expose it, and never ship the in-memory NOTES dict as your storage.

Find a verified MCP server in the vault for whatever system you want to connect next. Grades, tools and compatibility for every listing at /servers.

FastMCP vs the official Python SDK

The official mcp package on PyPI is the reference SDK and it includes its own FastMCP class, which is where the decorator style came from. The standalone fastmcp package by Prefect is the continuation of that idea with a lot more around it.

fastmcp (PrefectHQ)mcp (official SDK)
Decorators for tools, resources, promptsYesYes
In-process test clientYes, Client(mcp)Manual
CLI: run, dev, inspect, installYesmcp CLI, smaller
Server composition and proxyingYesNo
Auth providers built inYesBring your own
Low-level protocol controlThrough the SDK it wrapsFull
PowersRoughly 70% of public serversThe protocol itself
Use the official SDK when you need low-level control over the protocol or want zero extra dependencies. Use FastMCP for everything else; it is built on the official SDK, so you lose nothing.

6. Publish it so anyone can run it

The servers people actually install are the ones that run with a single command. For Python that command is uvx your-package, which needs two things: a package on PyPI and a console script entry point.

In pyproject.toml:

[project]
name = "notes-mcp"
version = "0.1.0"
dependencies = ["fastmcp>=4"]

[project.scripts] notes-mcp = "server:main"

Add a main() function to server.py that calls mcp.run(), then build and upload:

uv build
uv publish

Now uvx notes-mcp runs the server on any machine with uv, and every client config above collapses to "command": "uvx", "args": ["notes-mcp"]. Put that exact command in your README's install section. MCPVault's verifier runs uvx and npx commands inside a sandbox and completes a real MCP handshake, so a server published this way can be tested and verified automatically; servers that only document pip install cannot.

7. List it in the directory

The last step is being found. List your MCP server on MCPVault: submit the GitHub URL, claim the listing, set uvx notes-mcp as the launch command, and request verification. A passed handshake earns the verified badge and a do-follow link back to your project. FastMCP itself is in the vault at FastMCP on MCPVault, Grade A with 27,000 stars.

Frequently Asked Questions

What is FastMCP?

FastMCP is a Python framework for building MCP servers and clients. It turns decorated functions into MCP tools, resources and prompts, handles the protocol, and adds a CLI, a test client, auth and deployment helpers. It sits on top of the official MCP Python SDK.

Do I need to know the MCP protocol to use FastMCP?

No. The tutorial above never touches a JSON-RPC message. It helps to know the three primitives, tools, resources and prompts, because that is how clients present your server to users.

How do I debug a FastMCP server that a client cannot start?

Run the same command by hand in a terminal; import errors and missing dependencies show up immediately. Then use fastmcp inspect server.py to confirm the tools register, and the MCP Inspector to call them. If the client still fails, check that it can find uv on its PATH and that the project path is absolute.

Can FastMCP servers run over HTTP instead of stdio?

Yes. fastmcp run server.py:mcp --transport http --port 8000 serves the same server at /mcp, and every major client can connect to an HTTP MCP endpoint. Add authentication before exposing it beyond localhost.

How do I get my FastMCP server verified on MCPVault?

Publish it to PyPI with a console script so it runs via uvx, submit the repository, claim the listing and request verification. The verifier launches the command in a sandbox and completes the MCP handshake; if it needs an API key, you can add it on the edit page. See what verification checks.


Your server in the vault.

You just built an MCP server. Claim the listing and get it in front of developers who are actively looking. Free to claim. Verification is free during early access and earns a do-follow link to your project.

Claim your listing or submit a server if it is not indexed yet.

FastMCPPythonHow-ToMCP Server