How to Build an MCP Server in Python (2026 Guide)

A practical, end-to-end guide to building an MCP server in Python: scaffold with the official SDK, expose tools, resources, and prompts, run over stdio and Streamable HTTP, debug with the Inspector, and register in Claude Code and Cursor.

Quick answer. To build an MCP server in Python, install the official SDK with uv add "mcp[cli]", import FastMCP from mcp.server.fastmcp, decorate functions with @mcp.tool(), @mcp.resource("greeting://{name}"), and @mcp.prompt(), then run over stdio or Streamable HTTP. Register it in Claude Code with claude mcp add, and pin mcp<2 until stable v2 lands.

The Model Context Protocol (MCP) went from an Anthropic release to broadly adopted infrastructure in about a year. Community-sourced figures put the ecosystem at roughly 97 million monthly SDK downloads by December 2025 and around 10,000 production servers in early 2026, and Paul Iusztin reported the ecosystem crossing 110 million monthly downloads in June 2026. Python is one of the most popular languages for writing these servers, and the tooling has matured to the point where you can stand up a real, useful server quickly.

This guide walks the full path: scaffold a server with the official Python SDK, expose tools, resources, and prompts, run it over both stdio and Streamable HTTP, debug it with the MCP Inspector, and register it in Claude Code and Cursor. We will also confront the single biggest source of confusion in this space — the fact that “FastMCP” refers to two different packages — and a versioning trap that is about to bite anyone who does not pin their dependencies. Every command and version number here is current as of mid-2026.

What is an MCP server, and why write one in Python?

An MCP server is a small program that exposes capabilities to an AI client — Claude Code, Cursor, Claude Desktop, or any other MCP-aware host — over a standard JSON-RPC protocol. Instead of every tool integration being a bespoke plugin for one product, MCP defines a common contract: the server advertises what it can do, the client discovers those capabilities, and the model calls them on demand.

