Lesson 12 - Step 4 of 6

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.

Continuing from Step 3

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:

python notes_server.py - Add after Section 5
    # -------------------------------------------------------------------------
    # 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:

python notes_server.py - Add after resources list
    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),
            }]
        }
Dynamic Resources

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:

python notes_server.py - Add 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:

python notes_server.py - Add after prompts list
    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:

python notes_server.py - Add at end of 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:

text
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
Server Complete!

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