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
@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.
@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."View the code here
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.
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())policy://refunds refund_policy text/markdown # Refunds Full refund within 30 days of delivery. Damaged items: refund or replacement.
What listing and reading each do
list_resourcesshows the URI, name and type without running the function.read_resourceruns 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 by | Like a web API | |
|---|---|---|
| Tool | The model calls it | A POST that acts |
| Resource | The application loads it | A GET that reads |
| Prompt | The user picks it | A 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.
Related
- Previous: Tool annotations
- Next: Resource templates
- Reference: MCP server concepts
- Add a
policy://shippingresource 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.