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
- Set up the project. Install uv, create the project and add the SDK with uv add "mcp[cli]".
- Write the server. Create an MCPServer and define each tool as a function with @mcp.tool().
- Test it with Inspector. Run npx @modelcontextprotocol/inspector to call the tools by hand.
- Connect it. Add it to Claude Desktop in its config file or to Claude Code with claude mcp add.
- 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
stdiotransport, don’t useprint(): standard output is the communication channel with the app. To log messages, uselogging, 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.


