Introduction

Resources are one of the three core primitives in MCP, alongside tools and prompts. While tools let the LLM take actions, resources provide read-only data that enriches the LLM's context. This lesson covers what resources are, how they are identified, and how clients discover and read them.

Key Concepts

  • Resource: A read-only data source exposed by an MCP server. Resources provide context to the LLM without performing side effects.
  • Resource URI: A unique identifier for a resource, following the URI format (e.g., file:///project/README.md, postgres://localhost/mydb/users).
  • MIME type: Indicates the format of the resource content. Text resources use types like text/plain or text/markdown. Binary resources are base64-encoded.
  • resources/list: The JSON-RPC method a client sends to discover all available resources on a server.
  • resources/read: The JSON-RPC method a client sends to retrieve the content of a specific resource.

Real World Context

Imagine you are building an MCP server for a project management tool. You might expose the project README as a resource so the LLM can understand the project context, a database table schema so the LLM knows what queries are valid, and a configuration file so the LLM can reference current settings. None of these require the LLM to take action — they just provide information that makes the LLM's responses more accurate and relevant.

Deep Dive

What Resources Are

Resources represent data that an MCP server wants to make available to clients and LLMs. Unlike tools, which perform actions, resources are strictly read-only. They are designed to be included in the LLM's context window to improve the quality of responses.

Examples of resources include:

  • Files on disk (file:///path/to/file)
  • Database records (postgres://host/db/table)
  • API responses cached by the server
  • Configuration data
  • Live system metrics

Resource URIs and Naming

Every resource has a unique URI that identifies it. The URI scheme can be any valid scheme, and servers often define custom schemes:

text
file:///project/README.md        # Local file
postgres://localhost/mydb/users  # Database table
custom://config/settings         # Custom scheme
git://repo/main/src/index.ts     # Git repository file

The URI serves as both the identifier and a hint about the resource's origin. Clients use the exact URI when requesting a resource.

Discovering Resources

Clients discover available resources by sending a resources/list request. The server responds with a list of resources, each containing a URI, name, optional title (a human-readable display name), optional description, and MIME type:

json
{"resources":[{"uri":"file:///project/README.md","name":"README","title":"Project README","mimeType":"text/markdown"},{"uri":"postgres://localhost/mydb/users","name":"Users Table","title":"Users Database Table","mimeType":"application/json"}]}

This response shows two resources: a Markdown file and a JSON representation of a database table. The client can present these to the user or the LLM for selection.

Reading Resources

Once the client knows which resource it wants, it sends a resources/read request with the URI:

json
{"jsonrpc":"2.0","id":3,"method":"resources/read","params":{"uri":"file:///project/README.md"}}

The server responds with the resource content. For text resources, the content is returned directly as a string. For binary resources, the content is base64-encoded.

Static vs Dynamic Resource Lists

Some servers expose a fixed set of resources (static), while others generate the list dynamically based on current state:

  • Static: A documentation server that exposes a known set of Markdown files. The list does not change unless the server is restarted.
  • Dynamic: A database server that lists available tables. New tables appear in the list as they are created.

Dynamic resource lists can notify clients of changes using notifications/resources/list_changed, which we cover in a later lesson.

MIME Types: Text and Binary

Resources declare their content format using MIME types:

  • Text resources (text/plain, text/markdown, application/json): Content is returned as a UTF-8 string in the text field.
  • Binary resources (image/png, application/pdf, application/octet-stream): Content is returned as a base64-encoded string in the blob field.

Clients must check which field is present to determine how to handle the content.

Common Pitfalls

  1. Treating resources as tools — Resources are read-only and should not have side effects. If your resource modifies data when read, it should be a tool instead.
  2. Using overly generic URIs — URIs like data://info do not help clients or LLMs understand what the resource contains. Use descriptive schemes and paths like postgres://localhost/mydb/users.
  3. Forgetting MIME types — Without a MIME type, clients cannot determine how to display or process the content. Always specify the MIME type for every resource.

Best Practices

  1. Use meaningful URI schemes — Choose URI schemes that indicate the data source: file://, postgres://, github://, or a custom scheme relevant to your domain.
  2. Include descriptions — The description field in the resource list helps both human users and LLMs understand what each resource contains without reading it.
  3. Keep resource content focused — A resource should represent a single logical unit of data. Avoid cramming an entire database into one resource — expose individual tables or views instead.

Summary

  • Resources are read-only data sources that provide context to LLMs without side effects.
  • Each resource has a unique URI, a human-readable name, an optional title for display, and a MIME type.
  • Clients discover resources with resources/list and read them with resources/read.
  • Text content is returned as strings; binary content is base64-encoded.
  • Resource lists can be static (fixed) or dynamic (changing at runtime).

Code Examples

typescript
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";

const server = new McpServer({
  name: "project-server",
  version: "1.0.0"
});

// Expose a static file resource
server.resource(
  "readme",
  "file:///project/README.md",
  { mimeType: "text/markdown", description: "Project README" },
  async (uri) => {
    const content = await fs.readFile("/project/README.md", "utf-8");
    return { contents: [{ uri: uri.href, mimeType: "text/markdown", text: content }] };
  }
);
✓ Completed