Objective
Resources: read-only, addressed by URI
@mcp.resource("notes://recent")
def recent_notes() -> str:
"""The 5 most recently added notes, always available as context."""
conn = sqlite3.connect(DB_PATH)
rows = conn.execute("SELECT content FROM notes ORDER BY created_at DESC LIMIT 5").fetchall()
conn.close()
return "\n".join(r[0] for r in rows)
Notice the decorator is @mcp.resource(uri), not @mcp.tool() -- and there's no input_schema because resources don't take arguments the way tools do (a parameterized resource template is possible with {} placeholders in the URI, but this course's resource is static: one URI, one read). In Claude Desktop, the user (or the host on the user's behalf) attaches this resource to a conversation explicitly, rather than the model deciding mid-conversation to call it -- that's the entire point: standing context, not an action.
Resources move the decision from the model to the host/user
A tool means "the model decides if and when to use this." A resource means "this is available context, attached deliberately." If you find yourself writing a tool whose only job is "fetch some standing data with no real parameters," it's very often a resource wearing a tool's clothes -- and the model will occasionally forget to call it, where an attached resource never has that failure mode because it doesn't depend on the model remembering.
Prompts: reusable, parameterized templates
A prompt in MCP is a template the user invokes (often via a slash-command-style UI in the host), not something the model calls itself.
Prompts are for the user, not the model
This is the primitive most tutorials skip entirely, probably because it's the least "agentic" of the three -- there's no autonomous decision-making involved. But a well-designed prompt template turns "explain this codebase" (typed fresh every time, worded slightly differently each time) into a one-click, consistently-worded request with parameters your server controls. Think of it as the MCP equivalent of a saved query.
You're designing a server for a support team. You want a way for the user to say "summarize this ticket in the format our team always uses" without retyping the exact format instructions every time. Which primitive fits best?
This is exactly what prompt templates are for: a fixed, reusable structure the user invokes deliberately, parameterized by something that changes (the ticket ID) while the format stays constant. It's not an autonomous action (not a tool) and it's not standing reference data (not a resource) -- it's a reusable request shape.
What you're building
server.py extended with one resource (notes://recent, the 5 most recent notes, always available as context without a tool call) and one prompt template (parameterized, encoding a fixed request shape) — tested by attaching the resource in a real Claude Desktop conversation and invoking the prompt template.