Model Context ProtocolMCP Python SDK 2.2 · LangChain 1.4 · Python 3.10+
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
27 small wins to finish your pathNext lesson →

Resources

A resource is read-only data an application loads by its URI, such as a policy, and puts in front of the model.

Last updated: 29 Sep, 2026 · MCP 2.2

A tool is what the model decides to call. A resource is different: the application decides to load it. The shop server has tools; here it gains its first resource, a refund policy.

The @mcp.resource decorator

python
@mcp.resource("policy://refunds", mime_type="text/markdown")
def refund_policy() -> str:          # found by its URI, not its name
    """The shop's refund policy."""
    return "..."

The refund_policy resource

@mcp.resource(uri) registers a function as a resource. A resource is found by its URI, policy://refunds, not by the function's name. mime_type says what kind of text it is; without it, the type is plain text.

python
@mcp.resource("policy://refunds", mime_type="text/markdown")
def refund_policy() -> str:
    """The shop's refund policy."""
    return "# Refunds\n\nFull refund within 30 days of delivery. Damaged items: refund or replacement."
Project files used on this pageThis lesson builds on a project from earlier lessons. The code below imports this file. Click a file to see its code, or follow the link to the lesson that wrote it. To run the code yourself, keep it in the same folder.
View the code here
shop.py
from typing import Annotated, Literal

from pydantic import BaseModel, Field

from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ToolError
from mcp.types import ToolAnnotations

mcp = MCPServer("Shop support")

ORDERS = {
    "A17": {"item": "blue mug", "status": "shipped", "total": 12.50},
    "B42": {"item": "desk lamp", "status": "processing", "total": 48.00},
}

ARTICLES = {
    "Where is my order?": "orders",
    "Changing an order": "orders",
    "How refunds work": "refunds",
    "Refunds for damaged items": "refunds",
    "Resetting your password": "account",
}


class Order(BaseModel):
    id: str
    item: str
    status: Literal["processing", "shipped", "delivered"]
    total: float


@mcp.tool(annotations=ToolAnnotations(read_only_hint=True))
def lookup_order(order_id: str) -> Order:
    """Look up an order by its id."""
    if order_id not in ORDERS:
        raise ToolError(f"No order with id {order_id!r}. Order ids look like A17.")
    return Order(id=order_id, **ORDERS[order_id])


@mcp.tool()
def search_help(
    query: Annotated[str, Field(description="Words to look for in the help articles.")],
    topic: Literal["orders", "refunds", "account"] | None = None,
    limit: Annotated[int, Field(ge=1, le=5)] = 3,
) -> str:
    """Search the help centre and return matching article titles."""
    found = [title for title, t in ARTICLES.items() if query.lower() in title.lower() and topic in (None, t)]
    return "; ".join(found[:limit]) or "No articles found."


@mcp.tool(
    title="Refund an order",
    annotations=ToolAnnotations(read_only_hint=False, destructive_hint=False, idempotent_hint=False),
)
def refund_order(order_id: str, reason: str) -> str:
    """Refund the full total of an order to the customer's card."""
    if order_id not in ORDERS:
        raise ToolError(f"No order with id {order_id!r}.")
    return f"Refunded {ORDERS[order_id]['total']:.2f} for order {order_id}: {reason}."


@mcp.resource("policy://refunds", mime_type="text/markdown")
def refund_policy() -> str:
    """The shop's refund policy."""
    return "# Refunds\n\nFull refund within 30 days of delivery. Damaged items: refund or replacement."

Listing and reading a resource

Listing shows what exists; reading runs the function for one URI.

Example
import asyncio

from mcp import Client
from shop import mcp


async def main():
    async with Client(mcp) as client:
        listed = await client.list_resources()
        for resource in listed.resources:
            print(resource.uri, resource.name, resource.mime_type)

        result = await client.read_resource("policy://refunds")
        print(result.contents[0].text)


asyncio.run(main())

What listing and reading each do

  • list_resources shows the URI, name and type without running the function.
  • read_resource runs the function for one URI and returns its contents.
  • Listing is free, so a server can offer many resources and only pay for the ones that are opened.

Who decides: tool, resource or prompt

Decided byLike a web API
ToolThe model calls itA POST that acts
ResourceThe application loads itA GET that reads
PromptThe user picks itA saved template

In Claude Desktop and Claude Code you can add a server's resource to the conversation yourself, which is the application deciding on your behalf. Prompts are the user's pick.

When to use a resource

  • Reference text a model should read but not change, like a policy or a style guide.
  • Data the user or app chooses to attach, rather than something the model calls for.
Watch out. A resource is a read: it should load data and change nothing. If a function has a side effect, make it a tool so the model, and any approval the host adds, is in the loop.
Try it yourself
  • Add a policy://shipping resource with the shop's delivery times.
  • Return a dictionary from a resource with mime_type="application/json" and read it.
  • Read policy://returns, which does not exist, and read the error.

Every expert started right here.