# MCP resources: context the application decides to use

October 15, 2026 · Part 10 of 35

![MCP resources: context the application decides to use](https://pournasserian.com/_astro/lead.C4hGXaWQ_Ju8S9.webp)

Resources have been the forgotten primitive of the Model Context Protocol (MCP), because hosts were slow to show them. What surprised me is how much now runs on them: two extensions serve their files through `resources/read`, so a server with no documents to share may still need resources.

This is part 10 of my series on what an MCP server does under the 2026-07-28 specification, after [tool errors and tool design in part 9](https://pournasserian.com/writing/mcp-2026-tool-errors-and-design). The facts are as I read them in October 2026.

## In brief

1. **Resources are application-driven context.** Each is data with a Uniform Resource Identifier (URI), such as a file, a schema or a documentation page, and the host decides how and when to include it.
2. **A server answers three methods:** `resources/list`, `resources/templates/list` and `resources/read`, and can send updates through `subscriptions/listen`.
3. **A resource that doesn't exist MUST get error `-32602`,** ideally with the URI in `data`, never an empty `contents` array.
4. **Servers MUST validate every URI,** and MUST sanitize `file://` paths against directory traversal.
5. **My advice: implement resources even though hosts were slow to show them.** They pay off once you adopt MCP Apps or Skills, and a `resource_link` keeps a big document out of the model's context until someone asks for it.

## Context the host decides to use

Resources are data a server shares as context: files, database schemas, documentation, configuration, application records. The model picks tools; the host decides how and when to include resources.

The [specification's Resources page](https://modelcontextprotocol.io/specification/2026-07-28/server/resources) doesn't mandate a user interface (UI), but three patterns are common:

- a picker, as a tree or a list, where the user selects resources;
- search and filtering over the resources available;
- automatic inclusion, based on heuristics or on the model's own selection.

As of October 2026, four hosts show them like this:

| Host | How resources appear |
|---|---|
| Claude (web, Desktop) | Attach from the connector menu in the chat |
| [Claude Code](https://code.claude.com/docs/en/mcp) | `@server:protocol://path` mentions, fuzzy-searchable alongside files |
| [VS Code](https://code.visualstudio.com/docs/agent-customization/mcp-servers) (GitHub Copilot) | **Add Context**, then **MCP Resources**, or **MCP: Browse Resources** |
| [Gemini CLI](https://www.geminicli.com/docs/tools/mcp-server) | `@server://resource/path` references |

ChatGPT works differently. As of 8 October 2026, OpenAI documents two uses of resources. It loads `ui://` templates, served as `text/html;profile=mcp-app`, to [render an app's interface](https://developers.openai.com/plugins/build/chatgpt-ui). It also [imports Skills files as a snapshot](https://developers.openai.com/plugins/build/mcp-server) when a plugin is submitted. It doesn't document general-purpose resources you can attach as context.

## Listing, reading and templates

Your server declares resources in its capabilities, with two optional flags:

```json
{ "capabilities": { "resources": { "listChanged": true, "subscribe": true } } }
```

- `listChanged`: the server sends `notifications/resources/list_changed` when the set changes.
- `subscribe`: the server supports update notifications for single resources, requested through [`subscriptions/listen`](https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/subscriptions), covered in a later part.

Either flag, both or neither may be declared. As with tools and prompts, the listed set MUST NOT vary per connection, but MAY vary with the authorization on the request.

A client lists resources, reads one by its URI, and can ask to hear when it changes:

```mermaid
sequenceDiagram
    participant C as Client
    participant S as Server

    Note over C,S: Discovery
    C->>S: resources/list
    S-->>C: resources + nextCursor + ttlMs + cacheScope
    C->>S: resources/templates/list
    S-->>C: resourceTemplates (RFC 6570 URI templates)

    Note over C,S: Access
    C->>S: resources/read (uri)
    S-->>C: contents (text or blob) + ttlMs + cacheScope

    Note over C,S: Watch for changes
    C->>S: subscriptions/listen (resourceSubscriptions [uri])
    S-->>C: notifications/subscriptions/acknowledged
    S-->>C: notifications/resources/updated (uri)
    C->>S: resources/read (uri)
    S-->>C: updated contents
```

### Listing and reading

An entry in a resource list has a `uri` and a `name`, and may carry `title`, `description`, `icons`, `mimeType`, `size` (in bytes) and `annotations`. A complete read returns one or more content items, each either `text` or a base64 `blob`, with the caching hints `ttlMs` and `cacheScope` that [part 6](https://pournasserian.com/writing/mcp-2026-caching-and-pagination) covers:

```json
{
  "resultType": "complete",
  "contents": [
    { "uri": "file:///project/src/main.rs", "mimeType": "text/x-rust", "text": "fn main() { ... }" }
  ],
  "ttlMs": 60000,
  "cacheScope": "private"
}
```

A server MAY return several content items for one read: reading a directory resource might return the files in it.

### Templates

Resource templates expose parameterized resources with RFC 6570 URI templates, such as `file:///{path}` or `db://customers/{customerId}`. A client can autocomplete their arguments as the user types; the next part covers completion.

### Asking for input

`resources/read` is one of the three methods allowed to return `resultType: "input_required"`, part of [Multi Round-Trip Requests](https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr), which a later part explains. A server can, for example, ask which environment's configuration to read before returning it. That interim result [carries no caching hints](https://modelcontextprotocol.io/specification/2026-07-28/server/utilities/caching).

### Annotations

Resources, templates and content blocks can carry optional annotations:

| Annotation | Values | Use |
|---|---|---|
| `audience` | `["user"]`, `["assistant"]`, or both | Lets a host decide whether to show the content, give it to the model, or both |
| `priority` | 0.0 to 1.0 | 1 means effectively required context, 0 means entirely optional |
| `lastModified` | An ISO 8601 timestamp | Sort by recency, show freshness |

## URIs, errors and safety

Pick your scheme by what the resource is and who fetches it:

| Scheme | Guidance |
|---|---|
| `https://` | Use only when the client can fetch the resource from the web itself, without going through the server |
| `file://` | Filesystem-like resources, which don't have to map to a real filesystem. Directories may use `inode/directory` |
| `git://` | Git version-control integration |
| Custom (`db://`, `crm://` and so on) | Allowed if RFC 3986-compliant. Prefer a custom scheme over `https://` whenever the server does the fetching |
| `ui://` | Reserved for MCP Apps UI templates |
| `skill://` | The conventional scheme for Skills over MCP |

On errors, the specification is precise:

- A resource that doesn't exist MUST get `-32602` (Invalid Params), ideally with the URI in `data`. Clients SHOULD also accept the older `-32002`, for backward compatibility.
- A server MUST NOT return an empty `contents` array for a resource that doesn't exist, because an empty array is ambiguous.
- Internal failures SHOULD return `-32603`.

On safety, your server MUST validate all resource URIs, MUST encode binary data properly and, if it serves `file://` resources, MUST sanitize file paths against directory traversal. Access controls and permission checks SHOULD be applied to sensitive resources.

### In Python

With the official Python software development kit (SDK), `mcp` 2.3.0, a resource is a function under [`@mcp.resource`](https://py.sdk.modelcontextprotocol.io/servers/resources/). A URI with variables makes it a template, whose variables must match the function's parameters; a URI without them makes a static resource:

```python
from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ResourceNotFoundError

mcp = MCPServer("acme-docs")

PAGES = {
    ("api", "auth"): "# Authentication\nSend a bearer token with every request.",
    ("api", "errors"): "# Errors\nEvery error has a code and a message.",
}

@mcp.resource("docs://index", mime_type="text/markdown")
def index() -> str:
    """The table of contents."""
    return "\n".join(f"- docs://{area}/{slug}" for area, slug in PAGES)

@mcp.resource(
    "docs://{area}/{slug}", title="Documentation page", mime_type="text/markdown"
)
def page(area: str, slug: str) -> str:
    """One documentation page, by area and slug."""
    if (area, slug) not in PAGES:
        raise ResourceNotFoundError(f"No page {area}/{slug}")
    return PAGES[(area, slug)]
```

The in-memory client lists both kinds, reads a page, and asks for one that doesn't exist:

```python
import anyio
from mcp import Client, MCPError

async def main():
    async with Client(mcp) as client:
        listed = await client.list_resources()
        templates = await client.list_resource_templates()
        print([r.uri for r in listed.resources])
        print([t.uri_template for t in templates.resource_templates])
        item = (await client.read_resource("docs://api/auth")).contents[0]
        print(item.mime_type, repr(item.text))
        try:
            await client.read_resource("docs://api/billing")
        except MCPError as err:
            print(err.code, err.message, err.data)

anyio.run(main)
```

It prints (the server also logs the failed read to stderr):

```text
['docs://index']
['docs://{area}/{slug}']
text/markdown '# Authentication\nSend a bearer token with every request.'
-32602 No page api/billing {'uri': 'docs://api/billing'}
```

What the SDK does here:

- The static resource is listed by `resources/list`, the template by `resources/templates/list`, each with its docstring as its description.
- `ResourceNotFoundError` reaches the client as `-32602` with the URI in `data`, as the spec asks. Its parent `ResourceError` gives `-32603` with your message, and an `MCPError` you raise keeps its own code. Any other exception gives `-32603` with a generic message naming only the URI.
- Template values are checked first: `..`, absolute paths and null bytes are rejected by default, so reading `docs://api/..` got `-32602` ("Unknown resource") without reaching `page`.

## More than context

Resources play more roles than the word context suggests:

```mermaid
flowchart LR
    R["Resources primitive<br/>resources/list, resources/read"]
    R --> s1["Static context<br/>docs, schemas, config"]
    R --> s2["Templated context<br/>db://customers/{id}"]
    R --> s3["Live context<br/>status, metrics + subscriptions"]
    R --> s4["Tool output links<br/>resource_link in tool results"]
    R --> s5["UI templates<br/>ui://, MCP Apps"]
    R --> s6["Skill files<br/>skill://, Skills extension"]
```

Two extensions use them as plumbing: [MCP Apps](https://modelcontextprotocol.io/docs/extensions/apps) serves its HTML through `resources/read`, and [Skills over MCP](https://modelcontextprotocol.io/extensions/skills/overview) serves `SKILL.md` and its supporting files the same way. Later parts cover both.

My advice: use resources for anything big that the model shouldn't receive by default. Return a short summary and a `resource_link` from your tool (see [part 8](https://pournasserian.com/writing/mcp-2026-tool-definitions)), and let the host or the user decide whether to pull in the full document. That keeps context lean while the full content stays one click away.

## Method and caveats

- Built from my guide's chapter on resources, written against the 2026-07-28 specification. Host behaviour is as of October 2026 (ChatGPT's, 8 October 2026).
- The Python sample ran against `mcp` 2.3.0 on 9 October 2026 and printed the output above. The other SDK notes come from the package's source and from probes of a traversal URI and the other errors.
- Where OpenAI's documentation and the specification's Caching page are more precise than my notes (ChatGPT also reads app UI templates; only a complete result carries caching hints), this article follows them.

I'm [Amir Pournasserian](https://pournasserian.com/about). I build AI and platform systems for a living, maintain [FluentCMS](https://pournasserian.com/open-source/fluentcms) and [YeSvelte](https://pournasserian.com/open-source/yesvelte), and write here about what I find along the way.
