Chapter 4 · Part 2

Resources & prompts

Tools do things. The other two primitives provide things: resources hand data to the model, and prompts give users reusable templates. Same FastMCP, two more decorators.

Resources: data by URI

A resource is read-only data the model (or user) can load into context — like a GET endpoint. Each has a URI, and the URI can be templated with {placeholders} that become function arguments:

server.py — resources
@mcp.resource("notes://all")
def all_notes() -> str:
  """The full contents of the user's notes file."""
  with open("notes.txt") as f:
      return f.read()


@mcp.resource("note://{topic}")
def note_by_topic(topic: str) -> str:
  """The notes lines about one topic."""
  with open("notes.txt") as f:
      return "\n".join(l for l in f if topic.lower() in l.lower())

Ask for note://travel and the client calls note_by_topic("travel"). The difference from a tool is intent: a resource exposes data to read, a tool performs an action. Reach for a resource when the model just needs to see something, a tool when it needs to do something.

Prompts: reusable templates

A prompt is a parameterized template a user can pick from the host's UI — "slash commands" for your server. It returns the text that gets sent to the model:

server.py — a prompt
@mcp.prompt()
def summarize_notes(topic: str, style: str = "bullet points") -> str:
  """Summarize the user's notes on a topic."""
  return f"Read my notes on {topic} and summarize them as {style}."

Now a user can invoke "summarize_notes" with topic="travel", and the host expands it into a full instruction — no retyping the same prompt every time.

📌Who's in control of each
  • Tools — the model decides to call them, mid-task.
  • Resources — the application (or user) chooses what data to load in.
  • Prompts — the user picks them deliberately, like a menu command.

Your server now speaks all three primitives. Time to plug it into Claude.