Agent Protocols Explained
← /learn · 03

Tools, resources and prompts

The three things a server offers, told apart by who decides to use them: the model, the application or the user. The same document reaches the model all three ways.

Loading the animation…

Concept

A server offers three kinds of thing, and the specification tells them apart by who decides to use them (server overview, accessed 2026-10-08):

PrimitiveControlled byWhat it isExample
Toolsthe modelfunctions the model may call to act or look things uprun a query, write a file
Resourcesthe applicationdata the host attaches to the contexta file's contents, a git log
Promptsthe usertemplates the user picks, often as slash commands"/review this code"

The animation gets the same README in front of the model three ways in one session. As a tool: the host lists the tools and gives their definitions to the model, which decides to call read_doc; the result comes back as a tool result. As a resource: the host lists the resources and reads docs://readme itself, because the application (or the user through it) chose to attach it; the model never asked. As a prompt: the user picks the review prompt and fills in its argument; the server returns ready-made messages that start the conversation.

Each path is a list and then a get, four messages. They cost very different amounts. The tool path is the biggest, 2,424 bytes here, because tools/list returns every tool's JSON Schema, and those definitions also go into the model's prompt on every turn. The resource path costs 417 bytes and the prompt path 499 bytes. What reaches the model is small in all three: 7 tokens of README by the tool or the resource, 10 wrapped in the prompt's template (Qwen2.5 tokenizer). In 2026-07-28 every message carries its envelope, so the same three paths cost 3,086 bytes, 1,112 bytes and 1,161 bytes.

Which to use is a question of control. A tool hands the decision to the model, which is right for actions and for lookups the model must plan; it also exposes the tool to whatever text the model reads, which is why tool permissions exist. A resource keeps the decision with the application: nothing reaches the context unless the host puts it there. A prompt keeps it with the user.

Maths

For a path pp (its list and its get), the bytes on the wire are Bp=∑m∈p∣m∣B_p = \sum_{m \in p} |m| over its messages mm. What matters for the model is different: the tokens TT that land in its context. A tool adds its definition dd to every model call, so over nn turns the tool path costs

Ttool≈n T(d)+T(result),T_{\text{tool}} \approx n\,T(d) + T(\text{result}),

while a resource or a prompt adds only its content, once: Tresource=T(content)T_{\text{resource}} = T(\text{content}). That is the trade the specification's control column hides: a model-controlled primitive is also a per-turn cost.

Code

The three gets on the server side, cut from the vendored engine. Listing is the same code for all three primitives (with pagination when the server pages):

if (method === "tools/list") return [response(id, this.list("tools", params))];
if (method === "resources/list") return [response(id, this.list("resources", params))];
if (method === "prompts/list") return [response(id, this.list("prompts", params))];
if (method === "resources/read") {
  const uri = params.uri;
  if (!(uri in this.spec.texts)) {
    return [error(id, ERRORS.invalid_params!, "Unknown resource: " + String(uri), { uri }, true)];
  }