MCP defines resources and tools as separate primitives for a reason, even though it's tempting to just make everything a tool — resources are for exposing readable data a client can browse and pull in as context, while tools are for actions the model actively decides to invoke, and conflating the two leads to a server that's harder for clients to use well.
MCP resources are a primitive for exposing data — files, database records, API responses — that a client can list, read, and provide as context to a model, distinct from tools, which represent actions the model actively calls during a conversation. A resource is more like a file the client can attach; a tool is more like a function the model decides to invoke.
Why MCP Resources Matter (and When Tools Are the Better Fit)
Resources are well-suited to data the user or client wants to proactively include as context — a specific document, a configuration file, a particular record — where the access pattern is "the client knows what it wants and reads it directly," rather than "the model needs to search or reason about what to fetch," which is more naturally a tool's job.
Use tools instead of resources when the model needs to decide dynamically what data to fetch based on the conversation (searching for relevant records, querying based on a computed parameter) — resources are better suited to more static, addressable, browsable data than to dynamic, parameterized lookups.
Getting Started with MCP Resources
Defining a resource exposing a specific, addressable piece of data:
server.resource(
"project-readme",
"file:///project/README.md",
async (uri) => {
const content = await fs.readFile("/project/README.md", "utf-8");
return {
contents: [{ uri: uri.href, mimeType: "text/markdown", text: content }],
};
}
);
A resource template, for a parameterized but still browsable set of resources:
server.resource(
"project-file",
new ResourceTemplate("file:///project/{path}", { list: undefined }),
async (uri, { path }) => {
const content = await fs.readFile(`/project/${path}`, "utf-8");
return { contents: [{ uri: uri.href, text: content }] };
}
);
Core MCP Resources Concepts Every Developer Should Know
Resources are identified by URIs, giving them a stable, addressable identity distinct from a tool call's transient parameters — a resource URI can be listed, referenced, and read repeatedly, which fits use cases like "here's a specific document" better than a tool invocation's more ephemeral request/response shape.
The client controls when resources are read, often based on explicit user action (attaching a file, browsing a resource list) — this is a meaningful difference from tools, which the model decides to invoke autonomously based on its own reasoning about the conversation. Resources put more of the "what to include" decision in the client/user's hands.
Resource lists let clients discover what's available without needing to know exact URIs in advance, via a list capability that returns available resources — useful for building a browsable interface (a file picker, a document list) on top of an MCP server's exposed data.
Subscriptions let a client be notified when a resource changes, useful for resources backed by data that updates over time (a live document, a changing configuration) — without subscriptions, a client would need to poll or simply not know when previously-read resource content is stale.
Common Mistakes Using MCP Resources and How to Fix Them
Mistake 1: implementing dynamic, parameterized data lookups as resources when they'd fit better as tools. A resource template can technically accept parameters, but if the actual need is "the model should decide what to search for based on reasoning," a tool is the better-fitting primitive. Fix: use tools for model-driven dynamic lookups, resources for more static, addressable, client-browsable data.
Mistake 2: not implementing the list capability, leaving clients unable to discover available resources without already knowing exact URIs. Fix: implement resource listing for any resource set a client should be able to browse rather than needing prior knowledge of specific URIs.
Mistake 3: exposing sensitive data as a broadly listable resource without access control, assuming resources are inherently lower-risk than tools since they're "just reading." Fix: apply the same authorization considerations to resource access as to tool calls — reading sensitive data through a resource is just as consequential as through a tool.
When Should You Use Resources Instead of Tools for Exposing Data?
Use resources when the access pattern is client/user-driven browsing or attaching of relatively static, addressable data — documents, files, specific records a user wants to reference. Use tools when the model needs to dynamically decide what to fetch based on conversational context, search parameters, or reasoning about what's relevant — the more common pattern for data access driven by the model's own judgment rather than explicit user selection.
MCP Resources in Production
Apply the same authorization rigor to resource access that you would to tool calls, since exposing sensitive data through a resource carries the same real risk as through a tool, despite feeling more passive. Also implement resource listing thoughtfully for any resource set meant to be browsable, since discoverability is much of a resource's practical value over just using a tool with a URI parameter.
If your MCP server currently implements everything as tools, including static document or file access, resources are worth evaluating specifically for that subset — they better match the client-driven browsing pattern those use cases usually have.