Building Your First Server
A hands-on walkthrough of writing a small MCP server from scratch and connecting it to a real AI assistant.
What we're building
The fastest way to actually understand MCP is to build something small end to end. This chapter builds a Notebook server: a handful of tools for creating and searching short notes, plus a resource for reading one back. It's small enough to read in one sitting and real enough to extend afterward.
Prerequisites
You'll need Python 3.10 or later and the MCP SDK:
pip install mcp
# or, if you use uv:
uv add mcp
Writing the server
from mcp import FastMCP
from datetime import datetime, timezone
mcp = FastMCP("Notebook")
notes: dict[str, dict] = {}
@mcp.tool()
async def create_note(title: str, body: str) -> str:
"""Create a new note and return its id"""
note_id = f"note-{len(notes) + 1}"
notes[note_id] = {
"title": title,
"body": body,
"created_at": datetime.now(timezone.utc).isoformat(),
}
return note_id
@mcp.tool()
async def list_notes() -> list[dict]:
"""List all notes with their ids and titles"""
return [{"id": note_id, "title": note["title"]} for note_id, note in notes.items()]
@mcp.tool()
async def search_notes(query: str) -> list[dict]:
"""Search note titles and bodies for a keyword"""
needle = query.lower()
return [
{"id": note_id, "title": note["title"]}
for note_id, note in notes.items()
if needle in note["title"].lower() or needle in note["body"].lower()
]
@mcp.resource("note://{note_id}")
async def get_note(note_id: str) -> str:
"""Expose a single note's full content as a readable resource"""
note = notes.get(note_id)
if not note:
return "Note not found"
return f"{note['title']}\n\n{note['body']}"
if __name__ == "__main__":
mcp.run()
Three tools and one resource is enough to see the whole pattern: @mcp.tool() marks a function the AI model can call, and @mcp.resource(...) marks one it can read, addressed by a URI template. The docstring on each isn't decoration; it's what the AI model actually reads to decide when to use it, which matters enough to get its own chapter shortly.
Connecting it to a real assistant
To use this server from Claude Desktop, point its configuration file at the script:
{
"mcpServers": {
"notebook": {
"command": "python",
"args": ["/path/to/notebook_server.py"]
}
}
}
Restart the assistant after saving, and it should list "notebook" among its available tools on the next launch. From there, asking it to "make a note about tomorrow's standup" or "search my notes for standup" exercises the exact tools defined above, no extra wiring required.
Add a fourth tool, delete_note(note_id: str), that removes a note if it exists and returns whether it succeeded. It's a small enough change to make on your own, and it's the same pattern you'll use for every tool from here forward: define the function, describe what it does, decide what it returns.