C4 · lesson 5 · 10 min read
MCP primitives: tools, resources and prompts
Choose between MCP tools, resources and prompts by who controls them, and know the client features and annotations in the current spec.
Հայերեն
An MCP server can offer three kinds of things. They differ less in what they contain than in who decides to use them. That question is the fastest way to choose the right one.
Tools: the model decides
Tools are functions the model calls when it thinks they help, for example
kubectl_get_pods or pagerduty_ack_incident. Clients discover them with
tools/list and run them with tools/call. A tool definition has a name, a
description, an inputSchema and, optionally, a title, an outputSchema and
annotations.
A tool result carries content, a list of text, image, audio,
resource_link or embedded resource items. It can also include
structuredContent: JSON that matches the outputSchema, if the tool declares
one. isError: true marks a failed run (lesson 2).
Annotations are hints about behaviour:
| Hint | Meaning when true |
Default |
|---|---|---|
readOnlyHint |
The tool doesn’t modify its environment | false |
destructiveHint |
It may make destructive changes (not only additive) | true |
idempotentHint |
Repeating it with the same arguments has no extra effect | false |
openWorldHint |
It talks to an open set of external entities | true |
The defaults are pessimistic on purpose. A tool that says nothing is treated as
possibly destructive. Claude Code uses readOnlyHint and idempotentHint, for
example to run read-only calls in parallel and to retry a safe call after a dropped
connection.
Resources: the application decides
Resources are data identified by a URI, such as file:///etc/app.yaml,
runbook://payments or postgres://schema/users. The host application
decides when to load them and how much to show the model. Usually that happens
because the user attached them. Clients use:
resources/listfor fixed resources.resources/templates/listfor parameterised URIs, such asrunbook://{service}.resources/readto fetch the content.subscriptions/listento get change notifications. In 2026-07-28 this replaces the olderresources/subscribe.
In Claude Code you attach a resource with an @ mention:
@k8s-ro:runbook://payments.
Prompts: the user decides
Prompts are templates the user picks on purpose, usually from a slash menu.
prompts/list shows them with their arguments, and prompts/get renders one
into messages. Prompt arguments are simple named string values, not a JSON
Schema. In Claude Code an MCP prompt appears as a command:
/mcp__k8s-ro__triage payments.
# add to server.py from lesson 4
from pathlib import Path
RUNBOOKS = Path(__file__).parent / "runbooks" # e.g. runbooks/payments.md
@mcp.resource("runbook://{service}")
def runbook(service: str) -> str:
"""On-call runbook for one service: owners, dashboards, known failure modes."""
path = RUNBOOKS / f"{service}.md"
if not path.is_file() or path.resolve().parent != RUNBOOKS.resolve():
raise ValueError(f"No runbook for {service!r}")
return path.read_text()
@mcp.prompt()
def triage(service: str) -> str:
"""Start a read-only triage of one service."""
return (f"Triage {service}. First call kubectl_get_pods for its namespace, list pods that are "
f"not Ready or have restarts, then read runbook://{service} and suggest next steps. "
"Do not propose any change that writes to the cluster.")
Which primitive?
| You want… | Use |
|---|---|
| Claude to fetch or do something when it judges it useful | Tool |
| A user or app to attach known context (schema, runbook, config) | Resource |
| A repeatable workflow a person starts by name | Prompt |
A single server often offers all three. A database server might expose a query
tool, a schema resource and a “write a safe migration” prompt.
Client features
Servers can also ask things of the client. In 2026-07-28 the active feature is
elicitation (elicitation/create), which lets a server ask the user for input
or confirmation. It is now carried in the result as an “input required” response,
using the multi round-trip request pattern, rather than sent as a separate
server-to-client request.
Sampling, roots and logging are deprecated as of 2026-07-28. They still work during the deprecation window, but new servers shouldn’t use them:
- Instead of sampling, call an LLM API directly.
- Instead of roots, pass paths as tool arguments or server configuration.
- Instead of logging, write to stderr, or use OpenTelemetry.
Recap
- Tools are model-controlled, resources are application-controlled (URIs and templates), and prompts are user-controlled (slash commands).
- Tool results have
content, optionalstructuredContentthat matchesoutputSchema, andisError. - Annotations (
readOnlyHint,destructiveHint,idempotentHint,openWorldHint) are hints with cautious defaults. Never trust them from an untrusted server. - In Claude Code, use
@server:urifor resources and/mcp__server__promptfor prompts. - Elicitation is current. Sampling, roots and logging are deprecated in 2026-07-28.
See it in action
transcript
$ claude mcp add --transport http monitoring https://mcp.example.com/mcp Added HTTP MCP server monitoring (local config) $ claude > /mcp monitoring · connected · 3 tools > what is the p95 latency of checkout in the last hour? monitoring - query_metric(service: "checkout", stat: "p95", window: "1h") p95 latency for checkout over the last hour is 412 ms (SLO 500 ms).
$ ./try-it.sh
Try it: add a runbook resource and a triage prompt
- Add the resource and prompt from this lesson to the
k8s-roserver from lesson 4. - Restart Claude Code, type
@and pick@k8s-ro:runbook://payments. Check that the runbook text is attached to your prompt. - Type
/and run/mcp__k8s-ro__triage payments. Check that Claude useskubectl_get_podswhile following the prompt. - Mark
kubectl_get_podsas read-only withannotations=ToolAnnotations(read_only_hint=True)(frommcp.types) and look at the annotations in the MCP Inspector.
Check yourself
3 questions. Original practice questions, not taken from the real exam.