MCP resources: context the application decides to use

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. The facts are as I read them in October 2026.
In brief
- 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.
- A server answers three methods:
resources/list,resources/templates/listandresources/read, and can send updates throughsubscriptions/listen. - A resource that doesn’t exist MUST get error
-32602, ideally with the URI indata, never an emptycontentsarray. - Servers MUST validate every URI, and MUST sanitize
file://paths against directory traversal. - 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_linkkeeps 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 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 | @server:protocol://path mentions, fuzzy-searchable alongside files |
| VS Code (GitHub Copilot) | Add Context, then MCP Resources, or MCP: Browse Resources |
| Gemini CLI | @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. It also imports Skills files as a snapshot 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:
{ "capabilities": { "resources": { "listChanged": true, "subscribe": true } } }
listChanged: the server sendsnotifications/resources/list_changedwhen the set changes.subscribe: the server supports update notifications for single resources, requested throughsubscriptions/listen, 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:
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 covers:
{
"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, 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.
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 indata. Clients SHOULD also accept the older-32002, for backward compatibility. - A server MUST NOT return an empty
contentsarray 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. A URI with variables makes it a template, whose variables must match the function’s parameters; a URI without them makes a static resource:
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:
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):
['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 byresources/templates/list, each with its docstring as its description. ResourceNotFoundErrorreaches the client as-32602with the URI indata, as the spec asks. Its parentResourceErrorgives-32603with your message, and anMCPErroryou raise keeps its own code. Any other exception gives-32603with a generic message naming only the URI.- Template values are checked first:
.., absolute paths and null bytes are rejected by default, so readingdocs://api/..got-32602(“Unknown resource”) without reachingpage.
More than context
Resources play more roles than the word context suggests:
Two extensions use them as plumbing: MCP Apps serves its HTML through resources/read, and Skills over MCP 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), 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
mcp2.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.
Series What an MCP server actually does in 2026 Part 10 of 35
- What an MCP server does in 2026: much more than a list of tools
- MCP goes stateless: what changed in the 2026-07-28 specification
- server/discover: the one method every MCP server must implement
- Server instructions: the paragraph every model reads first
- Stateless MCP requests: _meta, resultType and explicit handles
- Caching hints and pagination in MCP
- MCP transports in 2026: stdio and Streamable HTTP
- Designing an MCP tool: the definition and the result
- MCP tool errors, and tools that models use well
- MCP resources: context the application decides to use
- MCP prompts: slash commands your server ships
- Tool, resource or prompt? Choosing the right MCP primitive