Skip to content

Server Primitives

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.

PrimitiveControlled byTypical UIMethod family
ToolsThe model — the LLM decides to call them based on the conversationTool-call confirmation, “running…” indicatortools/list, tools/call
ResourcesThe application — the host decides what context to attachFile picker, @-mention, tree viewresources/list, resources/templates/list, resources/read
PromptsThe user — a person explicitly picks oneSlash command, template menuprompts/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
Who drives each primitive

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.

A quick decision table for the most common cases:

I want to expose…UseWhy
”Search the catalog”, “create a ticket”, “run this query”ToolThe model needs to decide when to do it, based on the user’s request
A file, a database schema, a config document, a logResourceIt is context, not an action. The host or user attaches it when relevant
”All files matching repo://{repo}/…”Resource templateA parameterized family of resources, discoverable and auto-completable
”Review this PR the way our team does”PromptA reusable, user-triggered workflow with arguments and few-shot structure
A live dashboard the user should interact withTool + MCP Apps extensionCovered 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.

Every primitive follows the same shape — discover, then act:

StepToolsResourcesPrompts
Discovertools/listresources/list, resources/templates/listprompts/list
Acttools/callresources/readprompts/get
Change notificationnotifications/tools/list_changednotifications/resources/list_changed, notifications/resources/updatednotifications/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).

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_changed notification when the set changes (to clients that opted in via subscriptions/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.

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/call must not unlock a different tools/list for that client only.
  • The set MAY vary by the authorization presented on the request — a caller with files:write may see a delete_file tool 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 listChanged is 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.

LessonYou will learn
ToolsDefinition, naming, tools/list and tools/call, content types, the two error channels
Structured Output and AnnotationsoutputSchema, structuredContent, behavior hints, titles and icons
ResourcesURIs, text vs blob, templates, annotations, URI schemes
PromptsArguments, messages, completion, designing reusable workflows
Notifications and Cachingsubscriptions/listen, list-changed, ttlMs and cacheScope, pagination
Which primitive is designed to be invoked by the language model on its own initiative?
A server wants to reveal an extra tool only after a client has called a "login" tool earlier on the same connection. What does the spec say?
What does the `subscribe` sub-capability under `resources` mean?