Claude Academy
Sign in

MCP 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.md
  • postgres://db/schema/table
  • linear://team/ENG/issues
  • screen://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

NeedUse
"Get me X" with no decision requiredResource
"Decide which X to fetch then fetch"Tool that returns a resource
Side effects (write, send, delete)Tool
Browsable list the user picks fromResource

See also