A server exposes three kinds of capabilities:

  • Tools — functions the model can call to take action or fetch live data: query a database, hit an API, read a file, run a calculation.
  • Resources — read-only data the client can load into context, addressed by URI (for example greeting://Ada). Think of these as GET endpoints with no side effects.
  • Prompts — reusable, parameterized prompt templates the user can invoke, often surfaced as slash commands in the client.

Python is the natural language to write one in. The official SDK requires Python 3.10 or newer and turns ordinary, type-hinted functions into protocol-compliant tools with zero boilerplate — no manual JSON Schema, no request parsing, no transport plumbing. If you can write a Python function, you can write an MCP tool. That low floor is a big part of why Python is such a common choice for MCP servers — and why practitioners run hands-on workshops on it, like the one Pamela Fox shared in June 2026 on building servers with Python and FastMCP.

If you want a broader survey of what people are actually shipping before you build your own, the roundup of the best MCP servers for Claude Code and Cursor is a useful map of the territory, and our list of free, open-source MCP servers shows the patterns these projects converge on.

Which “FastMCP” do you actually mean?

This is the question that derails most tutorials, so settle it before writing a line of code. There are two distinct packages both called FastMCP:

  1. FastMCP 1.0, folded into the official SDK. In 2024, the original FastMCP project was merged into the official MCP Python SDK (PyPI package mcp). You get it as from mcp.server.fastmcp import FastMCP. This is the spec-aligned baseline maintained alongside the protocol itself.
  2. Standalone FastMCP, by Prefect. The separately-maintained, feature-richer line lives at gofastmcp.com (PyPI package fastmcp), currently at v3.0.0. You get it as from fastmcp import FastMCP. Its vendor page claims it is “downloaded a million times a day” and that “some version of FastMCP powers 70% of MCP servers across all languages.” Treat those as self-reported marketing figures, but the momentum is real: MCP co-creator David Soria Parra has publicly praised the standalone version over the official Python SDK that ships with the protocol.

The two are source-incompatible in subtle, frustrating ways. The decorator syntax differs: the official SDK uses @mcp.tool() with parentheses, while standalone FastMCP uses @mcp.tool without parentheses. The import paths differ. Copy a snippet from one project's docs into a server built on the other and it silently breaks. When you read a blog post or Stack Overflow answer, the very first thing to check is which import it uses.

For this guide we lead with the official SDK because it is the spec-aligned, fewest-dependencies path and maps one-to-one onto the protocol. We cover the standalone framework and when to switch in a dedicated section below.

How do you scaffold a Python MCP server?

Use uv for project and dependency management — it is what the official docs assume, and it makes the run commands shorter. Create a project and add the SDK:

uv init mcp-server-demo
cd mcp-server-demo
uv add "mcp[cli]"   # or: pip install "mcp[cli]"

The [cli] extra pulls in the command-line tooling you will use for the Inspector later. Now create server.py with a minimal but complete server that exposes one tool, one resource, and one prompt:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("Demo")


@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two numbers and return the sum."""
    return a + b


@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
    """Return a personalized greeting."""
    return f"Hello, {name}!"


@mcp.prompt()
def review_code(code: str) -> str:
    """Generate a prompt asking the model to review code."""
    return f"Please review this code and suggest improvements:\n\n{code}"


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

That is a working server. The single most important thing to understand here is that the type hints are the schema. The signature def add(a: int, b: int) -> int tells the SDK that add takes two integers and returns one. From that, the SDK auto-generates the JSON Schema, runtime validation, and the tool description the model sees — you write none of it by hand. The docstring becomes the tool's human-readable description, which directly affects how reliably the model picks the right tool. This is the recurring lesson from people shipping real servers — the popular PullMD server, discussed in a widely-upvoted r/ClaudeAI thread, is one example of a production Streamable HTTP server built on exactly this model. Spend real effort on docstrings; they are not decoration.

Pin your dependency before v2 ships

Before you go further, fix your dependency constraint. The official SDK's stable line is v1.x (around 1.27). A v2 rework is in pre-release as an alpha (mcp[cli]==2.0.0a3), it is explicitly marked “Do not use in production,” and it renames the server class — v2 code reads from mcp.server import MCPServer, not FastMCP. Stable v2 is targeted for 2026-07-27, alongside the 2026-07-28 protocol revision.

Today, pip and uv will not auto-select the pre-release, so a fresh install gives you v1.x. But an unpinned mcp dependency will pull the new, breaking API the moment stable v2 lands. Pin it now:

# in pyproject.toml dependencies, or:
uv add "mcp[cli]>=1.27,<2"

A second, related trap: the SDK repo's default README on the main branch already documents v2. Beginners who follow it land on the MCPServer API and wonder why their FastMCP tutorials do not match. For production guidance today, read the v1.x branch README, not the main one.

How do you expose tools, resources, and prompts?

The three decorators map cleanly onto the three capability types, and they compose naturally. A few patterns worth knowing:

Tools are for actions and live data. Any Python type the SDK can introspect becomes part of the schema — primitives, lists, and Pydantic models all work. Optional parameters with defaults become optional in the schema:

@mcp.tool()
def search_orders(customer_id: str, limit: int = 10) -> list[dict]:
    """Look up recent orders for a customer."""
    return db.query(customer_id, limit)

Resources are read-only and addressed by URI. The pattern in the decorator captures path parameters: @mcp.resource("greeting://{name}") binds {name} to the function argument. Use resources for content the client should be able to load into context without triggering side effects — a config file, a document, a computed summary. The mental model is REST: tools are POST, resources are GET.

Prompts are parameterized templates. They surface in clients as reusable commands, so a review_code prompt becomes a one-click way for the user to kick off a structured review with their own snippet substituted in. Prompts keep your best-performing instructions in the server instead of scattered across users' heads.

Because the schema is generated from your signatures, the discipline that matters is naming and documentation: clear function names, precise type hints, and docstrings that describe when the model should reach for each tool. Vague descriptions are the most common reason a model ignores a perfectly good tool. If you are designing a server that an autonomous agent will drive end-to-end, our guide to AI coding agents covers how these agents reason about tool selection, and the AGENTS.md guide is worth a read for documenting capabilities in a way agents reliably pick up.

How do you run it over stdio and Streamable HTTP?

MCP supports three transports, and the choice matters for how clients connect and whether your server can be remote:

TransportHow it runsDefault endpointWhen to use it
stdio (default)mcp.run()n/a (process stdin/stdout)Local servers the client launches as a subprocess. Simplest, no network.
Streamable HTTPmcp.run(transport="streamable-http")http://localhost:8000/mcpRemote or shared servers, multiple clients, networked deployment.
SSE (legacy)mcp.run(transport="sse")n/aOlder clients only. Prefer Streamable HTTP for anything new.

With stdio, the client starts your server as a child process and speaks JSON-RPC over its standard input and output. This is the default and the right choice for a tool that runs on the same machine as the client. It needs no ports, no HTTP server, and no authentication.

There is one non-obvious rule with stdio that will corrupt your server if you miss it: stdout is reserved for the protocol stream. Any stray print() to stdout injects garbage into the JSON-RPC channel and breaks the connection. Send all logging to stderr instead — use Python's logging module configured to sys.stderr, never bare print(). This is one of the most common reasons a freshly written stdio server “connects but does nothing.”

For Streamable HTTP, flip the transport and the server starts an HTTP listener:

if __name__ == "__main__":
    mcp.run(transport="streamable-http")

By default this serves at http://localhost:8000/mcp. The r/ClaudeAI PullMD thread walks through standing up exactly this kind of Streamable HTTP server and registering it with a single claude mcp add command. That said, watch the endpoint path — a very common error is registering the bare host http://localhost:8000 instead of the full http://localhost:8000/mcp, which 404s. The /mcp suffix is not optional.

One more caveat if you plan to self-host a remote server for Claude's hosted clients: there are reports (a 386-upvote r/ClaudeAI thread, tracking issue claude-ai-mcp#237) that Anthropic's hosted proxy currently cannot always reach newly self-hosted MCP servers. If you are targeting the hosted Claude apps specifically, test reachability early rather than assuming a publicly-deployed server is automatically usable.

How do you debug it with the MCP Inspector?

Before wiring your server into a real client, exercise it in isolation with the MCP Inspector — a browser-based tool that connects to your server, lists its tools, resources, and prompts, and lets you call them by hand. The official SDK ships a one-command launcher:

uv run mcp dev server.py

That starts your server and opens the Inspector wired up to it. Alternatively, launch the Inspector directly and point it at a running server:

npx -y @modelcontextprotocol/inspector

Then connect it to http://localhost:8000/mcp if you are running Streamable HTTP. The Inspector is where you confirm that your type hints produced the schema you expected, that tool descriptions read well, and that resources resolve. It is far faster to iterate here than to restart a full client every time you tweak a docstring. If your server needs extra dependencies at runtime, you can run it with them inline, for example uv run --with mcp examples/snippets/servers/fastmcp_quickstart.py.

How do you connect it to Claude Code?

Claude Code manages MCP servers through the claude mcp command family. The registration command depends on your transport.

For a local stdio server, give Claude Code the command to launch it. Everything after the -- is the literal command and its arguments:

claude mcp add my-server -- uv run server.py

For a Streamable HTTP server, register the URL — including the /mcp path:

claude mcp add --transport http my-server http://localhost:8000/mcp

For a legacy SSE server (for example a hosted vendor endpoint), use --transport sse:

claude mcp add --transport sse asana https://mcp.asana.com/sse

Manage what you have registered with these commands and the in-session /mcp panel:

claude mcp list
claude mcp get my-server
claude mcp remove my-server

Understand the three scopes

Where Claude Code stores a server's config determines who can use it. There are three scopes:

ScopeStored inVisibilityUse it for
local~/.claude.jsonCurrent project only, just youPersonal, throwaway, or experimental servers.
project.mcp.json in the repo rootEveryone who clones the repoServers the whole team needs; commit it to version control.
user~/.claude.jsonAll your projectsPersonal tools you want everywhere.

The project scope is the one that makes MCP servers shareable: drop a .mcp.json in your repository root, commit it, and every collaborator gets the same server config when they open the project. One markup detail to know — in .mcp.json, the transport key is type, and it accepts streamable-http as an alias for http. So configs copied from a server's own docs (which often say streamable-http) work even though the claude mcp add --transport flag wants http.

Two more Claude Code behaviors that trip people up. First, stdio servers are not auto-reconnected — only HTTP and SSE servers are. If a stdio server dies, you re-establish it; it does not silently come back. Second, the server name workspace is reserved and skipped, so do not name your server that. Finally, when Claude Code spawns a stdio server it sets CLAUDE_PROJECT_DIR in the server's environment, which you can read to resolve project-relative paths reliably instead of guessing the working directory. If you hit registration or connection issues, our writeup of common Claude Code errors and fixes covers the recurring failure modes.

How do you connect it to Cursor?

Cursor reads MCP server configuration from a JSON file, and the standalone FastMCP CLI can write that file for you. With FastMCP installed, the install command (new in FastMCP 2.10.3) auto-configures Cursor and opens a deeplink to confirm:

fastmcp install cursor server.py

That writes a global Cursor config. If you would rather keep the server scoped to one project — the equivalent of Claude Code's project scope — use the --workspace flag (new in FastMCP 2.12.0), which writes a project-local .cursor/mcp.json instead:

fastmcp install cursor server.py --workspace .

You can also hand-write .cursor/mcp.json if you prefer not to depend on the FastMCP CLI; the file format is straightforward and documented on gofastmcp.com's Cursor integration page. For a deeper tour of Cursor's MCP support and how it fits the rest of the editor, see our Cursor IDE complete guide.

The standalone FastMCP — should you switch?

Once your server outgrows the basics, the standalone FastMCP (by Prefect) is the framework many teams reach for. Here is the side-by-side so you can choose deliberately rather than by accident:

DimensionOfficial MCP SDK (mcp)Standalone FastMCP (fastmcp)
Installuv add "mcp[cli]"pip install fastmcp
Importfrom mcp.server.fastmcp import FastMCPfrom fastmcp import FastMCP
Tool decorator@mcp.tool() (with parens)@mcp.tool (no parens)
Current versionv1.x (~1.27) stable; v2 in alphav3.0.0 (bundles MCP 1.25.0)
MaintainerAnthropic / MCP projectPrefect (gofastmcp.com)
Run commandmcp.run() / transport="streamable-http"mcp.run()
Best forSpec-aligned baseline, fewest dependenciesRicher features, broad adoption, CLI integrations

The case for the standalone framework is feature depth and the ecosystem around it — the fastmcp install cursor integration you just saw is a FastMCP feature, not an official-SDK one, and the project ships more batteries for auth, deployment, and client tooling. Paul Iusztin, writing in June 2026, described standalone FastMCP (by Prefect) as having effectively become the practical default for MCP servers in Python. Check what you have installed with fastmcp version, which prints the FastMCP, bundled MCP, and Python versions.

The case for staying on the official SDK is simplicity and spec-tracking. If your server is a handful of tools, the official SDK has fewer moving parts and follows the protocol revisions directly. A reasonable rule: start on the official SDK to learn the protocol cleanly, move to standalone FastMCP when you need a feature it offers — advanced auth, richer deployment, or the CLI integrations. The core decorator model is similar enough that porting is mostly mechanical, the parentheses gotcha aside.

What are the most common mistakes?

Most failures building a Python MCP server cluster around a handful of avoidable traps:

  • Mixing the two FastMCPs. The number-one issue. Official SDK = pip install "mcp[cli]" + from mcp.server.fastmcp import FastMCP + @mcp.tool(). Standalone = pip install fastmcp + from fastmcp import FastMCP + @mcp.tool. Mixing imports and decorator styles silently breaks copied tutorials. Confirm which one a snippet targets before pasting it.
  • Following the v2 README by accident. The main-branch README documents the alpha v2 (MCPServer) that is not for production. Use the v1.x branch README for stable guidance.
  • Not pinning mcp<2. When stable v2 ships (targeted 2026-07-27), an unpinned dependency can pull a breaking API. Add mcp>=1.27,<2 today.
  • Printing to stdout on a stdio server. stdout is the JSON-RPC channel; stray print() corrupts the stream. Log to stderr.
  • Registering the wrong HTTP URL. Streamable HTTP defaults to /mcp on port 8000. Registering http://localhost:8000 instead of http://localhost:8000/mcp is a common 404.
  • Assuming a self-hosted remote server is reachable by hosted Claude clients. Per community reports, Anthropic's hosted proxy may not reach newly self-hosted servers. Test reachability before depending on it.
  • Naming a server workspace in Claude Code. That name is reserved and skipped. Pick something else.

Get those right and the rest of the experience is genuinely smooth — which is the whole point of a protocol-level abstraction. The first time you watch an agent discover and call a tool you wrote, with no glue code in between, the design pays for itself.

A note on getting this into production

A demo server is a few decorators; a server other people depend on is a different job — auth, error handling, observability, deployment, and keeping pace with a protocol that is still revising on a published schedule (the next revision lands 2026-07-28). If you are an engineering team that wants MCP tooling wired into your own systems without burning weeks on protocol plumbing, Codersera places vetted remote developers who can build and harden these integrations alongside your team. Either way, the on-ramp above gets you to a working server today.

FAQ

What Python version do I need to build an MCP server?

Python 3.10 or newer. The official MCP Python SDK (PyPI package mcp) requires 3.10+. Install it with uv add "mcp[cli]" or pip install "mcp[cli]".

What is the difference between the official SDK's FastMCP and the standalone FastMCP?

They are two different packages. The official SDK bundles FastMCP 1.0 as from mcp.server.fastmcp import FastMCP (decorators use parentheses, e.g. @mcp.tool()). The standalone fastmcp package by Prefect is imported as from fastmcp import FastMCP, currently v3.0.0, with no-parens decorators (@mcp.tool). The standalone version is more feature-rich and widely considered the practical default; the official SDK is the spec-aligned baseline.

How do I run my MCP server over HTTP instead of stdio?

Call mcp.run(transport="streamable-http"). By default it serves at http://localhost:8000/mcp. Register that full URL (including /mcp) in your client — registering the bare host returns a 404.

How do I add my server to Claude Code?

For a local stdio server: claude mcp add my-server -- uv run server.py. For Streamable HTTP: claude mcp add --transport http my-server http://localhost:8000/mcp. Manage servers with claude mcp list, claude mcp get <name>, claude mcp remove <name>, and the in-session /mcp panel.

Should I pin the mcp package version?

Yes. Pin mcp>=1.27,<2 now. The stable line is v1.x; v2 is a pre-release alpha that renames the server class to MCPServer and is explicitly not for production. Stable v2 is targeted for 2026-07-27, and an unpinned dependency will pull the breaking API once it lands.

How do I debug a server before connecting it to a client?

Use the MCP Inspector. Run uv run mcp dev server.py to launch your server in the Inspector, or run npx -y @modelcontextprotocol/inspector and connect it to http://localhost:8000/mcp. The Inspector lists your tools, resources, and prompts and lets you call them by hand to verify the generated schema.