Resources: data an application loads
A tool is what the model decides to call. A resource is data the application decides to load, such as a policy, and put in front of the model.
@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."@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.
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())list_resources shows what exists; read_resource runs the function for one URI and returns its contents. Listing never calls the function, so a server can offer many resources and only pay for the ones that are opened.
Who decides
The SDK's docs split the three things a server offers by who decides to use them. The model decides to call a tool. The application decides to attach a resource, often because the user picked it: in Claude Desktop and Claude Code you can add a server's resource to the conversation yourself. The user picks a prompt, which is lesson 10.
Like a web API, a resource is the GET: it loads data and changes nothing. A tool can be the POST.
- 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.
Slow is fine. Stopping is the only problem.