Reading Resources in the Client
Why Read Resources Client-Side
Our client can list and call tools. Now we add the ability to read resources — which unlocks a much smoother user experience. The motivating feature: document mentions. When a user types '@report.pdf', the app injects the document's contents directly into the prompt sent to Claude, so the model already has the data and doesn't need to make a tool call to fetch it.
This is the app-controlled nature of resources in action: your client decides to read the resource and place its contents into context, rather than waiting for the model to request it. It makes for faster, cleaner interactions — the answer comes immediately because the context is already present.
Two operations power '@mentions'
Mentions need exactly two resource reads: list all documents (the direct resource docs://documents) to power autocomplete as the user types '@', and fetch one document (the templated resource docs://documents/{id}) when they select it. Both are resources/read calls under the hood.
Implementing read_resource
We add a read_resource method to the client. It calls the session's read_resource with a URI, then processes the response according to its MIME type — parsing JSON when the server says application/json, and returning raw text otherwise. First, the imports:
import json
from pydantic import AnyUrl
import mcp.types as types
from typing import Anyasync def read_resource(self, uri: str) -> Any:
result = await self.session().read_resource(AnyUrl(uri))
resource = result.contents[0]
if isinstance(resource, types.TextResourceContents):
if resource.mimeType == "application/json":
return json.loads(resource.text)
return resource.textUnderstanding the Response
When you request a resource, the server returns a result with a contents list. We take the first element because we typically need one resource at a time. Each content item carries the actual data, a MIME type telling us how to parse it, and other metadata. The MIME-type check is the key decision point: application/json gets parsed into a Python object; anything else is returned as raw text.
| Server returns mimeType | Client does | Result type |
|---|---|---|
| application/json | json.loads(resource.text) | parsed object (list/dict) |
| text/plain | return resource.text | string |
| other text types | return resource.text | string |
The MIME type drives how the client parses the resource — structured data vs plain text, handled seamlessly.
The Mention Experience
With read_resource in place, the CLI '@' feature works end to end. Type '@', the app reads docs://documents to show an autocomplete list; pick one with the arrow keys; the app reads docs://documents/{id}, injects the contents into your prompt, and sends everything to Claude — no tool call needed. The model can answer about the document immediately because the data is already in context.
- 1.User types '@' — app reads docs://documents (JSON) and shows an autocomplete list.
- 2.User selects a document with arrow keys / space.
- 3.App reads docs://documents/{id} (text) to get the contents.
- 4.App injects the contents directly into the prompt and sends it to Claude.
- 5.Claude responds immediately about the document — no separate tool call required.
Resources vs tools for fetching data
This is the practical contrast from the primitives section. Could a tool fetch the document? Yes — but that adds a round trip (Claude decides → app runs tool → result returns). Reading it as a resource puts the data in context up front, which is faster and keeps the app in control of what's included. Choose resources when your APP knows it needs the data.
Next
Tools and resources are wired into the client. The last piece is prompts — letting users trigger those pre-built server workflows from the client. That completes the client.
Key Takeaways
- ✓Reading resources client-side powers '@mention' features: the app injects document contents into the prompt so Claude needs no tool call to fetch them.
- ✓This showcases resources being application-controlled — the client decides to read and inject, rather than the model requesting it.
- ✓Mentions use two reads: the direct resource docs://documents for autocomplete, and the templated docs://documents/{id} for the selected document.
- ✓read_resource calls session.read_resource(AnyUrl(uri)) and takes result.contents[0] (one resource at a time).
- ✓Parse by MIME type: application/json → json.loads into a Python object; otherwise return the raw text.
- ✓Prefer a resource over a tool when your app already knows it needs the data — it avoids the extra model round trip and keeps the app in control of context.
Check Your Understanding
Test what you learned in this lesson.
Q1.What user-facing feature does client-side resource reading enable in the course example?
Q2.In read_resource, why does the code check resource.mimeType?
Q3.Why take result.contents[0] when reading a resource?
Q4.When should you fetch data via a resource rather than a tool?
Practice This Lesson