The Three Primitives: Tools, Resources, and Prompts
- Distinguish the three MCP primitives — tools, resources, and prompts — and explain which actor controls each one
- Describe when to use a tool versus a resource versus a prompt when designing an MCP server
- Identify the protocol operations (tools/list, tools/call, resources/read, prompts/get) that clients use to interact with each primitive
Three Primitives, Three Controllers
Every capability an MCP server exposes falls into one of three categories. The key to understanding them is not just what they do, but who controls when they are used:
This distinction drives design decisions. If you want the AI to decide when to call something, it should be a tool. If you want data pre-loaded into the AI's context, it should be a resource. If you want the user to trigger a specific workflow, it should be a prompt.
Tools: Model-Controlled Actions
Tools are the most commonly used primitive. A tool is a function with a name, a description, an input schema, and a handler that executes when called.
The AI model reads the tool's name and description to understand what it does, and reads the input schema to know what arguments to pass. When the model decides the tool is relevant to the current conversation, it calls the tool — subject to user consent in the host application.
Tools can do anything a function can do: make HTTP requests, query databases, read files, perform calculations, call third-party APIs. The return value is a content array — one or more content items of type text, image, or resource.
Protocol operations for tools:
tools/list— client requests the full list of available toolstools/call— client asks the server to execute a specific tool with given arguments
Example tool definition in TypeScript (verified from official SDK):
server.registerTool("get_weather", {
description: "Get current weather for a city",
inputSchema: {
city: z.string().describe("City name"),
units: z.enum(["celsius", "fahrenheit"]).optional()
},
}, async ({ city, units = "celsius" }) => {
const data = await fetchWeather(city, units);
return {
content: [{ type: "text", text: `${city}: ${data.temp}°, ${data.condition}` }]
};
});
Resources: Application-Controlled Data
Resources represent data the host application can read and inject into context. Unlike tools — which are called dynamically by the AI during a conversation — resources are more like files or API endpoints: they have a URI, a MIME type, and content that can be fetched on demand.
Resources come in two forms:
- Direct resources — a fixed URI pointing to a specific piece of data. Example:
file:///logs/app.logordb://users/schema. - URI templates — parameterized URIs that represent a family of resources. Example:
db://users/{id}where{id}is filled in at read time.
Resources are read-only. Your server exposes them; clients fetch them. You cannot use a resource to execute an action or modify data — that is what tools are for.
Protocol operations for resources:
resources/list— client requests the list of available resourcesresources/read— client fetches a specific resource by URIresources/subscribe— client subscribes to notifications when a resource changes (optional capability)
Example resource definition in TypeScript:
server.registerResource(
"app-config",
"config://app",
{
title: "Application Configuration",
description: "Current application configuration values",
mimeType: "application/json"
},
async (uri) => ({
contents: [{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(await loadConfig(), null, 2)
}]
})
);
Prompts: User-Controlled Templates
Prompts are reusable instruction templates that users invoke explicitly. Think of them as slash commands: the user types /review-pr or selects "Review PR" from a menu, and the prompt template expands into a structured set of instructions for the AI, optionally with arguments the user fills in.
A prompt has a name, a description, optional arguments (with descriptions), and a handler that returns a list of messages — the pre-filled conversation the AI should use as its starting context.
Protocol operations for prompts:
prompts/list— client requests the list of available promptsprompts/get— client retrieves a specific prompt, passing any required arguments; server returns the message list
Example prompt definition in TypeScript:
server.registerPrompt("review-pr", {
description: "Review a pull request for correctness and style",
arguments: [
{ name: "pr_number", description: "Pull request number", required: true },
{ name: "focus", description: "Specific area to focus on (optional)", required: false }
]
}, async ({ pr_number, focus }) => ({
messages: [{
role: "user",
content: {
type: "text",
text: `Please review PR #${pr_number}. ${focus ? `Focus particularly on: ${focus}.` : ""} Check for correctness, edge cases, and style consistency.`
}
}]
}));
Choosing the Right Primitive
When designing your server, the key question for each capability is: who should trigger this?
- If the AI should decide when to use it based on conversation context → Tool
- If the host application should load it as background context → Resource
- If the user should explicitly invoke it as a starting point → Prompt
Most MCP servers focus primarily on tools, because tools are the most flexible primitive. Resources are most useful when you have large, stable data (schemas, configurations, documentation) that benefits from being loaded into context upfront. Prompts are most useful when you want to give users reproducible, parameterized workflows — standardized code review checklists, consistent debugging runbooks, guided onboarding flows.
A server is not required to implement all three. Start with the primitive that best fits your use case, and add others as you identify a need.
- Tools are model-controlled: the AI decides when to call them based on context — use tools for actions, API calls, calculations, and data retrieval
- Resources are application-controlled: read-only data identified by URI that the host injects into context — use resources for schemas, configs, and stable reference data
- Prompts are user-controlled: explicitly invoked templates that expand into structured instructions — use prompts for reproducible workflows and slash-command-style interactions
- Each primitive has its own protocol operations: tools use tools/list and tools/call; resources use resources/list and resources/read; prompts use prompts/list and prompts/get
- Most servers start with tools only — resources and prompts add value when you have stable background data or repeatable user workflows to expose