MCP resources: context the application decides to use Under the 2026-07-28 Model Context Protocol specification, servers must implement three resource methods — resources/list, resources/templates/list and resources/read — and return error -32602 for a resource that does not exist rather than an empty contents array, according to part 10 of a technical series on MCP server behavior. The author advises developers to implement resources even though hosts were slow to surface them, noting that as of October 2026 Claude, Claude Code, VS Code (GitHub Copilot) and Gemini CLI expose resources through attach menus or @-mentions, while OpenAI documents only ui:// template rendering and Skills file snapshots for ChatGPT. Servers must validate every URI and sanitize file:// paths against directory traversal, and can declare listChanged and subscribe capability flags for change notifications. 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 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: { "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: 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: { "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 : '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: 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.