Start here: the basics

MCP resources and prompts: who controls each one

A server offers three things over MCP: tools, resources and prompts. Only one of the three is picked by the model.

By
Dan Flanagan
Updated
October 15, 2026
Checked against
the MCP specification (modelcontextprotocol.io, revision 2026-07-28), mcp 2.3.0 on Python 3.14.7, and VS Code and Claude Code's own MCP docs, October 2026
Reading time
4 min

A server offers three things over MCP: tools, resources and prompts. A tool is an action the model can call. A resource is data the model can read. A prompt is a template a person picks on purpose. The spec gives each one a different owner.

Three primitives, three owners

The MCP specification describes a user interaction model for each primitive, and the three are not the same.

Tools are model-controlled. The tools page says the model "can discover and invoke tools automatically based on its contextual understanding and the user's prompts." You ask a question, the model decides a tool fits, and it calls it. What is an MCP server? covers this path in full.

Resources are application-driven. The resources page says the host application decides how to incorporate them, and lists three patterns: showing them in a tree or list for the person to pick from, letting the person search and filter them, or including them automatically by heuristic. The model does not reach for a resource the way it reaches for a tool. The application chooses to hand one over, or lets the person do it.

Prompts are user-controlled. The prompts page is explicit that this describes who decides to invoke the prompt, not who wrote its content. The server author writes the prompt text. The person using the app decides when to run it, usually by typing something that looks like a slash command.

So the model only ever picks one of the three on its own. A resource gets into context because the application put it there, and a prompt runs because a person asked for it by name.

A small server with one of each

The official Python SDK (mcp, version 2.3.0 at the time of writing) gives each primitive its own decorator: @mcp.tool, @mcp.resource and @mcp.prompt. Here is a server with one resource, one resource template and one prompt, no tools at all:

python
from mcp.server import MCPServer

mcp = MCPServer("library-demo")

BOOKS = {
    "moby-dick": "Call me Ishmael. Some years ago...",
    "frankenstein": "You will rejoice to hear that no disaster has accompanied the commencement of an enterprise...",
}


@mcp.resource("library://catalog")
def catalog() -> str:
    """The list of books this server knows about."""
    return "\n".join(BOOKS.keys())


@mcp.resource("library://books/{title}")
def book(title: str) -> str:
    """The opening line of one book, by its catalog title."""
    if title not in BOOKS:
        raise ValueError(f"No book called {title!r}.")
    return BOOKS[title]


@mcp.prompt()
def summarize_book(title: str) -> str:
    """A prompt that asks the model to summarize one catalog book."""
    return f"Read library://books/{title} and summarize it in two sentences."


if __name__ == "__main__":
    mcp.run()

library://catalog is a plain resource with a fixed URI. library://books/{title} is a resource template, a URI with a placeholder the client fills in, which the resources page calls out separately from a plain resource because a client has to ask for its parameters before it can read one. summarize_book is a prompt, and its return value becomes the message a client sends to the model once a person asks for it.

What a client actually gets back

The SDK's test client talks to the server over stdio and gets back the same shapes the spec defines for resources/list, resources/templates/list, resources/read, prompts/list and prompts/get. Running it against the server above gives this, trimmed to the fields the spec defines (the SDK's result objects also carry caching metadata the spec does not require):

text
resources/list ->
{
  "resources": [
    {
      "name": "catalog",
      "uri": "library://catalog",
      "description": "The list of books this server knows about.",
      "mime_type": "text/plain"
    }
  ]
}

resources/templates/list ->
{
  "resource_templates": [
    {
      "name": "book",
      "uri_template": "library://books/{title}",
      "description": "The opening line of one book, by its catalog title.",
      "mime_type": "text/plain"
    }
  ]
}

resources/read library://catalog ->
{
  "contents": [
    { "uri": "library://catalog", "mime_type": "text/plain", "text": "moby-dick\nfrankenstein" }
  ]
}

resources/read library://books/moby-dick ->
{
  "contents": [
    { "uri": "library://books/moby-dick", "mime_type": "text/plain", "text": "Call me Ishmael. Some years ago..." }
  ]
}

prompts/list ->
{
  "prompts": [
    {
      "name": "summarize_book",
      "description": "A prompt that asks the model to summarize one catalog book.",
      "arguments": [{ "name": "title", "required": true }]
    }
  ]
}

prompts/get summarize_book {"title": "frankenstein"} ->
{
  "description": "A prompt that asks the model to summarize one catalog book.",
  "messages": [
    { "role": "user", "content": { "type": "text", "text": "Read library://books/frankenstein and summarize it in two sentences." } }
  ]
}

prompts/get returned a message telling the model to go read library://books/frankenstein, without the book's text. The prompt's job was to produce that instruction. Whether the model then reads the resource depends on the client wiring the two together, an application-level decision the resources page leaves open.

Which clients actually show you this

modelcontextprotocol.io/clients lists MCP as broadly supported across "Claude and ChatGPT," Visual Studio Code, Cursor and others, but the page is a short overview and does not say which of those clients call resources/list or prompts/list at all.

You have to go to each client's own docs for that. VS Code's documentation is specific: it describes a resources picker, "Add Context > MCP Resources," and a prompt command typed as /<MCP server>.<prompt>, so both primitives have a real place in the UI. Claude Code's MCP docs say it sends tools/list, prompts/list and resources/list when it connects to a server, and keeps the cached results of all three if the connection drops and reconnects.

Claude's own guide to connectors, the consumer-facing way to add MCP servers to Claude.ai and Claude Desktop, reads differently. It talks entirely about tools and actions, read and write capabilities, and never mentions resources or prompts as a separate thing a connector can offer. That doesn't mean they're unsupported there, only that the guide doesn't document them.

The practical result: write a tool, and almost any client puts it in front of the model. Write a resource or a prompt, and whether a person ever sees it depends on whether their specific client built a picker or a slash command for it. Check the client's own docs before you plan a feature around either one.

Next

How to build an MCP server in Python covers tools in full, with annotations and error handling. What is an MCP client? explains the host and client side of this same connection. How to test an MCP server with the MCP Inspector is the fastest way to see a server's resources and prompts without writing a client. What happens when an AI agent has too many tools covers the model-controlled side in more depth. Because the model calls tools on its own, tools are where access control usually sits, and MCP permissions shows how Statio grants them one tool at a time.

Questions

Not on its own. A resource gets into the conversation because the application fetches it, usually after a person picks it or the application decides to include it. The model never sends a resources/read request itself.

No. Most servers in production ship tools only. Resources and prompts are optional, and plenty of clients give them little or no UI, so test with the client you actually plan to use before relying on either.

No. An MCP prompt is a named template the server offers that a person chooses to run, usually through a slash command. A system prompt is set by the application before the conversation starts and the person using it never sees a picker for it.

A resource with a parameterized URI, like library://books/{title}, instead of a fixed one. A client lists it with resources/templates/list and fills in the parameter before reading it.