Adding Resources and Prompts
Complete the server with resources and prompt templates.
Completing the Server
With tools implemented, let us add resources and prompts to make our server fully featured. Resources expose notes as readable data, and prompts provide Claude with helpful templates.
You are continuing to edit notes_server.py from the previous step.
All code in this step should be added to your existing file.
Section 6: Resources Implementation
Edit: notes_server.py
Add the resources handlers to the NotesServer class:
# -------------------------------------------------------------------------
# Section 6: Resources List Handler
# -------------------------------------------------------------------------
def handle_resources_list(self, params: dict) -> dict:
"""Return list of available resources."""
# Start with static resources
resources = [
{
"uri": "notes://list",
"name": "All Notes",
"description": "List of all notes with metadata",
"mimeType": "application/json",
},
{
"uri": "notes://tags",
"name": "All Tags",
"description": "List of all unique tags",
"mimeType": "application/json",
},
]
# Add dynamic resources for each note
for note in self.storage.notes:
resources.append({
"uri": f"notes://{note.id}",
"name": note.title,
"description": f"Note: {note.title}",
"mimeType": "application/json",
})
return {"resources": resources}
Edit: notes_server.py
Add the resources read handler and implementations:
def handle_resources_read(self, params: dict) -> dict:
"""Read a specific resource."""
uri = params.get("uri", "")
if uri == "notes://list":
return self.resource_list_all()
elif uri == "notes://tags":
return self.resource_list_tags()
elif uri.startswith("notes://"):
note_id = uri.replace("notes://", "")
return self.resource_get_note(note_id)
else:
raise ValueError(f"Unknown resource: {uri}")
# -------------------------------------------------------------------------
# Resource Implementations
# -------------------------------------------------------------------------
def resource_list_all(self) -> dict:
"""Return all notes as a resource."""
notes_data = []
for note in self.storage.notes:
notes_data.append({
"id": note.id,
"title": note.title,
"tags": note.tags,
"created_at": note.created_at,
"updated_at": note.updated_at,
"preview": note.content[:100] + "..." if len(note.content) > 100 else note.content,
})
return {
"contents": [{
"uri": "notes://list",
"mimeType": "application/json",
"text": json.dumps({"notes": notes_data, "count": len(notes_data)}, indent=2),
}]
}
def resource_list_tags(self) -> dict:
"""Return all tags as a resource."""
tags = self.storage.get_all_tags()
# Count notes per tag
tag_counts = {}
for tag in tags:
tag_counts[tag] = len([n for n in self.storage.notes if tag in n.tags])
return {
"contents": [{
"uri": "notes://tags",
"mimeType": "application/json",
"text": json.dumps({
"tags": tags,
"counts": tag_counts,
"total": len(tags),
}, indent=2),
}]
}
def resource_get_note(self, note_id: str) -> dict:
"""Return a specific note as a resource."""
note = self.storage.get_by_id(note_id)
if not note:
raise ValueError(f"Note not found: {note_id}")
return {
"contents": [{
"uri": f"notes://{note_id}",
"mimeType": "application/json",
"text": json.dumps(note.to_dict(), indent=2),
}]
}
Notice how we generate a resource for each note dynamically. This
allows Claude to request any specific note by its ID using the
notes://{id} URI pattern.
Section 7: Prompts Implementation
Edit: notes_server.py
Add the prompts handlers after Section 6:
# -------------------------------------------------------------------------
# Section 7: Prompts List Handler
# -------------------------------------------------------------------------
def handle_prompts_list(self, params: dict) -> dict:
"""Return list of available prompts."""
return {
"prompts": [
{
"name": "summarize_notes",
"description": "Generate a summary of notes, optionally filtered by tag",
"arguments": [
{
"name": "tag",
"description": "Optional tag to filter notes",
"required": False,
},
{
"name": "style",
"description": "Summary style: brief, detailed, or bullet-points",
"required": False,
},
],
},
{
"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,
},
],
},
]
}
Edit: notes_server.py
Add the prompts get handler and implementations:
def handle_prompts_get(self, params: dict) -> dict:
"""Get a specific prompt with its messages."""
name = params.get("name", "")
arguments = params.get("arguments", {})
if name == "summarize_notes":
return self.prompt_summarize_notes(arguments)
elif name == "create_from_template":
return self.prompt_create_from_template(arguments)
else:
raise ValueError(f"Unknown prompt: {name}")
# -------------------------------------------------------------------------
# Prompt Implementations
# -------------------------------------------------------------------------
def prompt_summarize_notes(self, args: dict) -> dict:
"""Generate a summarization prompt."""
tag = args.get("tag")
style = args.get("style", "brief")
# Get relevant notes
if tag:
notes = self.storage.search(tag=tag)
else:
notes = self.storage.notes
if not notes:
return {
"messages": [{
"role": "user",
"content": {
"type": "text",
"text": "There are no notes to summarize.",
},
}]
}
# Build the notes content
notes_text = ""
for note in notes:
notes_text += f"## {note.title}\n"
notes_text += f"Tags: {', '.join(note.tags) if note.tags else 'none'}\n"
notes_text += f"Created: {note.created_at}\n\n"
notes_text += f"{note.content}\n\n---\n\n"
# Style instructions
style_instructions = {
"brief": "Provide a brief 2-3 sentence summary.",
"detailed": "Provide a detailed summary covering all main points.",
"bullet-points": "Summarize using bullet points for easy scanning.",
}
instruction = style_instructions.get(style, style_instructions["brief"])
return {
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": f"Please summarize the following notes. {instruction}\n\n{notes_text}",
},
}
]
}
def prompt_create_from_template(self, args: dict) -> dict:
"""Generate a note creation prompt from a template."""
template = args.get("template", "")
topic = args.get("topic", "")
if not template or not topic:
return {
"messages": [{
"role": "user",
"content": {
"type": "text",
"text": "Error: Both 'template' and 'topic' are required.",
},
}]
}
templates = {
"meeting": f"""Create a meeting notes template for: {topic}
Please include:
- Date and attendees section
- Agenda items
- Discussion points
- Action items with owners
- Next steps
Format it as a structured note that I can save.""",
"todo": f"""Create a todo list for: {topic}
Please include:
- Main objectives
- Prioritized task list with checkboxes [ ]
- Deadlines if applicable
- Dependencies between tasks
Format it as a structured note that I can save.""",
"journal": f"""Create a journal entry template for: {topic}
Please include:
- Date header
- Reflection prompts
- Space for thoughts and observations
- Gratitude section
- Goals for tomorrow
Format it as a structured note that I can save.""",
"idea": f"""Create an idea note for: {topic}
Please include:
- Core concept description
- Problem it solves
- Key benefits
- Potential challenges
- Next steps to explore
Format it as a structured note that I can save.""",
}
template_text = templates.get(template)
if not template_text:
valid = ", ".join(templates.keys())
return {
"messages": [{
"role": "user",
"content": {
"type": "text",
"text": f"Error: Unknown template '{template}'. Valid templates: {valid}",
},
}]
}
return {
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": template_text,
},
}
]
}
Section 8: Main Entry Point
Edit: notes_server.py
Add the main entry point at the end of your file:
# =============================================================================
# Section 8: Main Entry Point
# =============================================================================
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Notes MCP Server")
parser.add_argument(
"--storage",
default="notes.json",
help="Path to notes storage file (default: notes.json)"
)
args = parser.parse_args()
server = NotesServer(storage_path=args.storage)
server.run()
Complete File Structure
Your notes_server.py should now contain:
notes_server.py
|
+-- Imports and Data Model
| +-- Note dataclass
|
+-- NotesStorage class
| +-- load(), save()
| +-- create(), update(), delete()
| +-- search(), get_by_id(), get_all_tags()
|
+-- NotesServer class
| +-- run(), send_response(), make_response(), make_error()
| +-- handle_request() - router
| +-- handle_initialize()
| +-- handle_tools_list(), handle_tools_call()
| +-- tool_create_note(), tool_update_note()
| +-- tool_delete_note(), tool_search_notes()
| +-- handle_resources_list(), handle_resources_read()
| +-- resource_list_all(), resource_list_tags(), resource_get_note()
| +-- handle_prompts_list(), handle_prompts_get()
| +-- prompt_summarize_notes(), prompt_create_from_template()
|
+-- Main entry point
Your Notes MCP Server is now complete with all primitives implemented. In the next step, we will test it thoroughly with curl and integrate it with Claude Desktop.
What We Built
Let us review what we have accomplished:
| Component | Count | Description |
|---|---|---|
| Tools | 4 | create, update, delete, search |
| Resources | 3+ | list, tags, plus dynamic per-note |
| Prompts | 2 | summarize, create_from_template |
| Storage | 1 | JSON file persistence |