Walk through resources/list, resources/read, and pagination with cursors.
Quick Answer
resources/list returns the available fixed resources; for large catalogs, the response includes a nextCursor string, and the client passes that back as the cursor param on a follow-up resources/list call to fetch the next page, continuing until no nextCursor is returned. resources/read takes a specific uri and returns a contents array (usually one entry, but a directory-like resource can return multiple) with either text (for text-based content) or blob (base64-encoded binary) per entry, plus a mimeType. Pagination exists specifically so a server with thousands of resources (e.g., every file in a large repo) doesn't have to return them all in one potentially enormous response, and so a client can start acting on the first page while later pages are still being fetched.
Detailed Answer
Pagination in MCP follows a simple cursor pattern, used identically across resources/list, tools/list, and prompts/list.
The listing flow with pagination
// First page
{"method": "resources/list", "params": {}}
// -> {"resources": [...], "nextCursor": "eyJvZmZzZXQiOjEwMH0="}
// Next page
{"method": "resources/list", "params": {"cursor": "eyJvZmZzZXQiOjEwMH0="}}
// -> {"resources": [...], "nextCursor": null} // no nextCursor => done
The cursor is an opaque string from the client's point of view. It might encode an offset, a database key, anything the server wants. Clients must not try to parse or construct cursors themselves — only pass back exactly what the server last gave them.
Reading a resource
{"method": "resources/read", "params": {"uri": "file:///project/data.csv"}}
// Text content
{"result": {"contents": [
{"uri": "file:///project/data.csv", "mimeType": "text/csv", "text": "id,name\n1,Alice\n2,Bob"}
]}}
// Binary content
{"result": {"contents": [
{"uri": "file:///project/logo.png", "mimeType": "image/png", "blob": "iVBORw0KGgoAAAANSUhEUg..."}
]}}
Why cursor-based, not offset/limit
A raw numeric offset breaks if the underlying data set changes between page fetches — items shift, and a delete shifts every subsequent offset. An opaque cursor lets the server encode whatever positioning scheme is actually stable for its data source: a database's own keyset pagination, a stable sort key, or a snapshot ID. None of that implementation detail, or its instability, leaks into the protocol itself.
A common implementation mistake
Treating resources/list as if it always returns everything in one call, and never checking for nextCursor. This works fine in local testing with a handful of files. It silently truncates results the moment a server is pointed at a real, large dataset.