Key Takeaway
MCP servers expose three primitives: tools, resources, and prompts. Tools are model-controlled actions the AI can invoke, resources are application-controlled context addressed by URI, and prompts are user-controlled templates. Knowing which primitive fits which job is the difference between a server a model uses well and one that constantly reaches for the wrong lever.
When people first meet the Model Context Protocol, they tend to fixate on tools and assume that is all a server offers. In fact the specification defines three server primitives, and the difference between them is not what they do but who decides when they run. Getting that distinction right is what separates a server a model uses smoothly from one it fumbles.
This explainer walks through all three primitives, shows how they differ in code, and gives a simple rule for choosing between them when you design a server.
The three server primitives
An MCP server can expose tools, resources, and prompts. They are distinguished by their control model, meaning which party in the conversation is expected to decide when the capability is invoked. That single idea explains most of how they are meant to be used.
In short:
- Tools are model-controlled: the AI decides to call them, typically to take an action or fetch something dynamic.
- Resources are application-controlled: the client application decides which ones to load as context, each addressed by a URI.
- Prompts are user-controlled: a person picks them deliberately, often surfaced as a slash command or menu item.
How they differ, in code
The distinction is easiest to see when you register one of each on a server. The shape is similar, but the intent behind each registration is different, and clients treat them differently as a result.
Here all three are registered on the same server object:
// Tool: model-controlled action the client can invoke
server.tool(
"create_issue",
{ title: z.string() },
async ({ title }) => ({
content: [{ type: "text", text: "Created: " + title }],
})
);
// Resource: application-controlled context, addressed by a URI
server.resource(
"readme",
"file:///project/README.md",
async (uri) => ({
contents: [{ uri: uri.href, text: "Project overview..." }],
})
);
// Prompt: user-controlled template, surfaced as a slash command
server.prompt(
"summarize",
{ topic: z.string() },
({ topic }) => ({
messages: [
{
role: "user",
content: { type: "text", text: "Summarize " + topic },
},
],
})
);The control model is the mental model to hold onto. Before adding a capability, ask who should decide when it runs. That answer, not the implementation, tells you whether it should be a tool, a resource, or a prompt.
Tools in depth
Tools are the primitive most servers lean on because they let the model act. Because the model invokes them on its own judgment, they carry the most responsibility and deserve the most care.
- They can have side effects, from reading a record to changing state, so their descriptions must set clear expectations.
- Their input schemas are the contract; strict validation is what keeps a model-chosen call from doing something unintended.
- A precise description is not documentation for humans but instructions the model relies on to decide when to call.
- They return content the client feeds back to the model, closing the loop so the result can inform the next step.
Resources and prompts
Resources expose data the application can choose to pull into context, each identified by a URI such as a file path or a custom scheme. They are meant for read-only context, like a document or a configuration file, that the client decides is relevant rather than the model demanding it.
Prompts are reusable, parameterized templates that a user invokes on purpose. They are ideal for common, structured requests, a code-review prompt or a summarize prompt, that you want to offer as a one-click starting point rather than making people retype every time.
Do not rely on resources for instructions the model must always follow. Because resources are application-controlled, there is no guarantee a given client loads a particular one into context, so anything mandatory belongs in a tool description or a prompt, not in a resource the client may quietly skip.
Choosing the right primitive
Most design mistakes come from forcing one primitive to do another's job. A short decision rule keeps a server clean and predictable for the clients that consume it.
- If the model should decide when to invoke it and it may act or fetch dynamically, make it a tool.
- If it is read-only context the application selects, make it a resource and give it a stable URI.
- If a person should trigger it deliberately as a reusable template, make it a prompt.
The three MCP primitives are not interchangeable; each answers a different question about who is in control. Tools are for the model to act, resources are for the application to supply context, and prompts are for the user to invoke a template. Design around that split and your server will feel obvious to every client that connects to it.

