PlugWiki

How to Build an MCP Server in Python

Install the official MCP Python SDK, write a server in about 15 lines, test it in MCP Inspector, then add it to Claude Code or Claude Desktop.

You build a Python MCP server with the official mcp package (version 2.3.0 on PyPI in November 2026). Its MCPServer class turns plain type-hinted functions into tools: a: int, b: int is the input schema and the docstring is the description, so you write no JSON Schema and no protocol code. You need Python 3.11 or later and uv or pip.

In SDK v1 the same class was called FastMCP (from mcp.server.fastmcp import FastMCP). v2 renamed it to MCPServer; the decorators work the same way. FastMCP is also the name of a separate framework from Prefect, on PyPI as fastmcp, with its own docs at gofastmcp.com. This guide follows the official SDK and the modelcontextprotocol.io tutorial.

Good for

  • Python developers who want Claude to call their own functions or APIs
  • Teams wrapping an internal service as MCP tools
  • Anyone moving a v1 FastMCP server to the v2 SDK

Build, test and connect a Python MCP server

  1. 1

    Create a project and install the SDK

    You need Python 3.11 or later. Install uv on macOS or Linux with curl -LsSf https://astral.sh/uv/install.sh | sh and open a new terminal. Then run:

    • uv init my-server
    • cd my-server
    • uv add "mcp[cli]"

    With pip instead: pip install "mcp[cli]". The cli extra adds the mcp command (mcp dev, mcp run, mcp install). The full block is in the Code section.

  2. 2

    Write server.py

    Copy server.py from the Code section below. MCPServer("Demo") creates the server. @mcp.tool() makes add a tool and @mcp.resource("greeting://{name}") makes greeting a resource template.

    The SDK reads the name, description and argument schema from the function itself. Keep the parentheses on the decorators: @mcp.tool without () fails at import.

    Tip: Import from mcp.server, not mcp: there is no from mcp import MCPServer.

  3. 3

    Test it in MCP Inspector

    Run uv run mcp dev server.py. It starts your server and the MCP Inspector. Open the URL it prints, go to Tools, pick add, enter a=1 and b=2 and run it: the result is 3.

    The Resources tab lists greeting under Resource Templates. Give it World and read it to get Hello, World!.

  4. 4

    Check the stdio launch command

    The last two lines of server.py call mcp.run(), which serves over stdio, the transport Claude Code and Claude Desktop launch. Run the launch command once by hand:

    uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py

    Nothing prints and it does not exit. That is correct: a stdio server waits for a host. A traceback here is the real bug. Stop it with Ctrl-C.

  5. 5

    Add it to Claude Code

    Register the same command; everything after -- is the launch command:

    claude mcp add demo -- uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py

    Start claude and run /mcp: demo shows as connected with add listed. Add --scope project to write the entry to .mcp.json in the project root so your team gets it, or --scope user for all your projects.

    Prompt
    Use the demo server to add 19 and 23.
  6. 6

    Or add it to Claude Desktop

    Run uv run mcp install server.py. It writes the entry into claude_desktop_config.json with absolute paths (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json, Windows: %APPDATA%\Claude\claude_desktop_config.json). Pass -v API_KEY=abc123 or -f .env if the server needs environment variables.

    Fully quit Claude Desktop (Cmd+Q on macOS, Quit from the tray icon on Windows) and reopen it. The server appears under the + button in Connectors.

  7. 7

    Serve it over HTTP when others need it

    To give the server to people who do not have your file, serve it by URL: uv run mcp run server.py --transport streamable-http listens at http://localhost:8000/mcp. Add that to Claude Code with claude mcp add --transport http demo http://localhost:8000/mcp.

    Before you deploy behind a real hostname, set transport_security with your allowed hosts. The default accepts only localhost, and clients see Server returned an error response.

Code

terminal
# macOS / Linux: install uv, then open a new terminal
curl -LsSf https://astral.sh/uv/install.sh | sh

