Lesson 9 - Step 5 of 5

Building Useful Prompts

Create practical prompts for code review, summarization, and explanation tasks.

Building on Previous Lessons

In this step, we'll create prompts.py — a reusable module for prompts. This follows the same decorator pattern as tools and resources.

Building on Lessons 3-8

This lesson imports from jsonrpc.py (Lesson 3). Make sure all previous modules are in your project directory.

Your Project Structure

text
your_project/
    jsonrpc.py              # Lesson 3
    mcp_core.py             # Lesson 4
    tools.py                # Lesson 5
    resources.py            # Lesson 6
    stdio_transport.py      # Lesson 7
    http_sse_transport.py   # Lesson 8
    prompts.py              # NEW - We'll create this
    prompts_example.py      # NEW - Example using prompts.py
How to Read Code Blocks

In this lesson:

  • Filename in header = Write this code to that file
  • "Add after Section X" = Append to your existing file
  • bash blocks = Commands to run in your terminal
  • "Expected Output" = What you should see (don't write this)

Section 1: Create prompts.py

Create: prompts.py

Create the prompts module with a registry class:

python prompts.py
"""
prompts.py - Reusable prompt registry for MCP servers.

This module builds on jsonrpc.py from Lesson 3.
"""

from typing import Callable

# Import error handling from Lesson 3
from jsonrpc import JsonRpcError, JsonRpcErrorCode


# =============================================================================
# Section 1: Prompt Registry
# =============================================================================

class PromptRegistry:
    """
    Registry for MCP prompts with decorator-based registration.

    Usage:
        prompts = PromptRegistry()

        @prompts.register(
            name="greet",
            description="Generate a greeting",
            arguments=[{"name": "name", "description": "Name to greet", "required": True}]
        )
        def greet_prompt(args: dict) -> dict:
            name = args.get("name", "World")
            return {
                "description": f"Greeting for {name}",
                "messages": [{"role": "user", "content": {"type": "text", "text": f"Hello, {name}!"}}]
            }
    """

    def __init__(self):
        self._prompts: dict[str, dict] = {}
        self._generators: dict[str, Callable] = {}

    def register(
        self,
        name: str,
        description: str,
        arguments: list[dict] | None = None
    ) -> Callable:
        """Decorator to register a prompt generator."""
        def decorator(func: Callable[[dict], dict]) -> Callable:
            self._prompts[name] = {
                "name": name,
                "description": description,
                "arguments": arguments or []
            }
            self._generators[name] = func
            return func
        return decorator

    def list_prompts(self) -> list[dict]:
        """Return all prompt definitions for prompts/list."""
        return list(self._prompts.values())

    def get_prompt(self, name: str, arguments: dict) -> dict:
        """Generate a prompt by name with arguments."""
        if name not in self._generators:
            raise JsonRpcError(
                JsonRpcErrorCode.INVALID_PARAMS,
                f"Unknown prompt: {name}"
            )

        # Validate required arguments
        prompt_def = self._prompts[name]
        for arg in prompt_def.get("arguments", []):
            if arg.get("required") and arg["name"] not in arguments:
                raise JsonRpcError(
                    JsonRpcErrorCode.INVALID_PARAMS,
                    f"Missing required argument: {arg['name']}"
                )

        return self._generators[name](arguments)

Section 2: Router Integration

Edit: prompts.py

Add after Section 1:

python prompts.py - Add after Section 1
# =============================================================================
# Section 2: MCP Router Integration
# =============================================================================

def register_prompt_handlers(router, prompts: PromptRegistry) -> None:
    """
    Register prompts/list and prompts/get handlers with an MCP router.

    Args:
        router: A JsonRpcRouter from mcp_core.py
        prompts: A PromptRegistry with registered prompts
    """

    @router.method("prompts/list")
    def handle_prompts_list(params: dict) -> dict:
        return {"prompts": prompts.list_prompts()}

    @router.method("prompts/get")
    def handle_prompts_get(params: dict) -> dict:
        name = params.get("name", "")
        arguments = params.get("arguments", {})
        return prompts.get_prompt(name, arguments)

Add a test block at the end:

python prompts.py - Add at the end
# =============================================================================
# Test the module
# =============================================================================

if __name__ == "__main__":
    prompts = PromptRegistry()

    @prompts.register(
        name="greet",
        description="Generate a greeting",
        arguments=[{"name": "name", "description": "Name to greet", "required": True}]
    )
    def greet_prompt(args: dict) -> dict:
        name = args.get("name", "World")
        return {
            "description": f"Greeting for {name}",
            "messages": [{"role": "user", "content": {"type": "text", "text": f"Hello, {name}!"}}]
        }

    print("Registered prompts:")
    for p in prompts.list_prompts():
        print(f"  - {p['name']}: {p['description']}")

    print("\nGenerating prompt:")
    result = prompts.get_prompt("greet", {"name": "MCP"})
    print(f"  {result}")

Try It

bash
python prompts.py

Section 3: Example with Useful Prompts

Create: prompts_example.py

Create a server with practical prompts:

python prompts_example.py
"""
prompts_example.py - Server with useful prompts

Run: python prompts_example.py
"""

# Import from our modules
from mcp_core import router
from prompts import PromptRegistry, register_prompt_handlers
from http_sse_transport import run_http_server

# =============================================================================
# Register prompts
# =============================================================================

prompt_registry = PromptRegistry()


@prompt_registry.register(
    name="code-review",
    description="Review code for issues",
    arguments=[
        {"name": "code", "description": "Code to review", "required": True},
        {"name": "language", "description": "Programming language", "required": True},
        {"name": "focus", "description": "Focus: security, performance, style", "required": False}
    ]
)
def code_review(args: dict) -> dict:
    code = args.get("code", "")
    language = args.get("language", "")
    focus = args.get("focus", "all")

    text = f"""Please review this {language} code, focusing on {focus}:

```{language}
{code}
```

Identify issues and suggest improvements."""

    return {
        "description": f"Code review ({focus})",
        "messages": [{"role": "user", "content": {"type": "text", "text": text}}]
    }


@prompt_registry.register(
    name="summarize",
    description="Summarize text",
    arguments=[
        {"name": "text", "description": "Text to summarize", "required": True},
        {"name": "style", "description": "Style: tldr, bullets, paragraph", "required": False}
    ]
)
def summarize(args: dict) -> dict:
    text = args.get("text", "")
    style = args.get("style", "paragraph")

    styles = {
        "tldr": "Provide a one-sentence TL;DR.",
        "bullets": "Summarize as bullet points.",
        "paragraph": "Write a paragraph summary."
    }

    prompt_text = f"""Summarize this text:

{text}

{styles.get(style, styles['paragraph'])}"""

    return {
        "description": f"Summary ({style})",
        "messages": [{"role": "user", "content": {"type": "text", "text": prompt_text}}]
    }


@prompt_registry.register(
    name="explain-code",
    description="Explain what code does",
    arguments=[
        {"name": "code", "description": "Code to explain", "required": True},
        {"name": "audience", "description": "Audience: beginner, developer, expert", "required": False}
    ]
)
def explain_code(args: dict) -> dict:
    code = args.get("code", "")
    audience = args.get("audience", "developer")

    text = f"""Explain this code for a {audience}:

```
{code}
```

Cover what it does and how it works."""

    return {
        "description": f"Code explanation for {audience}",
        "messages": [{"role": "user", "content": {"type": "text", "text": text}}]
    }


# Register with the MCP router
register_prompt_handlers(router, prompt_registry)


# =============================================================================
# Run the server
# =============================================================================

if __name__ == "__main__":
    run_http_server()

Section 4: Test Your Server

Start the Server

bash
python prompts_example.py

Test 1: Initialize the Server

In another terminal, initialize the MCP connection:

bash
# Initialize
curl -X POST http://localhost:8080/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","clientInfo":{"name":"test"}}}'

# Initialized notification
curl -X POST http://localhost:8080/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"notifications/initialized"}'

Test 2: List Prompts

bash
curl -X POST http://localhost:8080/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc": "2.0", "id": 2, "method": "prompts/list", "params": {}}'

Test 3: Get a Code Review Prompt

bash
curl -X POST http://localhost:8080/mcp \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "prompts/get",
    "params": {
      "name": "code-review",
      "arguments": {
        "code": "def add(a, b):\n    return a + b",
        "language": "python",
        "focus": "style"
      }
    }
  }'

Test 4: Get a Summary Prompt

bash
curl -X POST http://localhost:8080/mcp \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 4,
    "method": "prompts/get",
    "params": {"name": "summarize", "arguments": {"text": "MCP is great!", "style": "tldr"}}
  }'

What You've Built

You've created a reusable prompt system:

File Purpose
prompts.py Reusable PromptRegistry with decorator pattern
prompts_example.py Example server using prompts + HTTP transport

The pattern mirrors tools.py and resources.py:

  • Decorator-based registration for clean prompt definitions
  • Imports from jsonrpc.py for error handling
  • Works with any transport (HTTP or STDIO)
Combining All Three

You can now build a server with tools, resources, AND prompts by importing all three modules and calling all three registration functions!

What's Next?

In the next lesson, we'll switch perspectives and build an MCP client. You'll learn how to connect to MCP servers and invoke their capabilities.