MCP Tools, Resources and Prompts: The Three Server Primitives
An MCP server can offer three kinds of capability. Tools are actions the model decides to run. Resources are data the application chooses to load. Prompts are templates the user picks. The difference is mostly about who is in control, and choosing the right primitive makes your server easier to use.
Tools: model-controlled actions
A tool is a function with a name, a description and an input schema. The model reads the list of tools and decides on its own when to call one. Tools can read data, change data or run computations. Typical examples are search_issues, send_email and run_query.
Because the model chooses to call them, tools are the most powerful and the riskiest primitive. A good host asks the user to approve calls that have side effects. The protocol methods are tools/list and tools/call.
Resources: application-controlled data
A resource is a piece of content identified by a URI, such as file:///project/README.md or db://orders/schema. The server lists what exists, and the client reads a resource with resources/read. The host application usually decides which resources to attach to a conversation, often by letting the user pick them from a menu.
Resources are meant for context rather than action. Reading one should not change anything. They carry a MIME type, so a client knows whether the content is text, JSON or something else. Servers can also publish URI templates, such as users://{id}/profile, for families of resources.
Prompts: user-controlled templates
A prompt is a reusable message template with optional arguments. The user explicitly selects it, and many clients show prompts as slash commands. For example, a review_code prompt might take a language argument and expand to a full set of review instructions with the right context included.
The client fetches a prompt with prompts/get and receives a list of messages ready to send to the model.
Comparing the three
| Primitive | Controlled by | Purpose | Side effects? |
|---|---|---|---|
| Tool | The model | Do something or compute something | Possible |
| Resource | The application | Provide context data | Should not |
| Prompt | The user | Start a guided workflow | No |
One server, all three
Here is a small TypeScript server that uses each primitive, so you can see how they differ in code.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({ name: "demo", version: "1.0.0" });
// Tool: the model decides to call it
server.registerTool(
"add",
{
description: "Add two numbers",
inputSchema: { a: z.number(), b: z.number() },
},
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }],
})
);
// Resource: the app decides to load it
server.registerResource(
"changelog",
"docs://changelog",
{ description: "Project changelog", mimeType: "text/plain" },
async (uri) => ({
contents: [{ uri: uri.href, text: "1.0.0 - first release" }],
})
);
// Prompt: the user picks it
server.registerPrompt(
"explain",
{
description: "Explain a concept simply",
argsSchema: { topic: z.string() },
},
({ topic }) => ({
messages: [
{
role: "user",
content: { type: "text", text: "Explain " + topic + " to a beginner." },
},
],
})
);
await server.connect(new StdioServerTransport());
When a client connects, it can call tools/list, resources/list and prompts/list and will see one entry in each list.
How to choose
Ask these questions in order:
- Does it perform an action or a calculation the model should trigger when needed? Make it a tool.
- Is it data the user or app should be able to attach as context, with no action involved? Make it a resource.
- Is it a repeatable workflow a person starts on purpose? Make it a prompt.
A practical caveat
Not every client supports all three primitives equally. Many clients focus on tools first, and some show resources and prompts only in menus. If you want your server to work well everywhere, make sure the important functionality is available as a tool, even if you also publish the same data as a resource. For example, a read_schema tool and a db://schema resource can return the same content.
Common mistakes
- Exposing read-only data as a tool with a vague name, which makes the model call it too often.
- Putting side effects inside a resource read, which breaks the expectation that reading is safe.
- Writing prompts that contain secrets or internal URLs, since the client will show or send them.
Comments
No comments yet. Have a question or a tip? Be the first to share it.