Lesson 06 Resources & Prompts
Theme:
Lesson 06 · Slide 1

Lesson 06 — Resources & Prompts

The other two MCP primitives: read-only data and reusable templates.

MCP Servers Module 2

Lesson 06 · Slide 2

The three primitives, revisited

PrimitiveWho initiates the readSchema shapeExample use case
Tool LLM (model decides to call) Typed inputs + outputs Search notes, create a note
Resource Client / user (fetches when relevant) URI + mimeType + read-only contents Read note body, expose config file
Prompt User (invokes by name, e.g. slash command) Named template with optional args "/welcome", "/summarize-today"
Lesson 06 · Slide 3

Resources: URIs and types

Every resource has a URI (e.g., pkb://notes/hello, file:///etc/hosts), an optional name, a mimeType, and read-only contents. The LLM (or user) decides when to fetch — the server just exposes them.

Lesson 06 · Slide 4

Resource discovery

The client typically lists once at startup and refreshes when the server emits notifications/resources/list_changed — so adding a new note doesn't require a client restart.

Lesson 06 · Slide 5

PKB resources example

@mcp.resource("pkb://notes/{title}")
def get_note(title: str) -> str:
    """Read one note by title."""
    if title not in NOTES:
        raise McpError(ErrorData(code=METHOD_NOT_FOUND, message="not found"))
    return NOTES[title]

FastMCP's @mcp.resource(uri_template) registers a parameterized resource. URI variables (like {title}) become function arguments — the SDK extracts them from the requested URI.

Lesson 06 · Slide 6

Static vs dynamic resources

Both work; pick by whether the set of URIs is bounded. If you know all the URIs in advance, static. If they're derived from data, dynamic.

Lesson 06 · Slide 7

Prompts: server-supplied templates

Prompts are reusable prompt fragments the user can invoke. The client typically surfaces them as slash commands or insertable blocks in the chat composer. The server can parameterize them — e.g., a greet prompt that takes a name.

Unlike tools (LLM-initiated) and resources (client-initiated), prompts are user-initiated — the user picks one, the rendered text gets sent to the model.

Lesson 06 · Slide 8

PKB welcome prompt

@mcp.prompt()
def welcome(name: str = "friend") -> str:
    """Greet the user warmly and offer to summarize today's notes."""
    return f"Hi {name}! I'm your PKB assistant. Want me to summarize today's notes?"

The user (or the LLM) can invoke /welcome and receive this rendered prompt — with name filled in from the user's input or the default.

Lesson 06 · Slide 9

When to use a resource vs a tool

Resource if:

It's read-only and the client should fetch when relevant. Think "file the LLM can read by URI."

Tool if:

It does anything else — mutate state, compute, query with structured args, invoke external services.

Lesson 06 · Slide 10

When to use a prompt vs hardcoded text

Prompt if:

The user benefits from naming/invoking it; the LLM benefits from a reusable scaffold (consistent phrasing, slots for parameters).

Hardcoded if:

It's a single sentence in a single tool's response. Don't promote one-off text to a primitive.

Lesson 06 · Slide 11

What we'll do in lab

By the end of lab, the PKB server exposes all three MCP primitives.

Lesson 06 · Slide 12

Wiring it all together

from mcp.server.fastmcp import FastMCP
from mcp.shared.exceptions import McpError
from mcp.types import ErrorData, METHOD_NOT_FOUND

mcp = FastMCP("pkb")

# --- tools ---
@mcp.tool()
def search(args: SearchArgs) -> list[str]: ...

@mcp.tool()
def create_note(args: CreateNoteArgs) -> str: ...

# --- resources ---
@mcp.resource("pkb://about")
def about() -> str: ...

@mcp.resource("pkb://notes/{title}")
def get_note(title: str) -> str: ...

# --- prompts ---
@mcp.prompt()
def welcome(name: str = "friend") -> str: ...

def main():
    mcp.run()

if __name__ == "__main__":
    main()
Lesson 06 · Slide 13

Recap & what's next

Module 3 (Plan C): sampling, elicitation, and a real multi-tool server.
1 / ?