Chapters — Connection Layer (MCP)
MCP·Building With It·8 min read·Sep 5, 2026

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:

Config file location
macOS
~/Library/Application Support/Claude/claude_desktop_config.json
Windows
%APPDATA%\Claude\claude_desktop_config.json
Linux
~/.config/Claude/claude_desktop_config.json
{
  "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.

Try this before moving on

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.

Part of a free guide

Connection Layer (MCP)

A simple guide to MCP, the protocol that lets AI tools talk to the outside world.

Browse All Guides →