Resources, Prompts, and Common Utilities
This guide explains the server features other than Tools (Resources and Prompts) and the common utilities (Completion, Pagination, and Caching). For Tools, see Tools - Defining and Executing Tools; for the big picture, see Introduction to MCP - What Is the Model Context Protocol.
1. What This Document Covers
- Resources
- The resource-not-found error
- Prompts
- Putting Prompts to use
- Completion
- Pagination
- Caching
- Choosing by who controls the feature
Resources
Resources are data that a server exposes to clients as context. Files, database schemas, and application-specific information are each identified uniquely by a URI (RFC 3986). Resources are an application-driven feature: the host application decides which resources to bring into the context and how. For example, explicit selection from a tree or list, search and filtering, or automatic inclusion by heuristics or the model's choice.
The server declares the resources capability.
{
"capabilities": {
"resources": {
"listChanged": true,
"subscribe": true
}
}
}
listChanged: whether notifications are sent when the list of available resources changessubscribe: whether update notifications for individual resources are supported
The two are independent, and a server with neither can declare "resources": {}.
The methods related to resources are as follows.
| Method | Purpose |
|---|---|
resources/list | List of available resources (supports pagination and caching) |
resources/read | Fetch contents by specifying a URI (supports caching) |
resources/templates/list | List of parameterized resources by URI template |
A resource definition consists of uri, name, title, description, icons, mimeType, and size. The contents (contents) carry text for text or a base64 blob for binary data. A single resources/read may return multiple contents (for example, several files when a directory is read).
Resources and content blocks can carry annotations as hints for the consumer.
audience: the intended readers; an array of"user"and"assistant"priority: importance from 0.0 to 1.0. 1 means "most important (effectively required)" and 0 means "entirely optional"lastModified: the last modified time in ISO 8601 format
resources/templates/list exposes parameterized resources with RFC 6570 URI templates, for example file:///{path}. The arguments of a template can be completed with Completion, described below.
Subscriptions changed in the 2026-07-28 version. To follow updates to individual resources, list the URIs in notifications.resourceSubscriptions of subscriptions/listen, and the server delivers notifications/resources/updated on that stream for every change. The _meta of the notification carries io.modelcontextprotocol/subscriptionId, which shows which subscription it belongs to. The former resources/subscribe was replaced by this mechanism. Subscribe to list changes with resourcesListChanged: true. For details on subscriptions, see MCP Protocol Basics - JSON-RPC and Stateless Design and Transports - stdio and Streamable HTTP.
URI schemes are free to choose, but the specification lists these common ones.
https://: use only when the client can fetch the resource from the web on its own, without going through the MCP server. Otherwise use another scheme or a custom schemefile://: resources that behave like a filesystem. They need not be actual physical files. For non-regular files such as directories, a MIME type likeinode/directorycan be usedgit://: Git version control integration- Custom schemes: follow RFC 3986
resources/read can also return resultType: "input_required" when user input is needed mid-operation. See Elicitation and Multi Round-Trip Requests for details.
On the security side, the server must validate all resource URIs, apply access control to sensitive resources, and, when serving file:// resources, sanitize paths to prevent directory traversal.
The resource-not-found error
For a resource that does not exist, the server MUST return the JSON-RPC error code -32602 (Invalid Params). For internal errors, -32603 is recommended. Earlier protocol versions used -32002, so for backward compatibility clients SHOULD also accept -32002 as "resource not found."
The server MUST NOT return an empty contents array for a resource that does not exist, because an empty array can be read either as "the resource exists but is empty" or as "it does not exist."
{
"jsonrpc": "2.0",
"id": 5,
"error": {
"code": -32602,
"message": "Resource not found",
"data": { "uri": "file:///nonexistent.txt" }
}
}
Prompts
Prompts are prompt templates that a server exposes. They are a user-controlled feature: the intent is that the user explicitly chooses and invokes them. This is about who decides when to use them, not about who writes the content; the server defines the content. They are typically implemented as slash commands in the UI.
A server that declared the prompts capability responds to prompts/list (supports pagination and caching) and prompts/get.
A prompt definition consists of name, title, description, icons, and arguments (name, description, whether required). prompts/get takes the arguments and returns an array of messages, each with a role (user or assistant) and content. The content is one of text, image, audio, resource_link, or an embedded resource. Arguments can be completed with Completion.
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"resultType": "complete",
"description": "Code review prompt",
"messages": [
{
"role": "user",
"content": { "type": "text", "text": "Please review this Python code:\ndef hello():\n print('world')" }
}
]
}
}
For errors, an unknown prompt name and a missing required argument are -32602, and internal errors are -32603. The server carefully validates prompt inputs and outputs to prevent injection. prompts/get can also return input_required. List changes are announced with notifications/prompts/list_changed to clients that specified promptsListChanged: true in subscriptions/listen.
Putting Prompts to use
Prompts become the entry point to a standard workflow that the user invokes. Procedures known to the party providing the server, such as "start a code review" or "check the initial response to an incident," can be distributed as templates with arguments.
Recently there has also been interest in using Prompts as a way to distribute skills. However, client support varies. Some clients expose them as slash commands, while others do not handle Prompts at all. A design that depends only on Prompts loses the feature in clients that do not support them. For distributing structured procedures for agents, a dedicated extension, Skills over MCP, has been defined (see MCP Extensions - Tasks, MCP Apps, and Skills). For important procedures, it is safer to make them reachable through other routes as well, such as the descriptions of Tools, and not through Prompts alone.
Completion
Completion returns candidates, like IDE code completion, for the arguments of prompts and of resource templates. The server declares the completions capability.
{
"jsonrpc": "2.0",
"id": 1,
"method": "completion/complete",
"params": {
"ref": { "type": "ref/prompt", "name": "code_review" },
"argument": { "name": "framework", "value": "fla" },
"context": { "arguments": { "language": "python" } }
}
}
ref: what is being completed.ref/prompt(a prompt name) orref/resource(a resource URI or template)argument: thenameof the argument being completed, and its currentvaluecontext.arguments: other arguments already decided. When there are multiple arguments, candidates can be narrowed using the values of the earlier ones
The result is values, ordered by relevance (at most 100 items), an optional total (the total number of matches), and hasMore, which shows whether more candidates exist. The server returns them ordered by relevance, uses fuzzy matching, and rate-limits completion requests. The client debounces rapid input and caches results appropriately. Completion can also become a route for information disclosure, so the server controls access to sensitive candidates.
Pagination
resources/list, resources/templates/list, prompts/list, and tools/list support pagination with an opaque cursor, not numbered pages.
- The server returns one page of results and, if there is more, a
nextCursor - The client puts
nextCursorintoparams.cursorto request the continuation - The server decides the page size. Clients must not assume a fixed size
- If there is no
nextCursor, that is the end of the results - A cursor is an opaque token. Do not assume its format, and do not parse or modify it. An empty string is also a valid cursor and is not treated as the end (judge only by whether a value is present)
- For an invalid cursor, return
-32602(Invalid params)
Caching
The 2026-07-28 version attaches caching hints to some results. They are called CacheableResult: for results with resultType: "complete", the server MUST always include the following two fields. The targets are server/discover, tools/list, prompts/list, resources/list, resources/templates/list, and resources/read. Interim results of input_required carry no hints and are not cached.
| Field | Meaning |
|---|---|
ttlMs | How long (in milliseconds) the client may consider the result fresh. Close to HTTP Cache-Control: max-age. The server returns a value of 0 or more |
cacheScope | public or private. Close to HTTP Cache-Control: public / private |
If ttlMs is 0, the result is treated as stale immediately. When it is absent, it is treated as 0. A TTL is a hint about freshness, not a guarantee; the content can change before it expires. A client must not use the TTL as a polling interval for automatic refetching (if it does, jitter and backoff are mandatory); it checks freshness when it needs the data and fetches again if stale.
The meaning of cacheScope is as follows.
public: contains no user-specific data. Any client or shared gateway may reuse the cache for any userprivate: contains per-caller data. It can be reused only within the same authorization context, and must not be shared with caches for other access tokens
A tools/list or resource template that does not depend on the user suits public, while a resources/read whose content changes with the authenticated user, or a list filtered per user, suits private. Even a result returned from an authenticated endpoint can be shared beyond the authorization boundary if it is public. The server must not rely on cacheScope alone and must always apply per-primitive access control.
The cache key is the combination of the method and the parameters that affect the result (uri for resources/read, cursor for pagination). The results of retries that carry inputResponses or requestState must not be cached. In a paginated list, each page is cached independently and may have a different ttlMs, but cacheScope is the same across all pages of one list.
list_changed notifications and the TTL are complementary. You can use only the TTL or only notifications; using both lets the TTL avoid unnecessary refetches while notifications invalidate immediately. If a relevant notification arrives while the cache is still fresh, treat the cache as stale immediately.
Choosing by who controls the feature
Tools are model-controlled, Resources are application-controlled, and Prompts are user-controlled. The table below summarizes this.
2. Choosing Between Tools, Resources, and Prompts
| Feature | Who controls it | Examples |
|---|---|---|
| Tools | Model | Searching issues, creating tickets, running deployments |
| Resources | Application (host) | Attaching files, schemas, and settings as context |
| Prompts | User | Invoking a standard workflow such as "start a code review" |
The criterion is who decides to use it. Operations with side effects, or things you want the model to choose, belong in Tools. Data that is only referenced, where the user or application picks what to attach, is Resources. A standard procedure that the user explicitly starts is Prompts. Note, however, that real clients support Resources and Prompts less widely than Tools. If you expose a capability the model must use autonomously through Resources alone, the model may not use it. At present Tools are more widely supported.
3. Common Pitfalls for Developers
3.1 Assuming "if there are Resources, the model reads them automatically"
Resources are application-driven. Whether the model reads them on its own depends on the host's implementation. For data you want the model to read, include a resource_link in the result of a Tool, or also provide a tool for reading.
3.2 Returning an empty array for a missing resource
An empty contents for a nonexistent URI cannot be told apart from an "empty resource." Return a -32602 error. Clients also tolerate -32002, for servers of earlier versions.
3.3 Forgetting ttlMs and cacheScope, or marking a per-user result public
Always attach them to the results of list-type methods and resources/read. If you mark a result whose content varies per user as public, one user's content gets mixed into the cache for another user's token. When in doubt, use private.
3.4 Assuming page size or cursor format
A client must not parse a cursor or treat an empty string as the end. On the server side, return -32602 for an invalid cursor, and in that case the client discards all cached pages and fetches again from the beginning.
3.5 Using resources/subscribe for resource subscriptions
The 2026-07-28 version has no resources/subscribe. Pass the URIs in resourceSubscriptions of subscriptions/listen. Confirm the scope the server accepted in the first notifications/subscriptions/acknowledged.
3.6 Using a file:// path as is
If you open the URI of resources/read directly as a filesystem path, directory traversal with .. succeeds. Normalize it, and verify that it falls inside the permitted root.
3.7 Assuming Prompts are supported
In a client that does not support Prompts, the feature is invisible. When you use Prompts, make equivalent content reachable by another route as well.
4. Code Examples
Listing and reading resources
{
"jsonrpc": "2.0",
"id": 2,
"method": "resources/read",
"params": { "uri": "file:///project/src/main.rs" }
}
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"resultType": "complete",
"contents": [
{
"uri": "file:///project/src/main.rs",
"mimeType": "text/x-rust",
"text": "fn main() {\n println!(\"Hello world!\");\n}"
}
],
"ttlMs": 60000,
"cacheScope": "private"
}
}
Subscribing to resources
{
"jsonrpc": "2.0",
"id": 4,
"method": "subscriptions/listen",
"params": {
"notifications": {
"resourcesListChanged": true,
"resourceSubscriptions": ["file:///project/config.json"]
}
}
}
(_meta is omitted. In the 2026-07-28 version it is required on every request.)
Completing a prompt argument
{
"jsonrpc": "2.0",
"id": 1,
"method": "completion/complete",
"params": {
"ref": { "type": "ref/prompt", "name": "code_review" },
"argument": { "name": "language", "value": "py" }
}
}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "complete",
"completion": { "values": ["python", "pytorch", "pyside"], "total": 10, "hasMore": true }
}
}
A pagination read loop
async function listAllTools(client: Client): Promise<Tool[]> {
const all: Tool[] = [];
let cursor: string | undefined = undefined;
do {
const page = await client.listTools(cursor ? { cursor } : {});
all.push(...page.tools);
cursor = page.nextCursor; // An empty string is also a valid cursor. Judge only by whether it is undefined
} while (cursor !== undefined);
return all;
}
5. Summary
- Tools are model-controlled, Resources are application-controlled, and Prompts are user-controlled. Choose by who controls the feature
- Resources are identified by URI and have
resources/list,resources/read, andresources/templates/list. Not found is-32602. Never return an emptycontents - Subscriptions were consolidated into
subscriptions/listen. Do not useresources/subscribe - Prompts are templates the user explicitly invokes. Client support varies, so do not rely on Prompts alone
- Completion completes arguments (up to 100 items), and Pagination uses opaque cursors
- Caching uses
ttlMsandcacheScope. Results that vary per user areprivate.cacheScopeis no substitute for access control