Resources

By the Syncfusion DevTutorials team · Last updated

The MCP resource model: URIs, names and MIME types, text and base64 blob contents, resource templates, common kinds of resources, and why reads must be read-only and deterministic.

Think about a library. You can walk in, take a book off the shelf, and read it. Reading doesn't change the book. The next person who reads it sees the same words. Each book also has an address: a shelf number that tells you exactly where it is.

MCP resources are the library's books. They are data an app can read, each at its own address, called a URI. Reading a resource never changes anything.

The main idea: a resource is read-only data with a URI. The app decides which resources to read and put into the model's context.

A Real-World Example

Brightgrid's support assistant often answers questions about returns and shipping.

  • At first, the team made a tool called get_policy. The model called it on almost every message, "just in case". That was slow and cost tokens.
  • They turned the policies into resources: kb://returns-policy and kb://shipping.
  • Now the host decides: when the question mentions returns, it reads kb://returns-policy and adds it to the context. The model doesn't have to ask.

Order records became resources too, through one template: orders://{order_id}.

In simple words: tools are for doing. Resources are for reading. The app chooses what to read.

What You Will Learn

In this lesson, you will learn:

  1. The resource model: URI, name, title, description and MIME type.
  2. How text and binary resources are returned.
  3. How resource templates give one URI pattern for many resources.
  4. Common kinds of resources: static files, records, logs and schemas.
  5. The rules resources must follow: read-only, no side effects, the same answer every time.

The MCP Lab Explorer now lists Resource templates too. Pick one, fill in the URI, and press Read. Images that a server returns are shown as pictures.

Now that you know why resources matter, let's look at how a server describes them. Before a host can read a resource, it needs to know what resources are available and what kind of data they contain.

The Resource Model

The host discovers available resources through resources/list. Along with each resource's URI, the server can provide additional information that helps the host and users understand what the resource contains.

What a Resource Has

resources/list returns a list of resources.

FieldMeaning
uriIts address, like kb://shipping. Unique and stable
nameA short name
titleA friendly label for people (optional)
descriptionWhat it holds, for the host and the model (optional)
mimeTypeWhat kind of data it is, like text/plain or image/png (optional)

List resources by kind

// The resources/list result from the lab server (trimmed).
const resources = [
  {
    uri: "kb://returns-policy",
    name: "returns-policy",
    title: "Returns policy",
    mimeType: "text/plain",
  },
  {
    uri: "kb://shipping",
    name: "shipping",
    title: "Shipping times",
    mimeType: "text/plain",
  },
  {
    uri: "assets://logo.png",
    name: "logo",
    title: "Brightgrid logo",
    mimeType: "image/png",
  },
  { uri: "orders://A-1001", name: "Order A-1001", mimeType: "application/json" },
];

for (const r of resources) {
  const kind = r.mimeType.startsWith("text/")
    ? "text"
    : r.mimeType.startsWith("image/")
      ? "image"
      : "data";
  console.log(r.uri.padEnd(22), kind.padEnd(6), r.title ?? r.name);
}

Expected output:

kb://returns-policy    text   Returns policy
kb://shipping          text   Shipping times
assets://logo.png      image  Brightgrid logo
orders://A-1001        data   Order A-1001

Application-Driven

Resources are application-driven. The host decides which ones to read and when:

  • It may show a list and let the user attach one, like attaching a file to an email.
  • It may pick some automatically, like the policy that matches the question.
  • It may read one because a tool result pointed to it.

Knowing that a resource exists is only the first step. To use the data it contains, the host reads the resource using its URI.

Reading a Resource

resources/read takes a URI and returns contents, a list of items. Each item has its uri, its mimeType, and the data:

  • text for text, like a policy or a JSON record.
  • blob for bytes, like an image or a PDF, written as base64. Base64 turns any bytes into plain letters, digits, + and /, so they can travel inside JSON.

Text and blob contents

// resources/read returns "contents". Text comes as text. Bytes come as base64 in "blob".
const contents = [
  {
    uri: "kb://shipping",
    mimeType: "text/plain",
    text: "Standard shipping takes 3 to 6 days.",
  },
  {
    uri: "assets://logo.png",
    mimeType: "image/png",
    blob: "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMElEQVR42mPkndLxn4ECwMRAIRh4A1jQBT5ll+PVwDe1c7iFwagBWNIBejyPgDAAAO2SBnj02loiAAAAAElFTkSuQmCC",
  },
];

for (const c of contents) {
  if ("text" in c) {
    console.log(c.uri, "-> text,", c.text.length, "characters");
  } else {
    const bytes = atob(c.blob); // base64 text -> raw bytes
    console.log(
      c.uri,
      "-> blob,",
      c.blob.length,
      "base64 characters =",
      bytes.length,
      "bytes, starts with",
      bytes.slice(1, 4),
    );
  }
}

Expected output:

kb://shipping -> text, 36 characters
assets://logo.png -> blob, 140 base64 characters = 105 bytes, starts with PNG

The lab reads the logo's base64 blob and shows it as a small picture.

Listing every resource works when there are only a few items. However, some data sets contain thousands or even millions of records. Resource templates solve this problem by describing many resources with a single URI pattern.

Resource Templates

One Pattern, Many Resources

One template, orders://{order_id}, stands for one resource per order. The client fills in the placeholder and reads the URI.

Some data has too many items to list one by one, like every order. A resource template describes them all with one pattern: orders://{order_id}. The part in braces is a placeholder. The client fills it in to make a real URI, like orders://A-1002.

  • resources/templates/list returns the templates, each with a uriTemplate.
  • resources/read takes the filled-in URI.
  • The server matches the URI to the template and reads the right record.

