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 fastmcp
Test it locally with the built-in inspector:
fastmcp dev notes_server.py:mcp
This 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
To use your server inside Claude Desktop, edit the config file.
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
Add your server:
{
"mcpServers": {
"notes": {
"command": "python",
"args": ["/full/path/to/notes_server.py"]
}
}
}
Restart Claude Desktop. You'll see a hammer icon in the chat input indicating your tools are connected. Ask Claude to "save a note about today's meeting" and it will call add_note on your server.
How It Works
FastMCP handles all the MCP protocol details. Here's what each piece does:
FastMCP("Notes") creates a server instance. The string is the server name that MCP clients display.
@mcp.tool registers 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 tool description that the LLM reads 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 swap this for a database or file storage. The pattern stays the same -- FastMCP doesn't care what your function does internally, only that it has type hints and returns a value.
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 configuration, no registration steps. Decorate, restart, done.
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
This server loses notes when you restart it. 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 handles that coordination layer so you can focus on the tools themselves.
The full FastMCP docs are at gofastmcp.com. The Tools guide covers advanced patterns like async tools, Pydantic validation, and custom error handling.
Top comments (0)