Defining resources
Tools are for actions — read this, edit that, create a ticket, fetch a URL. Resources are for data — the stuff an application wants to fetch and embed directly into a prompt without going through Claude's tool-use loop. If you're building a "type @filename.md to reference a document" feature, you don't want to burn a round-trip to Claude just to get the document content. You want to fetch the document data directly from the MCP server and slot it into the prompt yourself. Resources are how you do that.
The difference in one sentence
Tools perform actions (Claude decides when to call them); resources expose data (your application requests them directly). Tools go through the Claude tool-use loop. Resources bypass it entirely.
Think HTTP: tools are like POST endpoints — they cause side effects and Claude orchestrates when they run. Resources are like GET endpoints — your application can hit them whenever it needs data, and the user or UI decides when.
A motivating example
You're building a chat UI where users type @ and get an autocomplete list of available documents. Once a user picks one and sends the message, you want the document's content automatically injected into the prompt sent to Claude.
That feature needs two operations:
- List all available documents — for the autocomplete dropdown.
- Fetch a specific document's content — when a user actually mentions one.
Both are pure data fetches. Nothing needs Claude's judgment. Making either of these a tool would be wasteful — you'd be asking Claude to call a tool purely to pass data around, consuming tokens and latency for no reasoning benefit.
With resources, you hit the MCP server directly, get the data back, and compose the prompt yourself. Much cleaner.
How resources work
Resources are addressed by URIs. Your client sends a ReadResourceRequest with a URI, and the server responds with the data at that URI plus metadata like a MIME type. The URI is a string you choose — conventionally it starts with a custom scheme like docs:// to namespace your server's resources.
Two flavours of resource:
- Direct resources have a fixed URI. Examples:
docs://documents,config://settings,status://health. Useful for "give me the list of all X" or "give me the current state of Y." - Templated resources have URIs with parameters. Examples:
docs://documents/{doc_id},users://{user_id}/profile. Useful for "give me the data for this specific thing."
For templated resources, the MCP Python SDK parses the parameters out of the URI and passes them as keyword arguments to your handler function — just like the way a web framework parses path parameters.
Defining a direct resource
For the document project, the "list all documents" endpoint is a natural direct resource:
@mcp.resource(
"docs://documents",
mime_type="application/json",
)
def list_docs() -> list[str]:
return list(docs.keys())
Three things:
@mcp.resource("docs://documents", ...)registers the function as a resource at the static URIdocs://documents.mime_type="application/json"hints to the client that the response should be interpreted as JSON. The SDK handles the serialisation — you return a Python list, the client sees a JSON array.list(docs.keys())is the entire implementation. Returns a list of document IDs.
Hit this resource and you get back ["deposition.md", "report.pdf", "financials.docx", ...].
Defining a templated resource
For "fetch a specific document's content," you want a URI with a parameter:
@mcp.resource(
"docs://documents/{doc_id}",
mime_type="text/plain",
)
def fetch_doc(doc_id: str) -> str:
if doc_id not in docs:
raise ValueError(f"Doc with id {doc_id} not found")
return docs[doc_id]
Four things:
"docs://documents/{doc_id}"— the URI template. The{doc_id}segment is the parameter.- Function parameter
doc_id: str— the name matches the URI segment. The SDK extractsdoc_idfrom the URI at request time and passes it as a keyword argument. mime_type="text/plain"— tells the client the response is plain text, not JSON. The SDK returns the string as-is without JSON-wrapping.ValueErroron unknown doc_id — same pattern as tools. Errors surface to the client with a useful message.
A client requesting docs://documents/deposition.md gets back the text content of that document, ready to splice into a prompt.
MIME types
Resources can return any data format. The mime_type parameter tells clients what to expect:
| MIME type | Use for |
|---|---|
| application/json | Structured JSON data (lists, dicts, nested objects) |
| text/plain | Plain text content (document bodies, logs, raw strings) |
| text/markdown | Markdown-formatted text |
| text/html | HTML content |
| application/octet-stream | Binary blobs |
The SDK serialises your return value automatically based on the MIME type. A function that returns a Python list with application/json gets serialised to a JSON array. A function that returns a string with text/plain gets returned as-is. You don't write the serialisation.
Testing resources in the inspector
The MCP Inspector from the last lesson lists resources alongside tools. Start the inspector and you'll see:
- Resources — all your direct resources. Click to view or refetch.
- Resource Templates — all your templated resources. Click, fill in parameter values, and invoke.
For the document project, click docs://documents under Resources and you should see the list of document IDs. Click the docs://documents/{doc_id} template under Resource Templates, enter deposition.md, and you should see the document content.
Use the inspector to verify resources the same way you verified tools — run them in isolation, confirm they return the expected shape, then wire them into your client.
Resources vs tools, a quick reference
| Concern | Use a resource | Use a tool | |---|---|---| | Pure data fetch ("give me X") | ✓ | | | Action with side effects ("create, update, delete") | | ✓ | | Triggered by user or UI | ✓ | | | Triggered by Claude's reasoning | | ✓ | | Cost-sensitive (want to avoid round trips) | ✓ | | | Benefits from Claude deciding when to run | | ✓ |
Both can coexist on the same MCP server — and often should. For the document project, read_doc_contents and edit_document are tools (Claude decides when to call them), while docs://documents and docs://documents/{doc_id} are resources (your UI fetches them directly). The client side for both is different, which is the topic of the next lesson: implementing read_resource in your MCPClient wrapper.
Key Takeaways
- 1Resources expose data and are requested directly by your application; tools perform actions and are invoked by Claude during its reasoning loop.
- 2Resources are addressed by URIs — direct resources have fixed URIs like docs://documents, templated resources have URI parameters like docs://documents/{doc_id}.
- 3Define resources with @mcp.resource(uri, mime_type=...) — the SDK extracts URI parameters and passes them as keyword arguments to your handler function.
- 4MIME types hint to clients how to parse the response (application/json, text/plain, text/markdown, etc.) and the SDK handles serialisation automatically.
- 5Resources are for data, tools are for actions — use resources when your UI needs to fetch something directly without going through the Claude tool-use round trip.
- 6The MCP Inspector shows resources alongside tools, so you can list, parameterise, and invoke them interactively before wiring them into a client.