BytesAI
MCP

MCP Resources: Giving the Model Read Access to Your Data

Tools get all the attention in MCP tutorials, but Resources are where you give the model read access to live data — files, databases, configs — without making it call a tool every time.

Abid Zaidi3 min read
#mcp#typescript#resources#ai-tools#tutorial
MCP Resources: Giving the Model Read Access to Your Data

If you have read the first MCP walkthrough, you know how tools work — the model calls a function, gets a result, and uses it to answer. Resources are the other half of the protocol, and they work differently in ways that matter.

A Resource is data the client exposes for the model to read, rather than a function the model invokes. Think of it as the model pulling context rather than pushing a request. Files, database records, configuration state, live metrics — anything you want available in context without a round-trip tool call per item.

Tools vs Resources: the practical difference

The distinction is more than architectural:

ToolsResources
Who decides when to use itThe modelThe client / user
Interaction styleRequest → responseSubscribe / read
Best forActions, fetching specific dataBackground context, browsable data
OverheadOne call per piece of dataLoaded into context up front

Tools are right when the model needs to decide what to fetch based on the conversation. Resources are right when data should simply be available — the model should know the current project config without needing to call get_project_config on every message.

Registering a Resource

The SDK's resource API mirrors the tool API closely:

typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import * as fs from "fs";
 
const server = new McpServer({ name: "file-server", version: "1.0.0" });
 
// Static resource — content known at registration time
server.registerResource(
  "readme",
  "file:///project/README.md",
  {
    title: "Project README",
    description: "The project README. Read this to understand the codebase.",
    mimeType: "text/markdown",
  },
  async () => ({
    contents: [
      {
        uri: "file:///project/README.md",
        mimeType: "text/markdown",
        text: fs.readFileSync("./README.md", "utf-8"),
      },
    ],
  }),
);
 
const transport = new StdioServerTransport();
await server.connect(transport);

The handler is called when the client reads the resource. The content is fetched fresh each time, so a resource backed by a file or a database query always returns the current state.

Dynamic resources with templates

Real use cases often need parameterized resources — not "the README" but "the logs for deployment X." MCP supports URI templates for this:

typescript
server.registerResourceTemplate(
  "deployment-logs",
  "logs://{deploymentId}",
  {
    title: "Deployment logs",
    description: "CloudWatch logs for a given deployment ID.",
  },
  async ({ deploymentId }) => {
    const logs = await fetchLogsFromCloudWatch(deploymentId);
    return {
      contents: [
        {
          uri: `logs://${deploymentId}`,
          mimeType: "text/plain",
          text: logs.join("\n"),
        },
      ],
    };
  },
);

The client can now request logs://deploy-abc123 and get the logs for that specific deployment. The model sees a list of available templates and constructs URIs to request what it needs.

Resource change notifications

For data that updates while a session is running — a live dashboard, a build status, a queue depth — the server can push change notifications:

typescript
// Tell the client that a resource's content has changed
server.notifyResourceUpdated("file:///project/README.md");

Clients that subscribe to the resource will re-fetch it when they receive this notification. This is how you keep the model's context current without polling.

What to expose as a resource vs a tool

The heuristic that has worked for me:

  • If the model should have it available as background reading — config files, project docs, environment state — use a Resource.
  • If the model needs to decide whether and when to fetch it — database queries, API calls, actions — use a Tool.
  • If it is large and the model will only sometimes need it, use a Tool so it is only fetched when needed.

Resources shine for the context a developer would paste into a prompt manually: the relevant config, the recent logs, the current schema. Once it is a Resource, the model always has it without anyone doing the pasting.

Building Your First MCP Server: A Practical Walkthrough
MCP
A hands-on guide to writing a Model Context Protocol server in TypeScript — exposing tools an AI assistant can actually call, and wiring it into Claude and other MCP clients.
4 min read
Adding Authentication to Your MCP Server
MCP
Exposing an MCP server beyond localhost means thinking about who can call it. This walkthrough adds API key authentication to a TypeScript MCP server without over-engineering it.
4 min read
Getting More From ChatGPT With Projects
ChatGPT
ChatGPT Projects changed how I use the tool daily — persistent instructions, shared context across conversations, and a much cleaner way to separate different kinds of work.
4 min read