uv init my-server
cd my-server
uv add "mcp[cli]"      # or: pip install "mcp[cli]"
Python 3.11 or later. The cli extra adds the mcp command (mcp dev, mcp run, mcp install).
server.py
from mcp.server import MCPServer

mcp = MCPServer("Demo")


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


@mcp.resource("greeting://{name}")
def greeting(name: str) -> str:
    """Greet someone by name."""
    return f"Hello, {name}!"


if __name__ == "__main__":
    mcp.run()
The SDK README's minimal server, plus the stdio entry point from its Connect to a real host page. Test with: uv run mcp dev server.py
terminal
# Test in MCP Inspector
uv run mcp dev server.py

# Claude Code (everything after -- is the launch command)
claude mcp add demo -- uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py

# Claude Desktop (writes claude_desktop_config.json for you)
uv run mcp install server.py

# Serve over Streamable HTTP at http://localhost:8000/mcp
uv run mcp run server.py --transport streamable-http
Commands from the MCP Python SDK docs.
claude_desktop_config.json
{
  "mcpServers": {
    "demo": {
      "command": "uv",
      "args": ["run", "--with", "mcp[cli]", "mcp", "run", "/absolute/path/to/server.py"]
    }
  }
}
Hand-written entry. If Claude Desktop cannot find uv, put the full path from `which uv` in "command". Fully quit and reopen Claude Desktop after saving.
test_server.py
import pytest
from mcp import Client

from server import mcp


@pytest.mark.anyio
async def test_add() -> None:
    async with Client(mcp) as client:
        result = await client.call_tool("add", {"a": 1, "b": 2})
        assert result.structured_content == {"result": 3}
In-memory test from the SDK docs: Client(mcp) connects to the server object with no subprocess or port.

Common errors and fixes

  • TypeError: The @tool decorator was used incorrectly. Did you forget to call it? Use @tool() instead of @tool

    Cause: `@mcp.tool` written without parentheses. `tool()` is a decorator factory.

    Fix: Write `@mcp.tool()`. The same applies to `@mcp.resource(...)` and `@mcp.prompt()`. This fails at import, so run `python server.py` to see it.

    Source

  • Server connects, then drops, or shows as failed in Claude Desktop or Claude Code

    Cause: Something wrote to stdout, such as a `print()` call or a wrapper script echo. On stdio, stdout carries the protocol.

    Fix: Remove `print()` from the server and log with the `logging` module, which writes to stderr.

    claude-desktop, claude-code Source

  • Server does not appear in Claude Desktop after editing claude_desktop_config.json

    Cause: A relative path in "args", a JSON syntax error, or Claude Desktop is still running with the old config.

    Fix: Use absolute paths, check the JSON, then fully quit (Cmd+Q on macOS, Quit from the tray on Windows) and reopen. Read `~/Library/Logs/Claude/mcp-server-<NAME>.log` for the server's stderr.

    claude-desktop Source

  • Host cannot start the server because uv is not found

    Cause: Hosts launch servers with a minimal PATH that may not include uv.

    Fix: Put the absolute path from `which uv` (macOS/Linux) or `where uv` (Windows) in "command".

    claude-desktop, cursor, vs-code Source

  • mcp install fails with Claude app not found

    Cause: Claude Desktop's config directory does not exist yet.

    Fix: Install Claude Desktop and open it once, then run `uv run mcp install server.py` again.

    claude-desktop Source

  • from mcp.server.fastmcp import FastMCP fails after upgrading to mcp 2.x

    Cause: SDK v2 renamed FastMCP to MCPServer and moved mcp.server.fastmcp.* to mcp.server.mcpserver.*.

    Fix: Use `from mcp.server import MCPServer`. To stay on v1 for now, pin `mcp>=1.28,<2`.

    Source

  • Tool already exists: <name>

    Cause: Two tools registered with the same name. The first wins and the second is dropped.

    Fix: Rename one of them. The same rule applies to resources and prompts.

    Source

  • MCPError: Server returned an error response

    Cause: A deployed Streamable HTTP server rejects the request with 421 Invalid Host header. By default it accepts only a localhost Host header.

    Fix: Pass `transport_security=TransportSecuritySettings(allowed_hosts=[...])` to `mcp.streamable_http_app()` with the hostname you serve.

    Source

