Tools, resources, prompts — one transport, three audiences. The pattern you learned for tools just repeats.
You asked whether prompts deserve their own deep dive. Here's the honest answer, and the most useful idea in this whole course: a server exposes three kinds of capability, and they share an almost identical wire pattern. What actually distinguishes them is who decides to use them.
Every server capability is defined by who is in control of invoking it. Get this and resources and prompts cost you almost nothing on top of tools.spec
| Surface | Controlled by | Discover | Use | Returns |
|---|---|---|---|---|
| Tools | the model LLM picks one mid-reasoning |
tools/list | tools/call | content[] + structuredContent? |
| Resources | the application host loads context/data |
resources/list | resources/read | contents[] (text or blob) |
| Prompts | the user human invokes a template, e.g. a slash-command |
prompts/list | prompts/get | messages[] (role + content) |
Notice the shape: every surface is {thing}/list + a verb, all over
the same Streamable HTTP transport, all with the same pagination cursor, and each with
its own notifications/{thing}/list_changed. You already know this rhythm.
Resources are addressable data the host application chooses to feed the model — files,
records, documents. They're identified by a URI, not a name. Discovery looks just
like tools/list; reading takes a uri instead of arguments:
Resources-only powers: a client can resources/subscribe
to one URI and get pushed updated notifications when it changes — the only surface with
per-item subscription. There are also resource templates (resources/templates/list)
for parameterised URIs like file:///{path}.
Note contents is an array (one read can yield several parts), and each part
carries its own mimeType and either text or base64 blob.
A prompt is a pre-written conversation template a user deliberately invokes (the classic UI is a slash-command menu). Discovery returns its arguments — and here's the first real difference from tools:
A tool describes its inputs with a full JSON Schema (inputSchema,
with types and validation). A prompt uses a flat list of named arguments —
name / description / required, no types. Lighter, because a
prompt's job is just to fill blanks in a template, not to validate a function call.spec
Now prompts/get with arguments. The second key difference: it returns
messages — ready-to-send conversation turns, each a role + content:
The server interpolated city/state into its template and handed back a
finished user turn. The host drops those messages straight into the model's
context — the user chose to do this, which is the whole point of the control axis.
Three surfaces controlled by three different actors means three different things to gate.
"Can the model autonomously call get-env?" is a different risk question from "can this
user load that resource?" When we reach authorization, scopes and access
rules attach to these surfaces — so knowing the axis is exactly what makes OAuth scoping in MCP
make sense, not arbitrary.
Session still open (re-run bash assets/mcp-curl.sh init if needed). The
send verb posts any raw JSON-RPC on your session:
For each surface, name the verb and the return-field before you read the output:
prompts return ____, resources return ____, tools return ____.
Then try prompts/get for args-prompt with no arguments and watch
what a required argument does.
inputSchema?prompts/get hand back to the host?MCP Spec 2025-11-25 — Server / Prompts and Server / Resources. Skim both side by side and notice how deliberately they mirror the Tools page you already know.