The primitives: tools, resources, prompts — and elicitation

Lesson 2 of 5 in MCP Fundamentals: One Protocol Instead of M × N Integrations.

MCP is small on purpose. A server offers three features to clients, and each one exists because a different party should be in control of it.

Ask who decides to use this and the three fall out immediately: the model decides to call a tool, the application decides which resources to pull into context, and the user decides to invoke a prompt. Same protocol, three different controllers. Getting this mapping wrong is the most common design error in first MCP servers — usually by shipping everything as a tool.

Tools — model-controlled

One concrete tool: a Sentry server exposes get_issue, with an inputSchema requiring an issue_id string. The agent decides, mid-loop, that it needs the stack trace, and calls it.

A tool definition carries a name (unique per server), an optional title and icons, a description, an inputSchema (JSON Schema — the default dialect is draft 2020-12 when no $schema is given), an optional outputSchema for structured results, and optional annotations describing behaviour.

Two things bite people here. First, name uniqueness is scoped to one server, so a host aggregating ten servers must disambiguate — commonly by prefixing the server name. Second, there are two distinct error paths: protocol errors (ordinary JSON-RPC errors, e.g. unknown tool) and tool execution errors returned inside the result with isError: true, which clients SHOULD pass to the model so it can self-correct and retry.

Results may carry unstructured content (text, image, audio, resource links, embedded resources) and/or structuredContent, a JSON value. If the tool declares an outputSchema, servers MUST return conforming structured results and clients SHOULD validate against it. Note that structuredContent is a transport concern and is unrelated to LLM structured outputs.

Resources — application-controlled

One concrete resource: a filesystem server exposes file:///home/dev/project/README.md. Nothing about it is executable. The host — or the user through the host’s UI — decides whether that text enters the context window.

Resources are addressed by URI and read with resources/read. They are how a server says here is data you may want, without claiming the authority to act. A database server can expose table schemas as resources and keep run_query as the only tool; a docs server can expose pages as resources so the host can attach them deliberately instead of hoping the model calls a search tool.

The practical test: if the model calling it without asking would be surprising, it is probably a resource, not a tool. Resources put the attachment decision in the application layer, where a human can see it.

Prompts — user-controlled

One concrete prompt: a Git server exposes a write-commit-message prompt. The user picks it from a slash-command menu; the server returns templated messages (perhaps with the staged diff embedded, perhaps with few-shot examples) that seed the conversation.

Prompts are fetched with prompts/get and are the most under-used primitive in the ecosystem. They exist because a lot of real value is not a function call at all — it is a good starting message, authored by whoever knows the domain, versioned alongside the server.

Think of a prompt as a workflow the server author packaged for the user to trigger, rather than something the model reaches for on its own.

Elicitation — the client feature

One concrete elicitation: a deployment server is asked to ship a release, needs the target environment, and instead of guessing, asks the user — a protocol-level human-in-the-loop moment.

Elicitation (elicitation/create) is the server saying I need more information from the human before I can continue. It is the client-side feature listed in the 2026-07-28 overview, and it matters because the alternative — a server inventing a plausible value — is exactly how agents cause quiet damage.

Delivery changed in 2026-07-28. Servers no longer initiate JSON-RPC requests at all; instead a server returns an InputRequiredResult (resultType: "input_required") carrying inputRequests, and the client retries the original request with inputResponses. That is the Multi Round-Trip Requests pattern (MRTR, SEP-2322), covered in the next lesson.

Interactive sorting exercise: You are designing a server for an internal analytics warehouse. Sort each capability into the primitive it belongs to. The test is always: who should decide to use this?

Interactive checkpoint quiz (2 questions) — open this page in a browser to take it.