Server Primitives
Three primitives, three controllers
Section titled “Three primitives, three controllers”An MCP server can expose exactly three kinds of things. They look similar on the wire — each has a */list method and a way to fetch one — but they differ in who decides when they are used. That single question is the most important design decision in this module.
| Primitive | Controlled by | Typical UI | Method family |
|---|---|---|---|
| Tools | The model — the LLM decides to call them based on the conversation | Tool-call confirmation, “running…” indicator | tools/list, tools/call |
| Resources | The application — the host decides what context to attach | File picker, @-mention, tree view | resources/list, resources/templates/list, resources/read |
| Prompts | The user — a person explicitly picks one | Slash command, template menu | prompts/list, prompts/get |
flowchart LR LLM["Language model"] -- "decides to call" --> T["Tools"] App["Host application"] -- "chooses context" --> R["Resources"] User["Human user"] -- "picks explicitly" --> P["Prompts"] T --> S["MCP server"] R --> S P --> S
The protocol does not enforce these interaction models — a host is free to let the model pick resources or auto-run prompts. But the primitives are designed around them, and the best servers respect the intent: a tool is an action the model may take, a resource is data the app can hand to the model, a prompt is a workflow a person invokes.
Which primitive do I use?
Section titled “Which primitive do I use?”A quick decision table for the most common cases:
| I want to expose… | Use | Why |
|---|---|---|
| ”Search the catalog”, “create a ticket”, “run this query” | Tool | The model needs to decide when to do it, based on the user’s request |
| A file, a database schema, a config document, a log | Resource | It is context, not an action. The host or user attaches it when relevant |
”All files matching repo://{repo}/…” | Resource template | A parameterized family of resources, discoverable and auto-completable |
| ”Review this PR the way our team does” | Prompt | A reusable, user-triggered workflow with arguments and few-shot structure |
| A live dashboard the user should interact with | Tool + MCP Apps extension | Covered in Extensions |
A useful test: if you would be uncomfortable with the model doing it without asking, it is a tool (and you should set its annotations carefully, see Structured Output and Annotations). If it is inert data, it is a resource. If a human would say “run the X workflow”, it is a prompt.
The method families
Section titled “The method families”Every primitive follows the same shape — discover, then act:
| Step | Tools | Resources | Prompts |
|---|---|---|---|
| Discover | tools/list | resources/list, resources/templates/list | prompts/list |
| Act | tools/call | resources/read | prompts/get |
| Change notification | notifications/tools/list_changed | notifications/resources/list_changed, notifications/resources/updated | notifications/prompts/list_changed |
All the */list methods support cursor pagination and carry the caching fields ttlMs and cacheScope. All the “act” methods — tools/call, resources/read, prompts/get — may return an InputRequiredResult when the server needs something from the user first (see Client Interaction).
Declaring capabilities
Section titled “Declaring capabilities”A server advertises which primitives it supports in the capabilities object returned from server/discover. Each primitive has its own key, with optional sub-flags:
{ "capabilities": { "tools": { "listChanged": true }, "resources": { "listChanged": true, "subscribe": true }, "prompts": { "listChanged": false } }}listChanged— the server will emit a*/list_changednotification when the set changes (to clients that opted in viasubscriptions/listen).resources.subscribe— the server can notify about updates to specific resource URIs.
A server that declares a capability MUST answer that primitive’s */list request. The list may be empty and may change over time. If a server does not declare prompts, a client must not send prompts/list.
Lists are stable, not per-connection
Section titled “Lists are stable, not per-connection”Because MCP is stateless (see Foundations), the spec is explicit about what a list may depend on:
- The set of tools, resources, or prompts MUST NOT vary per connection, and MUST NOT change as a side effect of other requests on the same connection. Calling
tools/callmust not unlock a differenttools/listfor that client only. - The set MAY vary by the authorization presented on the request — a caller with
files:writemay see adelete_filetool that a read-only caller does not. Credentials are per-request input, not connection state. - The set MAY change over time for everyone (new deployment, feature flag), which is what
listChangedis for.
This rules out the “stateful wizard” server that reveals tool B only after tool A was called on this session. If you need that, return an explicit handle from tool A and have tool B accept it as an argument — the handle pattern is covered in Tools.
What this module covers
Section titled “What this module covers”| Lesson | You will learn |
|---|---|
| Tools | Definition, naming, tools/list and tools/call, content types, the two error channels |
| Structured Output and Annotations | outputSchema, structuredContent, behavior hints, titles and icons |
| Resources | URIs, text vs blob, templates, annotations, URI schemes |
| Prompts | Arguments, messages, completion, designing reusable workflows |
| Notifications and Caching | subscriptions/listen, list-changed, ttlMs and cacheScope, pagination |