How to Build an MCP Server in Python
The Model Context Protocol (MCP) is an open standard that lets AI apps like Claude Desktop, Claude Code and Cursor use your tools and data. Write one MCP server, and every app that speaks MCP can use it.
This guide builds a working server with the official Python SDK v2 (stable since July 2026), tests it, and connects it to real apps. If you've seen older tutorials using FastMCP, read the migration note below: the class has been renamed.
馃幆 Quick Answer (30-Second Read)
- Install:
pip install "mcp[cli]"oruv add "mcp[cli]"(Python 3.11+). - Create:
mcp = MCPServer("Name"), then decorate functions with@mcp.tool(),@mcp.resource()or@mcp.prompt(). - Test:
uv run mcp dev server.pyopens the MCP Inspector. - Connect:
uv run mcp install server.pyfor Claude Desktop, orclaude mcp addfor Claude Code. - v2 change:
from mcp.server.fastmcp import FastMCPis nowfrom mcp.server import MCPServer.
How MCP Fits Together
server.py"] S --> T["Tools
actions Claude can call"] S --> RS["Resources
data Claude can read"] S --> P["Prompts
reusable templates"] T --> API[("Your APIs, DB, files")] RS --> API
The host launches your server as a child process and talks to it over stdin/stdout. You don't open a port or run a web server for local use. The host just needs the command that starts your server.
| Building block | What it is | Who uses it |
|---|---|---|
| Tool | A function Claude can call (search, create, send) | The model decides when |
| Resource | Read-only data addressed by a URI | The app or user attaches it |
| Prompt | A reusable prompt template | The user picks it |
Step 1: Install the SDK
# with uv (recommended)
uv init mcp-bookshop && cd mcp-bookshop
uv add "mcp[cli]"
# or with pip
pip install "mcp[cli]"The [cli] extra adds the mcp command used to test and install servers.
Step 2: Write the Server
Create server.py:
from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ToolError
mcp = MCPServer("Bookshop")
CATALOG = {
"Dune": "Frank Herbert",
"Neuromancer": "William Gibson",
"The Left Hand of Darkness": "Ursula K. Le Guin",
}
@mcp.tool()
def search_books(query: str) -> list[str]:
"""Search the catalog by title or author."""
needle = query.lower()
return [t for t, a in CATALOG.items() if needle in t.lower() or needle in a.lower()]
@mcp.tool()
def get_author(title: str) -> str:
"""Look up the author of a book in the catalog."""
if title not in CATALOG:
raise ToolError(f"No book titled {title!r} in the catalog.")
return CATALOG[title]
@mcp.resource("catalog://titles")
def titles() -> str:
"""Every title in the catalog, one per line."""
return "\n".join(sorted(CATALOG))
@mcp.prompt()
def recommend(genre: str) -> str:
"""Ask for a book recommendation in a genre."""
return f"Using the bookshop tools, recommend one {genre} book and explain why."
if __name__ == "__main__":
mcp.run()What the SDK does for you:
- The function name becomes the tool name.
- The docstring becomes the description Claude reads to decide when to use it. Write it clearly.
- Type hints become the input schema, so Claude knows
querymust be a string. ToolErrorsends a clean error message back to Claude instead of crashing.mcp.run()with no arguments starts a stdio server, which is what desktop apps expect.
mcp.run() under if __name__ == "__main__":. The mcp CLI imports your file, and an unguarded run() would start a server the moment it's imported.
Step 3: Test It in the MCP Inspector
uv run mcp dev server.pyThis opens the MCP Inspector in your browser. You can list your tools, call search_books with test input, read the catalog://titles resource and preview the prompt, all before involving an AI app.
Step 4: Connect It to an App
Every app needs the same launch command:
uv run --with "mcp[cli]" mcp run /absolute/path/to/server.pyClaude Desktop (the SDK writes the config for you):
uv run mcp install server.pyThen fully quit Claude Desktop and reopen it. The entry is saved in claude_desktop_config.json (on macOS: ~/Library/Application Support/Claude/). Need an API key? Add -v API_KEY=abc123 or -f .env, because Claude Desktop doesn't see your shell's environment variables.
Claude Code:
claude mcp add bookshop -- uv run --with "mcp[cli]" mcp run /absolute/path/to/server.pyRun /mcp inside Claude Code to confirm it's connected.
Cursor: create .cursor/mcp.json in your project:
{
"mcpServers": {
"bookshop": {
"command": "uv",
"args": ["run", "--with", "mcp[cli]", "mcp", "run", "/absolute/path/to/server.py"]
}
}
}Now ask Claude: "Who wrote Neuromancer?" It will call your get_author tool.
Migrating From v1 (FastMCP)
| v1 | v2 |
|---|---|
from mcp.server.fastmcp import FastMCP |
from mcp.server import MCPServer |
mcp.server.fastmcp.* |
mcp.server.mcpserver.* |
ctx.fastmcp |
ctx.mcp_server |
get_context() |
Declare a ctx: Context parameter |
FastMCPError |
MCPServerError |
The @mcp.tool(), @mcp.resource() and @mcp.prompt() decorators work as before. Not ready to migrate? Pin mcp>=1.28,<2.
Common Problems
- Server doesn't appear: fully quit and reopen the app. Hosts read config only at launch.
- "uv not found": apps start servers with a minimal PATH. Use the absolute path from
which uv. - Relative path used: always give the absolute path to
server.py. - Missing API keys: pass them with
-v/-fwhen installing, or in the config'senvfield. - Printing to stdout: in stdio mode, stdout carries protocol messages. Log to stderr instead.
Going Remote
Local stdio servers are perfect for personal tools. To share a server with people who don't have your file, serve the same mcp object over Streamable HTTP:
mcp.run(transport="streamable-http")Users then connect with a URL instead of a command. Add authentication before exposing it on the internet.
Frequently Asked Questions
What is the difference between an MCP tool and a resource?
A tool is a function the model can decide to call, such as searching or creating something. A resource is read-only data identified by a URI that the app or user can attach as context. Use tools for actions and lookups, and resources for documents or data to read.
Is FastMCP still available?
In the official MCP Python SDK v2, the FastMCP class was renamed to MCPServer and the old import path was removed. Older tutorials using FastMCP need the new import. You can stay on v1 by pinning the package below version 2.
Does an MCP server work with apps other than Claude?
Yes. MCP is an open protocol. The same server works in any MCP-compatible host, including Claude Desktop, Claude Code, Cursor and VS Code. Each host just needs the command that starts your server, or a URL for remote servers.
Conclusion
Building an MCP server in Python takes one file: create an MCPServer, decorate functions as tools, resources and prompts, test in the Inspector, and register the launch command with your app. Clear docstrings and type hints do most of the work of teaching Claude how to use your tools.
Sources
- MCP Python SDK on GitHub
- MCP Python SDK docs: Connect to a real host
- MCP Python SDK: Migration guide v1 to v2
Last verified: 14 October 2026.
Related reads: Claude Code CLI Tutorial 路 How AI Agents Work 路 How to Create a CLAUDE.md File 路 Claude Models Explained
I write about building with AI and how real products work. Follow on LinkedIn or X, or invite me to speak.