Skip to content
estudIA

MCP & connectors3 min read

Build Your Own MCP Server in Python, Step by Step

A tutorial to build a simple MCP server with the official Python SDK, test it with MCP Inspector and connect it to Claude Desktop and Claude Code.

In short

  1. Set up the project. Install uv, create the project and add the SDK with uv add "mcp[cli]".
  2. Write the server. Create an MCPServer and define each tool as a function with @mcp.tool().
  3. Test it with Inspector. Run npx @modelcontextprotocol/inspector to call the tools by hand.
  4. Connect it. Add it to Claude Desktop in its config file or to Claude Code with claude mcp add.
  5. Use and improve it. Ask for tasks in plain language and refine the tool descriptions.

An MCP server is a small program that offers tools to any compatible AI app: Claude, Claude Code, Cursor, ChatGPT and many more. Building your own is surprisingly simple. In this tutorial we’ll make a notes server: the assistant will be able to save notes and search them in a file on your computer.

You need Python 3.10 or higher and some comfort with the terminal.

Step 1: set up the project

The official guide uses uv, a very fast Python project manager:

# install uv (macOS and Linux)
curl -LsSf https://astral.sh/uv/install.sh | sh

uv init notes
cd notes
uv venv
source .venv/bin/activate
uv add "mcp[cli]"

On Windows, install uv with powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" and activate the environment with .venv\Scripts\activate.

Step 2: write the server

Create notes.py:

from pathlib import Path
from mcp.server import MCPServer

mcp = MCPServer("notes")
NOTES_FILE = Path.home() / "mcp-notes.txt"


@mcp.tool()
def save_note(text: str) -> str:
    """Saves a text note. Use it when the user asks to jot down or remember something.

    Args:
        text: The content of the note.
    """
    with NOTES_FILE.open("a", encoding="utf-8") as f:
        f.write(text.replace("\n", " ") + "\n")
    return "Note saved."


@mcp.tool()
def search_notes(word: str) -> str:
    """Searches saved notes containing a word.

    Args:
        word: Word or text to look for (case-insensitive).
    """
    if not NOTES_FILE.exists():
        return "No notes yet."
    found = [l.strip() for l in NOTES_FILE.read_text(encoding="utf-8").splitlines() if word.lower() in l.lower()]
    return "\n".join(found) or "No notes with that word."


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

Three details matter:

  • The docstring is the description the model will see. Explain what the tool does and when to use it.
  • The type hints (text: str) become the parameter schema.
  • With the stdio transport, don’t use print(): standard output is the communication channel with the app. To log messages, use logging, which writes to standard error.

Step 3: test it with MCP Inspector

Before connecting it to anything, try the tools by hand with the official Inspector (needs Node.js):

npx @modelcontextprotocol/inspector uv run notes.py

It opens a browser interface where you can see the tools and call them with any arguments. If something fails here, it will fail in Claude too.

Step 4: connect it

In Claude Desktop, edit the config file (on macOS, ~/Library/Application Support/Claude/claude_desktop_config.json; on Windows, %AppData%\Claude\claude_desktop_config.json):

{
  "mcpServers": {
    "notes": {
      "command": "uv",
      "args": ["--directory", "/ABSOLUTE/PATH/TO/notes", "run", "notes.py"]
    }
  }
}

Restart the app. In Claude Code, from any folder:

claude mcp add --transport stdio notes -- uv --directory /ABSOLUTE/PATH/TO/notes run notes.py

Always use the absolute path.

Step 5: use it

Ask in plain language: “note that I have the dentist on Monday at 10” and then “what notes do I have about the dentist?”. The assistant will decide which tool to use and ask your permission before running it.

If it doesn’t choose well, improve the tool descriptions: it’s the most important lever. We explain why in agent design patterns.

Before you share it

  • Validate inputs: never run commands or paths coming from the model without checking them.
  • Minimum permissions: the server should only touch what it needs, here a single file.
  • Secrets out of the code: keys and tokens in environment variables.
  • Read the MCP security best practices and our guide to agent safety.

Frequently asked questions

Which SDK version do I need?

The current official guide uses Python 3.10 or higher and the MCP Python SDK 2.0 or higher, which exposes the MCPServer class. Older tutorials use FastMCP, from earlier versions.

Can I do it in TypeScript?

Yes, there's an official TypeScript SDK with the same idea. The official guide has a version for each language.

Glossary terms

Sources

Related articles