Recap: What We Covered Last Time
In Episode 3, we built a professional MCP Server that managed a filesystem. We learned about server structure, precise input schemas, and error handling. Now it’s time to explore two more MCP capabilities: Resources and Prompts.
Why Tools Alone Aren’t Enough
So far, everything has been about Tools — functions the AI calls to perform actions. But MCP isn’t just about Tools. It has two other capabilities that are often more important.
Imagine you have a server connected to your company database. Sometimes the AI needs to see a list of customers (reading data), and other times it needs to add a new customer (performing an action). These are two fundamentally different types of interaction, and MCP treats them separately.
Resources: Exposing Data to the AI
A Resource allows the AI to read data — without performing any action. The difference from a Tool is that a Resource is read-only. A Tool executes an operation, but a Resource simply returns information.
Why does this separation matter?
- Security: Reading data is safe, but writing can be dangerous. When something is defined as a Resource, the AI knows it’s safe and can use it more freely
- Performance: The Client can cache Resources. If it needs the same data twice, it doesn’t have to fetch it from the server again
- Clarity: When the AI sees the capability list, it immediately understands which items are read-only and which are operational
Defining a Resource
Let’s build a simple Resource. Say we want our filesystem server to also expose general information about the allowed directory:
server.resource(
"workspace-info",
"workspace://info",
async (uri) => {
const stats = await fs.stat(ALLOWED_DIR);
const entries = await fs.readdir(ALLOWED_DIR);
return {
contents: [{
uri: uri.href,
mimeType: "text/plain",
text: [
`Workspace: ${ALLOWED_DIR}`,
`Total items: ${entries.length}`,
`Last modified: ${stats.mtime.toISOString()}`
].join("\n")
}]
};
}
);
Notice the differences from a Tool:
The server.resource() function: Instead of server.tool(). It takes three arguments: the resource name, a URI (unique address), and a function that returns the data.
URI: Every Resource has a unique address. Here it’s workspace://info. The Client uses this address to request data from the server. The URI format is flexible but must be unique.
No input schema: A Resource doesn’t have an input schema. Its address is fixed and the AI simply requests that address.
mimeType: Specifies the content type. Here it’s plain text, but it could be application/json or even image/png.
Resources with Templates
Resources can also be dynamic. For example, a Resource that returns any file’s content based on its path:
server.resource(
"file-content",
new ResourceTemplate("file:///{path}", { list: undefined }),
async (uri, { path: filePath }) => {
const fullPath = path.resolve(ALLOWED_DIR, filePath);
if (!fullPath.startsWith(ALLOWED_DIR)) {
throw new Error("Access denied");
}
const content = await fs.readFile(fullPath, "utf-8");
return {
contents: [{
uri: uri.href,
mimeType: "text/plain",
text: content
}]
};
}
);
Here we used ResourceTemplate. The {path} is a dynamic parameter — the AI can supply any path. For example, file:///src/index.js returns the contents of src/index.js.
Prompts: Pre-Built Interaction Templates
Prompts are MCP Server’s third capability — perhaps less well-known but incredibly powerful. A Prompt is a pre-defined template for interacting with the AI — like a ready-made recipe that the AI can follow.
Why are Prompts useful?
- Standardization: Instead of crafting a complex request every time, you have a ready-made template
- Domain expertise: You can create templates specific to your field — like “code review” or “report summary”
- Shareability: Templates can be shared across your team
Defining a Prompt
Let’s create a Prompt that reviews code:
server.prompt(
"review-code",
"Review code for bugs, style issues, and improvements",
{
filePath: {
type: "string",
description: "Path to the file to review"
},
focus: {
type: "string",
description: "What to focus on: 'bugs', 'style', 'performance', or 'all'",
enum: ["bugs", "style", "performance", "all"]
}
},
async ({ filePath, focus = "all" }) => {
const fullPath = path.resolve(ALLOWED_DIR, filePath);
const content = await fs.readFile(fullPath, "utf-8");
const focusInstructions = {
bugs: "Focus specifically on potential bugs, edge cases, and error handling issues.",
style: "Focus on code style, naming conventions, and readability.",
performance: "Focus on performance bottlenecks and optimization opportunities.",
all: "Review for bugs, style issues, and performance improvements."
};
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `Please review this code:\n\n\`\`\`\n${content}\n\`\`\`\n\n${focusInstructions[focus]}\n\nProvide specific, actionable feedback with line references where applicable.`
}
}
]
};
}
);
Here’s what’s happening:
The server.prompt() function: Similar to a tool, but instead of returning content, it returns a messages array. These messages are injected directly into the AI conversation.
Smart template: Based on the selected focus, different instructions are given to the AI. If you say “just check for bugs,” the AI focuses only on bugs.
File reading + prompting: The Prompt first reads the file content, then enters the conversation with appropriate instructions. A combination of Resource and Prompt!
Multi-Message Prompts
Prompts can contain multiple messages — even system-level messages:
server.prompt(
"explain-simply",
"Explain a technical concept in simple terms",
{
concept: {
type: "string",
description: "The technical concept to explain"
}
},
async ({ concept }) => {
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `Explain "${concept}" as if I'm a complete beginner. Use everyday analogies, avoid jargon, and keep it under 200 words. If the concept has practical applications, mention one real-world example.`
}
}
]
};
}
);
This is a simpler Prompt — it takes a concept and asks the AI to explain it simply. But notice how precise the instructions are: “Explain like I’m a beginner, use everyday analogies, avoid jargon, under 200 words.” The more precise the Prompt, the better the AI’s output.
Tools vs Resources vs Prompts — When to Use Which?
Let’s have a clear comparison. Each of these three capabilities has its place:
Tools are used when:
- The AI needs to perform an action (write, delete, send, calculate)
- The result depends on different inputs
- There may be side effects (changing files, sending messages)
- The AI decides when to call them
Resources are used when:
- The AI needs to read data (information, files, system status)
- Reading is safe and changes nothing
- The data is cacheable
- The user or application decides when to load them
Prompts are used when:
- You have a recurring interaction pattern
- You want to standardize the quality of AI output
- You need complex instructions that are tedious to repeat every time
- The user chooses which template to use
A Practical Example — All Three Together
Let’s see how a real server combines all three capabilities. Imagine an MCP Server for managing notes:
Resources:
notes://list— List all notes (read-only)notes:///{id}— Content of a specific note (read-only)
Tools:
create_note— Create a new note (write operation)update_note— Edit a note (write operation)delete_note— Delete a note (dangerous operation)search_notes— Search through notes (computational operation)
Prompts:
summarize-notes— Summarize all of today’s notesbrainstorm— Brainstorm ideas based on existing notes
See how each capability has its proper place. Reading the note list is a Resource because it’s safe and cacheable. Creating and deleting notes are Tools because they’re operations. Summarization is a Prompt because it’s a recurring interaction pattern.
Advanced Tips
Subscribing to Resources
An interesting MCP feature is that Clients can subscribe to Resources — meaning they get notified automatically when data changes. This is valuable for data that changes frequently (like system status or logs).
Composite Prompts
Prompts can use Resources and Tools internally. For example, a “weekly analysis” Prompt first reads the note list Resource, then asks the AI to analyze it with appropriate instructions.
Clear Descriptions
For all three capabilities, write clear and precise descriptions. The AI uses these descriptions to understand when and how to use each capability. A good description should say “what it does” and “when it should be used.”
What We Learned
This was the most conceptual episode so far:
- Resources: Read-only data the AI can access — safe and cacheable
- Prompts: Pre-built interaction templates — standardize AI output quality
- The three-way split: Tools for operations, Resources for reading, Prompts for templates
- URI: Every Resource has a unique address
- ResourceTemplate: For dynamic Resources with parameters
- Messages: Prompts return an array of messages