Lesson 12 - Step 2 of 6

Planning Your Server

Design the architecture and data model for your MCP server.

Planning Before Coding

Before writing any code, let us plan our server architecture. Good planning helps avoid rewrites and makes implementation smoother. We will define:

  • The data model for notes
  • The tool interfaces and their parameters
  • The resource URIs and their responses
  • The prompt templates and their arguments

Data Model

Each note in our system will have the following structure:

python
from dataclasses import dataclass, field
from typing import List, Optional
from datetime import datetime
import uuid


@dataclass
class Note:
    """Represents a single note."""
    id: str
    title: str
    content: str
    tags: List[str] = field(default_factory=list)
    created_at: str = field(default_factory=lambda: datetime.utcnow().isoformat())
    updated_at: str = field(default_factory=lambda: datetime.utcnow().isoformat())

    def to_dict(self) -> dict:
        """Convert to dictionary for JSON serialization."""
        return {
            "id": self.id,
            "title": self.title,
            "content": self.content,
            "tags": self.tags,
            "created_at": self.created_at,
            "updated_at": self.updated_at,
        }

    @classmethod
    def from_dict(cls, data: dict) -> "Note":
        """Create a Note from a dictionary."""
        return cls(
            id=data["id"],
            title=data["title"],
            content=data["content"],
            tags=data.get("tags", []),
            created_at=data.get("created_at", datetime.utcnow().isoformat()),
            updated_at=data.get("updated_at", datetime.utcnow().isoformat()),
        )

    @staticmethod
    def generate_id() -> str:
        """Generate a unique ID for a new note."""
        return str(uuid.uuid4())[:8]

Tool Specifications

Let us define the JSON Schema for each tool:

create_note

json
{
    "name": "create_note",
    "description": "Create a new note with a title, content, and optional tags",
    "inputSchema": {
        "type": "object",
        "properties": {
            "title": {
                "type": "string",
                "description": "The title of the note"
            },
            "content": {
                "type": "string",
                "description": "The content/body of the note"
            },
            "tags": {
                "type": "array",
                "items": {"type": "string"},
                "description": "Optional tags for categorizing the note"
            }
        },
        "required": ["title", "content"]
    }
}

update_note

json
{
    "name": "update_note",
    "description": "Update an existing note by ID",
    "inputSchema": {
        "type": "object",
        "properties": {
            "id": {
                "type": "string",
                "description": "The ID of the note to update"
            },
            "title": {
                "type": "string",
                "description": "New title (optional)"
            },
            "content": {
                "type": "string",
                "description": "New content (optional)"
            },
            "tags": {
                "type": "array",
                "items": {"type": "string"},
                "description": "New tags (optional, replaces existing)"
            }
        },
        "required": ["id"]
    }
}

delete_note

json
{
    "name": "delete_note",
    "description": "Delete a note by ID",
    "inputSchema": {
        "type": "object",
        "properties": {
            "id": {
                "type": "string",
                "description": "The ID of the note to delete"
            }
        },
        "required": ["id"]
    }
}

search_notes

json
{
    "name": "search_notes",
    "description": "Search notes by keyword in title/content or by tag",
    "inputSchema": {
        "type": "object",
        "properties": {
            "query": {
                "type": "string",
                "description": "Search keyword to find in title or content"
            },
            "tag": {
                "type": "string",
                "description": "Filter notes by tag"
            }
        }
    }
}
Input Validation

The JSON Schema serves as documentation and can be used for validation. Claude will use these schemas to understand how to call your tools correctly.

Resource Specifications

Our server will expose three resources:

URI Description MIME Type
notes://list JSON array of all notes with metadata application/json
notes://{id} Single note by ID (dynamic) application/json
notes://tags List of all unique tags application/json

Prompt Specifications

Our prompts will help Claude work with notes effectively:

summarize_notes

json
{
    "name": "summarize_notes",
    "description": "Generate a summary of notes, optionally filtered by tag",
    "arguments": [
        {
            "name": "tag",
            "description": "Optional tag to filter notes before summarizing",
            "required": false
        },
        {
            "name": "style",
            "description": "Summary style: brief, detailed, or bullet-points",
            "required": false
        }
    ]
}

create_from_template

json
{
    "name": "create_from_template",
    "description": "Generate a prompt for creating a note from a template",
    "arguments": [
        {
            "name": "template",
            "description": "Template type: meeting, todo, journal, or idea",
            "required": true
        },
        {
            "name": "topic",
            "description": "Topic or subject for the note",
            "required": true
        }
    ]
}

Server Architecture

Here is the high-level architecture of our server:

diagram
                    STDIO (stdin/stdout)
                            |
                            v
+----------------------------------------------------------+
|                     NotesServer                          |
|----------------------------------------------------------|
|                                                          |
|  handle_request(line) --> parse JSON-RPC                 |
|         |                                                |
|         v                                                |
|  +------------------+                                    |
|  | Method Router    |                                    |
|  |------------------|                                    |
|  | initialize       | --> handle_initialize()            |
|  | tools/list       | --> handle_tools_list()            |
|  | tools/call       | --> handle_tools_call()            |
|  | resources/list   | --> handle_resources_list()        |
|  | resources/read   | --> handle_resources_read()        |
|  | prompts/list     | --> handle_prompts_list()          |
|  | prompts/get      | --> handle_prompts_get()           |
|  +------------------+                                    |
|         |                                                |
|         v                                                |
|  +------------------+                                    |
|  | NotesStorage     |                                    |
|  |------------------|                                    |
|  | load()           |                                    |
|  | save()           |                                    |
|  | create()         |                                    |
|  | update()         |                                    |
|  | delete()         |                                    |
|  | search()         |                                    |
|  +------------------+                                    |
|         |                                                |
|         v                                                |
|    notes.json                                            |
+----------------------------------------------------------+

File Storage

We will use a simple JSON file for persistence. Here is the storage helper:

python
import json
import os
from typing import List, Optional


class NotesStorage:
    """Handle persistent storage of notes."""

    def __init__(self, filepath: str = "notes.json"):
        self.filepath = filepath
        self.notes: List[Note] = []
        self.load()

    def load(self):
        """Load notes from file."""
        if os.path.exists(self.filepath):
            try:
                with open(self.filepath, "r") as f:
                    data = json.load(f)
                    self.notes = [Note.from_dict(n) for n in data]
            except (json.JSONDecodeError, KeyError):
                self.notes = []
        else:
            self.notes = []

    def save(self):
        """Save notes to file."""
        with open(self.filepath, "w") as f:
            json.dump([n.to_dict() for n in self.notes], f, indent=2)

    def get_by_id(self, note_id: str) -> Optional[Note]:
        """Find a note by ID."""
        for note in self.notes:
            if note.id == note_id:
                return note
        return None

    def get_all_tags(self) -> List[str]:
        """Get all unique tags across notes."""
        tags = set()
        for note in self.notes:
            tags.update(note.tags)
        return sorted(list(tags))
Production Considerations

For a production server, you would want to use a proper database, add file locking for concurrent access, and implement backup/recovery. For this capstone, JSON file storage is sufficient to demonstrate the MCP concepts.

Planning Checklist

Before moving to implementation, verify you understand:

  • The Note data model with all fields
  • How each tool works and its parameters
  • The three resource URIs and what they return
  • How the two prompts generate messages
  • The overall server architecture
Next Up

In the next step, we will start coding! We will implement the core server structure, the initialization handshake, and the four tools.