How to Build an MCP Server with Python in 5 Min
Source: Dev.to
You want to give Claude (or any MCP client) access to your own custom tools. Every Python tutorial you find is 2,000+ words and 15 steps. Here’s a working MCP server with two tools in under 30 lines.
The Code
Create a file called notes_server.py:
from fastmcp import FastMCP
mcp = FastMCP("Notes")
# In‑memory storage
notes: dict[str, str] = {}
@mcp.tool
def add_note(name: str, content: str) -> str:
"""Save a note with a given name and content."""
notes[name] = content
return f"Saved note '{name}'."
@mcp.tool
def search_notes(query: str) -> list[dict]:
"""Search notes by keyword. Returns all notes containing the query string."""
results = [
{"name": name, "content": content}
for name, content in notes.items()
if query.lower() in name.lower() or query.lower() in content.lower()
]
return results if results else [{"message": f"No notes found for '{query}'"}]
if __name__ == "__main__":
mcp.run()That’s the entire server—two tools, fully typed, ready to connect.
Install and Run
Install FastMCP:
pip install fastmcpTest it locally with the built‑in inspector:
fastmcp dev notes_server.py:mcpThis opens a browser‑based inspector where you can call add_note and search_notes directly. Try adding a note, then searching for it.
Connect to Claude Desktop
Edit the Claude Desktop config file:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
Add your server definition:
{
"mcpServers": {
"notes": {
"command": "python",
"args": ["/full/path/to/notes_server.py"]
}
}
}Restart Claude Desktop. A hammer icon will appear in the chat input, indicating your tools are connected. You can now ask Claude to “save a note about today’s meeting,” and it will call add_note on your server.
How It Works
FastMCP("Notes")creates a server instance; the string is the name displayed by MCP clients.@mcp.toolregisters a function as an MCP tool. FastMCP reads the function’s type hints and docstring to generate the tool’s schema automatically. The docstring becomes the description the LLM sees when deciding which tool to call.mcp.run()starts the server using the stdio transport (the default). Claude Desktop launches your script as a subprocess and communicates over stdin/stdout.
The notes dictionary is intentionally simple. In production you’d replace it with a database or file storage. The pattern stays the same—FastMCP only cares about type hints and return values, not the internal implementation.
Add More Tools
Extending the server is just adding more decorated functions:
@mcp.tool
def delete_note(name: str) -> str:
"""Delete a note by name."""
if name in notes:
del notes[name]
return f"Deleted note '{name}'."
return f"Note '{name}' not found."No extra configuration or registration steps are required—decorate, restart, and the new tool is available.
Quick Reference
| What | Command |
|---|---|
| Install | pip install fastmcp |
| Test locally | fastmcp dev server.py:mcp |
| Run with stdio | python server.py |
| Run with HTTP | mcp.run(transport="http", port=8000) |
| Client connect | Client("http://localhost:8000/mcp") |
What’s Next
The current server loses notes on restart. For persistence, swap the dictionary for SQLite or a JSON file. If you’re building agents that need to orchestrate multiple MCP servers across services, Nebula provides a coordination layer so you can focus on the tools themselves.
Full FastMCP documentation:
Tools guide (advanced patterns like async tools, Pydantic validation, custom error handling):