Fill and match a template

// A resource template: one pattern, many resources.
const template = "orders://{order_id}";

function expand(t, values) {
  return t.replace(/\{(\w+)\}/g, (_, name) => encodeURIComponent(values[name]));
}

function match(t, uri) {
  const names = [];
  const pattern = t.replace(/\{(\w+)\}/g, (_, name) => {
    names.push(name);
    return "([^/]+)";
  });
  const m = uri.match(new RegExp("^" + pattern + "$"));
  return m
    ? Object.fromEntries(names.map((n, i) => [n, decodeURIComponent(m[i + 1])]))
    : null;
}

console.log(expand(template, { order_id: "A-1002" }));
console.log(JSON.stringify(match(template, "orders://A-1003")));
console.log(JSON.stringify(match(template, "tickets://T-501")));

Expected output:

orders://A-1002
{"order_id":"A-1003"}
null

Now try a missing order. In the Explorer, change the URI to orders://A-9999 and press Read. The server answers with error -32602, "Resource not found".

In the SDK, a template is a ResourceTemplate. It can also list the items it knows and suggest placeholder values while the user types, through completion/complete.

Templates follow a standard called URI templates (RFC 6570). The simple form is all most servers need. You will meet query parameters and other schemes in the Resource URIs lesson.

Resources can hold many kinds of information. Recognizing common types helps you choose a clear URI and MIME type for the data the host needs.

Kinds of Resources

Static Files

Files that do not change often: a README, a policy, or a style guide. Their URIs are often file:// paths or a custom scheme like kb://.

Records

One row or document from a database, often through a template: orders://{order_id}, customers://{id}.

Logs

Recent log lines from a system, like logs://api/today. They change over time, but each read still only reads. The server should say clearly what time range a read covers.

Schemas

The shape of a database or an API: table names, columns, and types. A model that writes queries needs this, and it is a perfect resource: read-only and rarely changed.

Kinds of resources and their MIME types

// Common kinds of resources, and the MIME type that tells the host how to show them.
const kinds = [
  ["Static file", "file:///project/README.md", "text/markdown"],
  ["Database schema", "db://shop/schema", "application/sql"],
  ["Log file", "logs://api/today", "text/plain"],
  ["Record via template", "orders://A-1002", "application/json"],
  ["Image", "assets://logo.png", "image/png"],
];

for (const [kind, uri, mime] of kinds) {
  console.log(kind.padEnd(20), uri.padEnd(26), mime);
}

Expected output:

Static file          file:///project/README.md  text/markdown
Database schema      db://shop/schema           application/sql
Log file             logs://api/today           text/plain
Record via template  orders://A-1002            application/json
Image                assets://logo.png          image/png

No matter what they contain, resources should behave predictably. Hosts may cache resources, read them multiple times, or load them automatically. To support these scenarios, resources must follow a few important rules.

The Rules for Resources

Read-Only and No Side Effects

Reading a resource must never change anything: no 'mark as read', no counters stored in the data, no emails sent. A host may read the same resource many times, read it ahead of time, or keep a copy. If reading had side effects, all of that would cause trouble.

Deterministic Reads

Deterministic means "the same input gives the same output." Two reads of the same URI, close together, should return the same data unless the data itself changed in between.

A good read and a bad read

// Reading a resource must not change anything. Two reads in a row give the same answer.
const store = { "kb://shipping": "Standard shipping takes 3 to 6 days." };
let reads = 0;

function goodRead(uri) {
  reads++;
  return store[uri];
}

let counter = 0;
function badRead(uri) {
  counter++;
  return store[uri] + " (viewed " + counter + " times)";
}

console.log(
  "good:",
  goodRead("kb://shipping") === goodRead("kb://shipping")
    ? "same both times"
    : "different",
);
console.log(
  "bad: ",
  badRead("kb://shipping") === badRead("kb://shipping")
    ? "same both times"
    : "different: the read changed something",
);
console.log("reads counted outside the data:", reads);

Expected output:

good: same both times
bad:  different: the read changed something
reads counted outside the data: 2

Immutability Where You Can

Some resources never change once made, like an invoice for a past month or a specific version of a file. Put the version or date in the URI, like invoices://2026-09. Then a host can keep a copy forever, and a URI always means the same thing.

Resource text goes into the model's context, so it can carry prompt injection: text that tries to give the model new instructions. A support ticket or a web page saying "ignore your rules" is data, not a command. Hosts should label resource text as data and never let it change the rules.

Common mistakes

  • Using a tool where a resource fits. If it only returns data and the app can choose it, make it a resource.
  • Forgetting templates. Listing a million records is impossible. A template covers them all.
  • Sending big binaries as text. Use blob with base64, and the right MIME type.
  • Side effects on read. A read that changes data breaks caching and surprises users.
  • Trusting resource text. It is data. Label it, and never let it override the host's rules.

Exercises

Each exercise ends with a value on the last line. Press Check to compare it with the expected answer.

What to Remember

  • A resource is read-only data with a URI, a name, an optional title and description, and a MIME type.
  • Resources are application-driven: the host chooses what to read.
  • resources/read returns text or base64 blob contents.
  • Templates like orders://{order_id} cover many resources with one pattern.
  • Typical resources: static files, records, logs and schemas.
  • Reads must be side-effect free and deterministic. Resource text is data, never instructions.

Next: Prompts, ready-made message templates a user can pick.