Once a client is connected to a server, the server needs a standard way to say "here's what I can do for you." MCP defines exactly three categories for that - tools, resources, and prompts - and the distinction between them isn't academic: it determines who decides when each one gets used.
Tools: actions the model decides to take
A tool is a function the server exposes that the model can choose to call - search a codebase, create a GitHub issue, run a database query, send a Slack message. Tools are model-controlled: the model looks at the conversation, decides a tool call would help, and calls it with whatever arguments it determines are appropriate. This is the primitive people usually mean when they say "the AI used a tool," and it's the one that makes MCP servers feel like they're actually doing work rather than just supplying information.
Every tool a server exposes comes with a name, a description, and a schema for its expected inputs - that description is what the model reads to decide whether a given tool is the right one for the current situation, so a well-written tool description matters as much as the code behind it.
Resources: data the application decides to attach
A resource is a piece of data the server can hand over - a file's contents, a database record, a config value, a page of documentation. The key difference from a tool: resources are application-controlled, not model-controlled. The host application decides which resources are relevant and attaches them to the conversation - often because the user explicitly selected a file, or because the host has some logic for picking relevant context - rather than the model deciding mid-conversation to go fetch one.
Think of it this way: a resource is context the app hands the model, while a tool is an action the model reaches for on its own.
Prompts: templates the user decides to invoke
A prompt in MCP terms isn't a stray instruction - it's a reusable, pre-written template the server defines for a common task, often exposed to the user as something like a slash command. A server might define a "review-pr" prompt that expands into a structured, well-tested set of instructions for reviewing a pull request, so the user doesn't have to write that instruction from scratch every time. Prompts are user-controlled: the person explicitly chooses to invoke them, rather than the model deciding on its own or the app attaching them silently.
Same server, three different control models - who decides is baked into the primitive itself, not left up to convention.
A concrete example, all three at once
Picture a GitHub MCP server in a code review session: the user attaches the current diff as a resource so it's in context; the user then invokes a prompt called "thorough-review" that expands into detailed reviewing instructions; and partway through, the model decides on its own to call the tool "get_file_contents" to pull in a file the diff references but doesn't show in full. Three primitives, three different decision-makers, one coherent session.