Questions

What is FastMCP?

Two things share the name. In the official MCP Python SDK v1, FastMCP was the high-level server class (from mcp.server.fastmcp import FastMCP). SDK v2 renamed it to MCPServer (from mcp.server import MCPServer) and moved mcp.server.fastmcp.* to mcp.server.mcpserver.*. FastMCP is also a separate framework maintained by Prefect, on PyPI as fastmcp, documented at gofastmcp.com. This page covers the official SDK.

How do I install the MCP Python SDK?

Run uv add "mcp[cli]" in a uv project, or pip install "mcp[cli]". The SDK needs Python 3.11 or later. The cli extra adds the mcp command for mcp dev, mcp run and mcp install; install plain mcp if you do not need it. For a one-off command without a project, uv run --with "mcp[cli]" mcp ... works.

My v1 code says from mcp.server.fastmcp import FastMCP. What do I change?

Change the import to from mcp.server import MCPServer and FastMCP("Demo") to MCPServer("Demo"). @mcp.tool(), @mcp.resource() and @mcp.prompt() accept what they accepted in v1. Transport options such as host, port and stateless_http moved from the constructor to run(). If you cannot migrate yet, pin mcp>=1.28,<2 to stay on the 1.x line.

How do I test a Python MCP server with MCP Inspector?

Run uv run mcp dev server.py and open the URL it prints. The Inspector has a tab each for Tools, Resources and Prompts; you can call a tool with your own arguments and see the exact result a model would get. For any server in any language you can also run npx @modelcontextprotocol/inspector (the Inspector needs Node.js 22.19 or later).

How do I add a Python MCP server to Claude Code?

Run claude mcp add <name> -- uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py, then /mcp inside a session to confirm it is connected. Use an absolute path: the host launches your server from its own working directory.

How do I add it to Claude Desktop?

Run uv run mcp install server.py. It writes the launch command (with the absolute path to uv, --frozen and a version pin) into claude_desktop_config.json. Then fully quit and reopen Claude Desktop. If it fails with Claude app not found, install Claude Desktop and open it once so its config folder exists.

Why does my server break when I use print()?

On stdio, stdout carries the protocol messages. A print() that reaches stdout can corrupt a message and the host drops the connection. Log with the standard logging module, which writes to stderr. Over HTTP, stdout logging is fine.

Can I run it over HTTP or inside FastAPI?

Yes. uv run mcp run server.py --transport streamable-http serves the same server at http://localhost:8000/mcp. To add it to an existing ASGI app such as FastAPI, mount the app from mcp.streamable_http_app(); the SDK's Running your server pages cover mounting and deployment.

Why does my host list zero tools?

Check in this order: run python server.py yourself to catch an import error such as @mcp.tool without parentheses; make sure the host runs the file that defines your mcp object; look for Tool already exists: in the log, which means two tools shared a name; restart the host so it reloads its list.

Sources

Docs, issue trackers and discussions this page is built from.

Show all 12
  1. MCP Python SDK README · github.com
  2. mcp on PyPI · pypi.org
  3. Installation · py.sdk.modelcontextprotocol.io
  4. What's new in v2 · py.sdk.modelcontextprotocol.io
  5. fastmcp on PyPI · pypi.org
  6. First steps · py.sdk.modelcontextprotocol.io
  7. Connect to a real host · py.sdk.modelcontextprotocol.io
  8. Connect Claude Code to tools via MCP · code.claude.com
  9. Troubleshooting · py.sdk.modelcontextprotocol.io
  10. Build an MCP server · modelcontextprotocol.io
  11. Get started · py.sdk.modelcontextprotocol.io
  12. MCP Inspector README · github.com

Updated Nov 1, 2026.