Vault / wiki/201/mcp-resources.md
updated 2026-05-28MCP Resources
Application-controlled, read-only data sources exposed by an MCP server. Think files, query results, log streams — anything you'd "read" rather than "do".
Key distinction: who decides
- Tools = model-controlled (the LLM picks when to call).
- Resources = application-controlled (the host app surfaces them to the user / includes in context).
- Prompts = user-controlled (user explicitly picks).
A resource isn't "called" by Claude. The host application (e.g., Claude Desktop) lets the user attach a resource to their context, then Claude reads it.
Resource URIs
Each resource has a URI scheme like:
file:///path/to/doc.mdpostgres://db/schema/tablelinear://team/ENG/issuesscreen://current(a screenshot)
The scheme is opaque to MCP — your server defines it.
Server methods
@server.list_resources()
async def list_resources():
return [
Resource(
uri="postgres://main/users",
name="Users table",
description="Production users table; PII redacted on read.",
mimeType="application/json",
)
]
@server.read_resource()
async def read_resource(uri: str):
if uri == "postgres://main/users":
rows = await db.query("SELECT id, email_hash FROM users LIMIT 1000")
return [{"uri": uri, "mimeType": "application/json", "text": json.dumps(rows)}]
Resource templates
URIs can be templated with parameters:
postgres://main/users/{user_id}
linear://issue/{key}
The client expands them at runtime. Templates appear in resources/templates/list.
Subscriptions / notifications
A server can notify clients when a resource updates:
notifications/resources/updated uri=...
Useful for live-updating data (logs, dashboards, watched files).
Resources vs tools — when which
| Need | Use |
|---|---|
| "Get me X" with no decision required | Resource |
| "Decide which X to fetch then fetch" | Tool that returns a resource |
| Side effects (write, send, delete) | Tool |
| Browsable list the user picks from | Resource |