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:
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
{
"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
{
"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
{
"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
{
"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"
}
}
}
}
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
{
"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
{
"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:
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:
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))
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
In the next step, we will start coding! We will implement the core server structure, the initialization handshake, and the four tools.