Module 3: Tools, Resources, and Prompts
Duration: ~25 minutes | Level: Beginner | Prerequisites: Module 2: Anatomy of MCP
Three Primitives, One Philosophy
An MCP server exposes three types of capabilities: Tools, Resources, and Prompts. This isn't minimalism for its own sake. Each primitive encodes a distinct contract about how the AI should interact with the capability, who controls execution, and what side effects are allowed.
Understanding the philosophy behind each primitive helps you make the right design decision when building your server. The wrong choice doesn't just violate the spec; it produces worse AI behaviour.
MCP is symmetrical. Clients also expose three primitives back to servers: Roots (which URI scopes the server may operate on), Sampling (the server can ask the client to run a prompt through the user's chosen model), and Elicitation (the server can ask the user a structured question mid-tool-call). They're covered in Module 6: Capability Negotiation, and there's a full blog post on why that half gets so much less attention: Six Primitives, Not Three. For now we focus on the server side, because that's the side you'll build first.
Tools: The AI Acts
A Tool is a function the AI can invoke. When the AI calls a tool, something happens in the real world: a database row is created, an API is hit, a file is written, an email is sent.
The defining characteristic
Tools are model-controlled. The AI decides when to call a tool, which tool to call, and what arguments to pass. This is fundamentally different from resources; the human doesn't have to explicitly invoke a tool. The model reasons about it autonomously.
This also means tools are the mechanism for agentic behaviour: an AI that can take actions, not just answer questions.
What a tool definition looks like
Every tool has three parts:
- name: a unique identifier (the spec allows letters, digits, underscore, hyphen, and dot; snake_case is a common convention, not a spec rule)
- description: a natural-language explanation for the model; this is what the model reads to decide when to use the tool
- inputSchema: a JSON Schema defining the parameters
{
"name": "search_products",
"description": "Search the product catalogue by name, category, or price range. Use this when the user asks about available products or wants to find items matching specific criteria.",
"inputSchema": {
"type": "object",
"properties": {
"query": { "type": "string", "description": "Search terms" },
"max_price": { "type": "number", "description": "Maximum price in USD" },
"category": { "type": "string", "enum": ["electronics", "clothing", "books"] }
},
"required": ["query"]
}
}
Good tool descriptions
The description is arguably the most important part of a tool. The model reads it to understand when the tool is appropriate. A vague description leads to missed calls or inappropriate calls.
Bad: "search_products", searches products
Good: "search_products", Search the product catalogue by name, category, or price range. Use this when the user asks about available products or wants to find items matching specific criteria. Do not use for inventory or order status queries."
Tool results
When a tool is called, it returns content. MCP supports several content types (full list as of spec 2025-06-18 and later):
- Text: the most common; JSON strings, human-readable output (since 2024-11-05)
- Image: base64-encoded binary with a MIME type (since 2024-11-05)
- EmbeddedResource: a full resource (text or blob) embedded directly in the result (since 2024-11-05)
- Audio: base64-encoded audio with a MIME type (added in 2025-03-26)
- ResourceLink: a reference to a resource by URI (lets the AI follow up with
resources/readwithout the server having to inline the bytes), added in 2025-06-18
Tools can also indicate success or failure. A tool call that fails returns the error information as content with isError: true; the model can reason about the failure and decide how to proceed.
Resources: The AI Reads
A Resource is a piece of data the AI can access. Files, database records, API responses, live metrics, documentation pages, anything with a stable identifier and readable content.
The defining characteristic
Resources are application-controlled (or can be). The human or the host decides which resources are attached to a conversation. The AI can read a resource, but typically doesn't choose to fetch arbitrary resources the way it chooses to call tools.
This matters: resources are the safe, non-destructive side of MCP. Reading a resource has no side effects. You can expose your entire database as a set of resources without worrying about the AI accidentally modifying data; reading doesn't write.
Resource addressing
Every resource has a URI, a stable identifier that follows a scheme:
file:///home/user/project/README.md
postgres://mydb/public/users/42
github://repos/myorg/myrepo/issues/123
metrics://prometheus/cpu_usage?window=1h
MCP standardises a few common schemes (https://, file://, git://) and lets servers define custom schemes for everything else; custom schemes must be valid RFC 3986 URIs. The convention is to use something meaningful and stable.
Resource content types
Resources can contain:
- Text (MIME type
text/plain,text/markdown,application/json, etc.) - Blob (binary data as base64, e.g.
image/png,application/pdf)
A resource can also be referenced (not inlined) via a ResourceLink, which carries the resource's URI and name, plus an optional MIME type and short description. Tools that produce many or large resources typically return ResourceLinks; the client decides whether to read them.
Resource discovery
Clients can list available resources with resources/list. How change notifications work depends on which revision you target, and this is one of the mechanisms 2026-07-28 rewrote.
As of 2026-07-28 there is no resources/subscribe RPC. A client opens a long-lived subscriptions/listen stream and opts in to exactly the notification types it wants: resourcesListChanged for notifications/resources/list_changed, and resourceSubscriptions (an array of resource URIs) for notifications/resources/updated on those specific resources. The server MUST NOT send a notification type the client did not ask for. It acknowledges with notifications/subscriptions/acknowledged and tags every notification with io.modelcontextprotocol/subscriptionId, so a client running several subscriptions over one transport can tell them apart. The result is the same live-updating context, scoped to a request rather than to a connection.
Up to 2025-11-25 a client subscribed with resources/subscribe, cancelled with resources/unsubscribe, and on HTTP the server pushed notifications down a separate GET/SSE channel. 2026-07-28 removed all three (SEP-2575). The reason is statelessness: a per-connection subscription registry is connection state, and this revision's whole thrust is removing that. A subscription now belongs to a request, the long-lived subscriptions/listen request, rather than to the connection underneath it. The Java MCP SDK 2.0.0 predates the revision and still implements the 2025-11-25 subscribe/unsubscribe shape.
// resources/list response
{
"resources": [
{
"uri": "file:///project/README.md",
"name": "README.md",
"description": "Project overview and setup instructions",
"mimeType": "text/markdown"
}
]
}
Prompts: Reusable AI Workflows
A Prompt is a parameterised instruction template that surfaces in the client UI as something the user can invoke. Think of them as slash commands with arguments, structured, repeatable workflows.
The defining characteristic
Prompts are user-controlled. A user explicitly invokes a prompt from the host application's UI (typically a dropdown or slash command). The prompt returns a pre-built conversation structure: a sequence of user and assistant messages carrying the instructions and context, all formatted to produce high-quality AI output for a specific task. (MCP prompt messages have no system role; every message is user or assistant, and the host decides how to present them to the model.)
Why prompts exist
Without prompts, every user writes the same "review my code carefully and check for security issues, edge cases, and…" instruction from scratch, in slightly different ways, getting slightly different quality results. A prompt server standardises that instruction across your team.
// prompts/list response
{
"prompts": [
{
"name": "code_review",
"description": "Perform a thorough code review focusing on security, correctness, and style",
"arguments": [
{ "name": "language", "description": "Programming language", "required": false },
{ "name": "focus", "description": "Optional specific area: security | performance | style", "required": false }
]
}
]
}
When invoked (prompts/get), the server returns a GetPromptResult with an array of messages, the fully-assembled conversation starter:
{
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "Please review the following code for security vulnerabilities, correctness issues, and style problems. Be specific about line numbers when possible.\n\n[code here]"
}
}
]
}
The Decision Matrix: Which Primitive Do I Use?
| Question | Answer | Use |
|---|---|---|
| Does the AI need to do something with a side effect? | Yes | Tool |
| Does the AI need to read data without side effects? | Yes | Resource |
| Do you want users to invoke a pre-built, reusable workflow? | Yes | Prompt |
| Should the AI autonomously decide when to use this? | Yes | Tool |
| Is this a stable, addressable piece of data? | Yes | Resource |
| Is this a structured, parameterised instruction template? | Yes | Prompt |
Common misclassifications
"I'll make a get_user tool", this one can go either way. If the application or user selects which user's data to attach as context, expose it as a Resource (users://42). But if the model must decide at runtime which user to look up, a read-only Tool is correct: Resources are application-controlled, and most hosts do not let the model fetch them autonomously. Mark such tools with the readOnlyHint annotation so clients know they have no side effects.
"I'll make a search resource", Search is dynamic and model-controlled. It should be a Tool: the model decides when to search and what to search for.
"I'll make a write_code tool", Could be either, depending on context. If the AI autonomously calls it as part of reasoning, Tool. If users explicitly invoke a code-writing workflow from a menu, Prompt.
All Three Together: A Full Example
Imagine an MCP server for a project management system:
Tools (things the AI can do):
create_task(title, description, assignee, due_date)update_task_status(task_id, status)post_comment(task_id, comment)
Resources (things the AI can read):
tasks://project-123, all tasks in a projecttasks://task-456, a specific task with full detailsmembers://team-789, team member list
Prompts (workflows users invoke):
daily_standup, generates a standup summary from recent task activitysprint_review, creates a sprint retrospective based on completed tasks
With these three sets of capabilities, an AI can: understand your project's current state (via Resources), take action when instructed (via Tools), and produce structured team deliverables (via Prompts).
Key Takeaways
- Tools = the AI acts; model-controlled; side effects allowed
- Resources = the AI reads; application/user-controlled; no side effects
- Prompts = users invoke; pre-built workflow templates with parameters
- Prefer Resources for browsable, user-selected context; use a read-only Tool (with
readOnlyHint) when the model must choose what to fetch at runtime - The distinction encodes safety and control semantics, not just API style
In the next module, we look at how all three primitives are communicated over the wire: the JSON-RPC protocol layer.